pi-harness-runtime 1.1.395 → 1.1.397

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 (210) hide show
  1. package/AGENTS.md +64 -8
  2. package/package.json +1 -1
  3. package/packages/a2a-adapter/package.json +1 -1
  4. package/packages/agent-policy/package.json +1 -1
  5. package/packages/architecture-generator/package.json +1 -1
  6. package/packages/auth/package.json +1 -1
  7. package/packages/autonomous-refactor/package.json +1 -1
  8. package/packages/autonomous-runtime/package.json +1 -1
  9. package/packages/cache-strategy/package.json +1 -1
  10. package/packages/capability-registry/package.json +1 -1
  11. package/packages/checkpoint/package.json +1 -1
  12. package/packages/cli-plugin-sdk/package.json +1 -1
  13. package/packages/clipboard/package.json +1 -1
  14. package/packages/clipboard-plugin/package.json +1 -1
  15. package/packages/code-generation/package.json +1 -1
  16. package/packages/code-review/package.json +1 -1
  17. package/packages/codex-adapter/package.json +1 -1
  18. package/packages/codex-watcher/package.json +1 -1
  19. package/packages/config-capture/package.json +1 -1
  20. package/packages/connection-runtime/package.json +1 -1
  21. package/packages/context-compiler/package.json +1 -1
  22. package/packages/context-discovery/package.json +1 -1
  23. package/packages/context-manager/package.json +1 -1
  24. package/packages/contracts/dist/workspace-env-format.d.ts +24 -0
  25. package/packages/contracts/dist/workspace-env-format.js +41 -1
  26. package/packages/contracts/dist/workspace-env.js +7 -4
  27. package/packages/contracts/package.json +1 -1
  28. package/packages/contracts/src/workspace-env-format.ts +38 -1
  29. package/packages/contracts/src/workspace-env.ts +8 -3
  30. package/packages/contracts/test/workspace-env.test.ts +48 -0
  31. package/packages/cookie-sanitizer/package.json +1 -1
  32. package/packages/cost-optimizer/package.json +1 -1
  33. package/packages/dependency-analyzer/package.json +1 -1
  34. package/packages/django-plugin/package.json +1 -1
  35. package/packages/doc-generator/package.json +1 -1
  36. package/packages/durable-review/package.json +1 -1
  37. package/packages/e2e-tester/package.json +1 -1
  38. package/packages/evaluation-engine/package.json +1 -1
  39. package/packages/evaluation-runner/package.json +1 -1
  40. package/packages/event-bus/package.json +1 -1
  41. package/packages/event-sink/package.json +1 -1
  42. package/packages/event-store/package.json +1 -1
  43. package/packages/experience-replay/package.json +1 -1
  44. package/packages/feedback-collector/package.json +1 -1
  45. package/packages/file-copy-helper/package.json +1 -1
  46. package/packages/framework-detector/package.json +1 -1
  47. package/packages/framework-plugin-sdk/package.json +1 -1
  48. package/packages/frappe-plugin/package.json +1 -1
  49. package/packages/generic-web-plugin/package.json +1 -1
  50. package/packages/health-monitor/package.json +1 -1
  51. package/packages/intent-analyzer/package.json +1 -1
  52. package/packages/jev-judge/package.json +1 -1
  53. package/packages/knowledge-graph/package.json +1 -1
  54. package/packages/knowledge-retrieval/package.json +1 -1
  55. package/packages/laravel-plugin/package.json +1 -1
  56. package/packages/learning-engine/package.json +1 -1
  57. package/packages/logging/README.md +41 -6
  58. package/packages/logging/dist/classification.d.ts +65 -0
  59. package/packages/logging/dist/classification.d.ts.map +1 -0
  60. package/packages/logging/dist/classification.js +155 -0
  61. package/packages/logging/dist/classification.js.map +1 -0
  62. package/packages/logging/dist/config.d.ts +9 -0
  63. package/packages/logging/dist/config.d.ts.map +1 -1
  64. package/packages/logging/dist/config.js +40 -3
  65. package/packages/logging/dist/config.js.map +1 -1
  66. package/packages/logging/dist/index.d.ts +9 -1
  67. package/packages/logging/dist/index.d.ts.map +1 -1
  68. package/packages/logging/dist/index.js +10 -1
  69. package/packages/logging/dist/index.js.map +1 -1
  70. package/packages/logging/dist/ingest/schema.d.ts +60 -0
  71. package/packages/logging/dist/ingest/schema.d.ts.map +1 -0
  72. package/packages/logging/dist/ingest/schema.js +245 -0
  73. package/packages/logging/dist/ingest/schema.js.map +1 -0
  74. package/packages/logging/dist/installation.d.ts +32 -0
  75. package/packages/logging/dist/installation.d.ts.map +1 -0
  76. package/packages/logging/dist/installation.js +64 -0
  77. package/packages/logging/dist/installation.js.map +1 -0
  78. package/packages/logging/dist/sensitive-keys.d.ts +12 -0
  79. package/packages/logging/dist/sensitive-keys.d.ts.map +1 -0
  80. package/packages/logging/dist/sensitive-keys.js +39 -0
  81. package/packages/logging/dist/sensitive-keys.js.map +1 -0
  82. package/packages/logging/dist/server/credentials.d.ts +85 -0
  83. package/packages/logging/dist/server/credentials.d.ts.map +1 -0
  84. package/packages/logging/dist/server/credentials.js +147 -0
  85. package/packages/logging/dist/server/credentials.js.map +1 -0
  86. package/packages/logging/dist/server/http.d.ts +36 -0
  87. package/packages/logging/dist/server/http.d.ts.map +1 -0
  88. package/packages/logging/dist/server/http.js +139 -0
  89. package/packages/logging/dist/server/http.js.map +1 -0
  90. package/packages/logging/dist/server/index-store.d.ts +95 -0
  91. package/packages/logging/dist/server/index-store.d.ts.map +1 -0
  92. package/packages/logging/dist/server/index-store.js +190 -0
  93. package/packages/logging/dist/server/index-store.js.map +1 -0
  94. package/packages/logging/dist/server/index.d.ts +22 -0
  95. package/packages/logging/dist/server/index.d.ts.map +1 -0
  96. package/packages/logging/dist/server/index.js +15 -0
  97. package/packages/logging/dist/server/index.js.map +1 -0
  98. package/packages/logging/dist/server/keys.d.ts +22 -0
  99. package/packages/logging/dist/server/keys.d.ts.map +1 -0
  100. package/packages/logging/dist/server/keys.js +24 -0
  101. package/packages/logging/dist/server/keys.js.map +1 -0
  102. package/packages/logging/dist/server/outbox-worker.d.ts +55 -0
  103. package/packages/logging/dist/server/outbox-worker.d.ts.map +1 -0
  104. package/packages/logging/dist/server/outbox-worker.js +95 -0
  105. package/packages/logging/dist/server/outbox-worker.js.map +1 -0
  106. package/packages/logging/dist/server/outbox.d.ts +112 -0
  107. package/packages/logging/dist/server/outbox.d.ts.map +1 -0
  108. package/packages/logging/dist/server/outbox.js +247 -0
  109. package/packages/logging/dist/server/outbox.js.map +1 -0
  110. package/packages/logging/dist/server/service.d.ts +83 -0
  111. package/packages/logging/dist/server/service.d.ts.map +1 -0
  112. package/packages/logging/dist/server/service.js +144 -0
  113. package/packages/logging/dist/server/service.js.map +1 -0
  114. package/packages/logging/dist/shipper.d.ts +20 -4
  115. package/packages/logging/dist/shipper.d.ts.map +1 -1
  116. package/packages/logging/dist/shipper.js +31 -5
  117. package/packages/logging/dist/shipper.js.map +1 -1
  118. package/packages/logging/dist/singleton.d.ts.map +1 -1
  119. package/packages/logging/dist/singleton.js +3 -0
  120. package/packages/logging/dist/singleton.js.map +1 -1
  121. package/packages/logging/dist/types.d.ts +28 -1
  122. package/packages/logging/dist/types.d.ts.map +1 -1
  123. package/packages/logging/dist/types.js +8 -0
  124. package/packages/logging/dist/types.js.map +1 -1
  125. package/packages/logging/package.json +1 -1
  126. package/packages/logging/src/classification.ts +175 -0
  127. package/packages/logging/src/config.ts +60 -3
  128. package/packages/logging/src/index.ts +45 -1
  129. package/packages/logging/src/ingest/schema.ts +295 -0
  130. package/packages/logging/src/installation.ts +76 -0
  131. package/packages/logging/src/sensitive-keys.ts +40 -0
  132. package/packages/logging/src/server/credentials.ts +198 -0
  133. package/packages/logging/src/server/http.ts +170 -0
  134. package/packages/logging/src/server/index-store.ts +242 -0
  135. package/packages/logging/src/server/index.ts +56 -0
  136. package/packages/logging/src/server/keys.ts +32 -0
  137. package/packages/logging/src/server/outbox-worker.ts +124 -0
  138. package/packages/logging/src/server/outbox.ts +316 -0
  139. package/packages/logging/src/server/service.ts +196 -0
  140. package/packages/logging/src/shipper.ts +54 -8
  141. package/packages/logging/src/singleton.ts +3 -0
  142. package/packages/logging/src/types.ts +29 -1
  143. package/packages/logging/test/classification.test.ts +137 -0
  144. package/packages/logging/test/identity-transport.test.ts +197 -0
  145. package/packages/logging/test/ingest-schema.test.ts +184 -0
  146. package/packages/logging/test/integration.test.ts +9 -4
  147. package/packages/logging/test/log-http.test.ts +246 -0
  148. package/packages/logging/test/server.test.ts +444 -0
  149. package/packages/logging/test/shipper.test.ts +11 -11
  150. package/packages/logging/test/spool.test.ts +4 -1
  151. package/packages/mcp-adapter/package.json +1 -1
  152. package/packages/memory-engine/package.json +1 -1
  153. package/packages/milestone-manager/package.json +1 -1
  154. package/packages/model-registry/package.json +1 -1
  155. package/packages/nextjs-plugin/package.json +1 -1
  156. package/packages/notification/package.json +1 -1
  157. package/packages/observability/package.json +1 -1
  158. package/packages/okf-indexer/package.json +1 -1
  159. package/packages/paperclip-integration/package.json +1 -1
  160. package/packages/performance-optimizer/package.json +1 -1
  161. package/packages/permission-mode/package.json +1 -1
  162. package/packages/privilege-broker/package.json +1 -1
  163. package/packages/project-analyzer/package.json +1 -1
  164. package/packages/project-bootstrap/package.json +1 -1
  165. package/packages/projection-engine/package.json +1 -1
  166. package/packages/prompt-compiler/package.json +1 -1
  167. package/packages/prompt-versioning/package.json +1 -1
  168. package/packages/provider-adapter-sdk/package.json +1 -1
  169. package/packages/provider-router/package.json +1 -1
  170. package/packages/provider-selector/package.json +1 -1
  171. package/packages/providers/package.json +1 -1
  172. package/packages/qdrant-skills/package.json +1 -1
  173. package/packages/quota-manager/package.json +1 -1
  174. package/packages/rate-limiter/package.json +1 -1
  175. package/packages/react-vite-plugin/package.json +1 -1
  176. package/packages/relay/package.json +1 -1
  177. package/packages/release-manager/package.json +1 -1
  178. package/packages/requirement-compiler/package.json +1 -1
  179. package/packages/runtime/package.json +1 -1
  180. package/packages/scheduler/package.json +1 -1
  181. package/packages/scheduler-adapter/package.json +1 -1
  182. package/packages/session/package.json +1 -1
  183. package/packages/session-api/package.json +1 -1
  184. package/packages/session-export/package.json +1 -1
  185. package/packages/shared-context/package.json +1 -1
  186. package/packages/skill-mcp-client/package.json +1 -1
  187. package/packages/skill-registry/package.json +1 -1
  188. package/packages/skills/package.json +1 -1
  189. package/packages/sprint-planner/package.json +1 -1
  190. package/packages/subscription-engine/package.json +1 -1
  191. package/packages/task-compiler/package.json +1 -1
  192. package/packages/telegram/package.json +1 -1
  193. package/packages/tencentdb-memory/package.json +1 -1
  194. package/packages/tencentdb-sync/package.json +1 -1
  195. package/packages/test-data-generator/package.json +1 -1
  196. package/packages/test-generator/package.json +1 -1
  197. package/packages/todo-bd-sync/package.json +1 -1
  198. package/packages/token-estimation/package.json +1 -1
  199. package/packages/token-optimizer/package.json +1 -1
  200. package/packages/trajectory/package.json +1 -1
  201. package/packages/tui/package.json +1 -1
  202. package/packages/types/package.json +1 -1
  203. package/packages/voice-api/package.json +1 -1
  204. package/packages/webmcp-tester/package.json +1 -1
  205. package/packages/wiki/package.json +1 -1
  206. package/packages/workflow-events/package.json +1 -1
  207. package/packages/workspace-scanner/package.json +1 -1
  208. package/packages/worktree/package.json +1 -1
  209. package/packages/write-review/package.json +1 -1
  210. package/scripts/unified-webhook-server.ts +86 -81
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Metadata index for the log query service.
3
+ *
4
+ * R2 is an archive, not a search API, so the server keeps a small metadata
5
+ * index next to it: tenant, machine, session, timestamp, severity,
6
+ * source/event, batch id and the object key of the archived batch.
7
+ *
8
+ * The index deliberately holds NO message text. Prompts, responses and
9
+ * command output never enter it, and an agent asking "which errors happened
10
+ * in this session" gets metadata, not content.
11
+ *
12
+ * Storage is one NDJSON file per tenant and day:
13
+ *
14
+ * <dir>/<tenant>/2026-10-24.ndjson
15
+ *
16
+ * The index is tenant scoped by construction: every query is filtered by the
17
+ * tenant of the authenticated caller, and the tenant of the request body is
18
+ * never used.
19
+ */
20
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
21
+ import { join } from "node:path";
22
+ import { safeSegment } from "./outbox.js";
23
+ function toIsoDay(value) {
24
+ return value.toISOString().slice(0, 10);
25
+ }
26
+ function encodeCursor(value) {
27
+ return Buffer.from(value, "utf8").toString("base64url");
28
+ }
29
+ function decodeCursor(value) {
30
+ try {
31
+ return Buffer.from(value, "base64url").toString("utf8");
32
+ }
33
+ catch {
34
+ return undefined;
35
+ }
36
+ }
37
+ export class LogIndexStore {
38
+ dir;
39
+ maxScanDays;
40
+ defaultLimit;
41
+ maxLimit;
42
+ constructor(options) {
43
+ this.dir = options.dir;
44
+ this.maxScanDays = Math.min(options.maxScanDays ?? 7, 31);
45
+ this.defaultLimit = options.defaultLimit ?? 100;
46
+ this.maxLimit = options.maxLimit ?? 500;
47
+ }
48
+ /**
49
+ * Index the metadata of a batch.
50
+ *
51
+ * Returns the number of entries that were written. The write is best
52
+ * effort: a failing index must not lose the batch itself.
53
+ */
54
+ append(batch, r2Key) {
55
+ if (!Array.isArray(batch.records) || batch.records.length === 0)
56
+ return 0;
57
+ const lines = [];
58
+ for (const record of batch.records) {
59
+ const entry = {
60
+ tenantId: batch.tenantId,
61
+ installationId: batch.installationId,
62
+ ts: record.ts,
63
+ level: record.level,
64
+ source: record.source,
65
+ event: record.event,
66
+ batchId: batch.batchId,
67
+ };
68
+ if (batch.sessionId)
69
+ entry.sessionId = batch.sessionId;
70
+ else if (record.sessionId)
71
+ entry.sessionId = record.sessionId;
72
+ if (r2Key)
73
+ entry.r2Key = r2Key;
74
+ lines.push(JSON.stringify(entry));
75
+ }
76
+ try {
77
+ const day = toIsoDay(new Date(batch.sentAt));
78
+ const dir = join(this.dir, safeSegment(batch.tenantId));
79
+ mkdirSync(dir, { recursive: true });
80
+ appendFileSync(join(dir, `${day}.ndjson`), `${lines.join("\n")}\n`, { mode: 0o600 });
81
+ return lines.length;
82
+ }
83
+ catch {
84
+ return 0;
85
+ }
86
+ }
87
+ /**
88
+ * Query the index for one tenant.
89
+ *
90
+ * The result is bounded by the page limit and by the day budget, and the
91
+ * caller is always a tenant: there is no way to query another tenant
92
+ * because the tenant comes from the parameter, which the server fills
93
+ * from the authenticated credential.
94
+ */
95
+ query(params) {
96
+ const limit = Math.min(params.limit ?? this.defaultLimit, this.maxLimit);
97
+ const days = this.dayRange(params);
98
+ const dir = join(this.dir, safeSegment(params.tenantId));
99
+ const matches = [];
100
+ let scannedFiles = 0;
101
+ let truncated = days.truncated;
102
+ for (const day of days.days) {
103
+ const path = join(dir, `${day}.ndjson`);
104
+ if (!existsSync(path))
105
+ continue;
106
+ scannedFiles++;
107
+ let content;
108
+ try {
109
+ content = readFileSync(path, "utf8");
110
+ }
111
+ catch {
112
+ continue;
113
+ }
114
+ for (const line of content.split("\n")) {
115
+ if (!line.trim())
116
+ continue;
117
+ let entry;
118
+ try {
119
+ entry = JSON.parse(line);
120
+ }
121
+ catch {
122
+ continue;
123
+ }
124
+ if (!this.matches(entry, params))
125
+ continue;
126
+ matches.push(entry);
127
+ }
128
+ }
129
+ matches.sort((a, b) => (a.ts < b.ts ? -1 : a.ts > b.ts ? 1 : 0));
130
+ const cursor = params.cursor ? decodeCursor(params.cursor) : undefined;
131
+ let start = 0;
132
+ if (cursor) {
133
+ const index = matches.findIndex((entry) => `${entry.ts}|${entry.batchId}` === cursor);
134
+ start = index >= 0 ? index + 1 : 0;
135
+ }
136
+ const page = matches.slice(start, start + limit);
137
+ const hasMore = start + limit < matches.length;
138
+ const result = {
139
+ entries: page,
140
+ hasMore,
141
+ scannedFiles,
142
+ truncated,
143
+ };
144
+ if (hasMore && page.length > 0) {
145
+ const last = page[page.length - 1];
146
+ result.nextCursor = encodeCursor(`${last.ts}|${last.batchId}`);
147
+ }
148
+ return result;
149
+ }
150
+ matches(entry, params) {
151
+ if (entry.tenantId !== params.tenantId)
152
+ return false;
153
+ if (params.installationId && entry.installationId !== params.installationId)
154
+ return false;
155
+ if (params.sessionId && entry.sessionId !== params.sessionId)
156
+ return false;
157
+ if (params.levels && params.levels.length > 0 && !params.levels.includes(entry.level)) {
158
+ return false;
159
+ }
160
+ if (params.source && entry.source !== params.source)
161
+ return false;
162
+ if (params.event && entry.event !== params.event)
163
+ return false;
164
+ if (params.since && entry.ts < params.since)
165
+ return false;
166
+ if (params.until && entry.ts >= params.until)
167
+ return false;
168
+ return true;
169
+ }
170
+ dayRange(params) {
171
+ const now = new Date();
172
+ const until = params.until ? new Date(params.until) : now;
173
+ // The budget counts the scanned days, so the default window covers
174
+ // exactly `maxScanDays` days including the day of `until`.
175
+ const since = params.since
176
+ ? new Date(params.since)
177
+ : new Date(until.getTime() - (this.maxScanDays - 1) * 86_400_000);
178
+ const days = [];
179
+ const cursor = new Date(since);
180
+ let truncated = false;
181
+ while (cursor <= until && days.length < this.maxScanDays) {
182
+ days.push(toIsoDay(cursor));
183
+ cursor.setUTCDate(cursor.getUTCDate() + 1);
184
+ }
185
+ if (cursor <= until)
186
+ truncated = true;
187
+ return { days, truncated };
188
+ }
189
+ }
190
+ //# sourceMappingURL=index-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-store.js","sourceRoot":"","sources":["../../src/server/index-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Server side of the logging pipeline.
3
+ *
4
+ * The server is the only component that writes to the archive: clients ship
5
+ * batches to the ingest endpoint, the server derives the tenant from the
6
+ * credential, queues the batch durably and a worker uploads it.
7
+ */
8
+ export { DEFAULT_TENANT_ID, LOG_READ_SCOPE, LOG_WRITE_SCOPE, LogCredentialStore, hashToken, readBearerToken, } from "./credentials.js";
9
+ export type { AuthenticatedCredential, LogCredential, LogCredentialStoreOptions, } from "./credentials.js";
10
+ export { LogOutbox, outboxKey, safeSegment } from "./outbox.js";
11
+ export type { EnqueueResult, LogOutboxOptions, OutboxBatch, OutboxEntry, OutboxStats, } from "./outbox.js";
12
+ export { LogOutboxWorker } from "./outbox-worker.js";
13
+ export type { LogOutboxWorkerOptions, WorkerRunResult } from "./outbox-worker.js";
14
+ export { LogIndexStore } from "./index-store.js";
15
+ export type { LogIndexEntry, LogIndexStoreOptions, LogQueryParams, LogQueryResult, } from "./index-store.js";
16
+ export { serverObjectKey } from "./keys.js";
17
+ export type { ObjectKeyInput } from "./keys.js";
18
+ export { LogIngestService } from "./service.js";
19
+ export type { IngestFailure, IngestResult, IngestSuccess, LogIngestServiceOptions, QuerySuccess, } from "./service.js";
20
+ export { createLogHttpHandler } from "./http.js";
21
+ export type { LogHttpHandler, LogHttpOptions } from "./http.js";
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACN,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,SAAS,EACT,eAAe,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACX,uBAAuB,EACvB,aAAa,EACb,yBAAyB,GACzB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAChE,YAAY,EACX,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,WAAW,GACX,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,YAAY,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAElF,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,YAAY,EACX,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,cAAc,GACd,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EACX,aAAa,EACb,YAAY,EACZ,aAAa,EACb,uBAAuB,EACvB,YAAY,GACZ,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Server side of the logging pipeline.
3
+ *
4
+ * The server is the only component that writes to the archive: clients ship
5
+ * batches to the ingest endpoint, the server derives the tenant from the
6
+ * credential, queues the batch durably and a worker uploads it.
7
+ */
8
+ export { DEFAULT_TENANT_ID, LOG_READ_SCOPE, LOG_WRITE_SCOPE, LogCredentialStore, hashToken, readBearerToken, } from "./credentials.js";
9
+ export { LogOutbox, outboxKey, safeSegment } from "./outbox.js";
10
+ export { LogOutboxWorker } from "./outbox-worker.js";
11
+ export { LogIndexStore } from "./index-store.js";
12
+ export { serverObjectKey } from "./keys.js";
13
+ export { LogIngestService } from "./service.js";
14
+ export { createLogHttpHandler } from "./http.js";
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACN,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,SAAS,EACT,eAAe,GACf,MAAM,kBAAkB,CAAC;AAO1B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAShE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAQjD,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAShD,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Deterministic object keys for the log archive.
3
+ *
4
+ * The server generates the keys - never the client - and they contain no
5
+ * user controlled path segment beyond an already sanitized tenant and batch
6
+ * id. The key is derived from the batch itself, so the same batch always
7
+ * lands under the same key and the metadata index can compute it without
8
+ * waiting for the upload.
9
+ */
10
+ export interface ObjectKeyInput {
11
+ tenantId: string;
12
+ batchId: string;
13
+ /** ISO timestamp used for the date partition. */
14
+ sentAt: string;
15
+ }
16
+ /**
17
+ * Build the archive key of a batch:
18
+ *
19
+ * logs/<tenant>/2026/10/24/09/<batchId>.ndjson
20
+ */
21
+ export declare function serverObjectKey(input: ObjectKeyInput): string;
22
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/server/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAO7D"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Deterministic object keys for the log archive.
3
+ *
4
+ * The server generates the keys - never the client - and they contain no
5
+ * user controlled path segment beyond an already sanitized tenant and batch
6
+ * id. The key is derived from the batch itself, so the same batch always
7
+ * lands under the same key and the metadata index can compute it without
8
+ * waiting for the upload.
9
+ */
10
+ import { safeSegment } from "./outbox.js";
11
+ /**
12
+ * Build the archive key of a batch:
13
+ *
14
+ * logs/<tenant>/2026/10/24/09/<batchId>.ndjson
15
+ */
16
+ export function serverObjectKey(input) {
17
+ const date = new Date(input.sentAt);
18
+ const year = date.getUTCFullYear();
19
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
20
+ const day = String(date.getUTCDate()).padStart(2, "0");
21
+ const hour = String(date.getUTCHours()).padStart(2, "0");
22
+ return `logs/${safeSegment(input.tenantId)}/${year}/${month}/${day}/${hour}/${safeSegment(input.batchId)}.ndjson`;
23
+ }
24
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/server/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAS1C;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,KAAqB;IACpD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9D,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACzD,OAAO,QAAQ,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,IAAI,IAAI,KAAK,IAAI,GAAG,IAAI,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;AACnH,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Retry worker for the server-side outbox.
3
+ *
4
+ * The worker drains the queue in the background, so an R2 outage never
5
+ * blocks the ingest endpoint: the batch waits in the queue and is retried
6
+ * with a bounded exponential backoff.
7
+ *
8
+ * A batch that exhausts its attempts stays in the queue (visible in the
9
+ * diagnostics) instead of being dropped silently.
10
+ */
11
+ import type { LogOutbox, OutboxEntry } from "./outbox.js";
12
+ export interface LogOutboxWorkerOptions {
13
+ outbox: LogOutbox;
14
+ /** Uploads one batch. Throwing marks the batch as failed. */
15
+ upload: (entry: OutboxEntry) => Promise<void>;
16
+ /** Interval between two drain runs. Default 5000 ms. */
17
+ intervalMs?: number;
18
+ /** Attempts before a batch is parked. Default 10. */
19
+ maxAttempts?: number;
20
+ /** First backoff step. Default 5000 ms. */
21
+ baseDelayMs?: number;
22
+ /** Upper bound of the backoff. Default 300000 ms (5 minutes). */
23
+ maxDelayMs?: number;
24
+ /** Batches per run. Default 50. */
25
+ batchLimit?: number;
26
+ /** Clock injection (tests). */
27
+ now?: () => number;
28
+ }
29
+ export interface WorkerRunResult {
30
+ uploaded: number;
31
+ failed: number;
32
+ skipped: number;
33
+ }
34
+ export declare class LogOutboxWorker {
35
+ private readonly outbox;
36
+ private readonly upload;
37
+ private readonly intervalMs;
38
+ private readonly maxAttempts;
39
+ private readonly baseDelayMs;
40
+ private readonly maxDelayMs;
41
+ private readonly batchLimit;
42
+ private readonly now;
43
+ private timer;
44
+ private running;
45
+ constructor(options: LogOutboxWorkerOptions);
46
+ /** Backoff for the given attempt count (1 = first failure). */
47
+ backoffMs(attempts: number): number;
48
+ /** Start the periodic drain. */
49
+ start(): void;
50
+ /** Stop the periodic drain and wait for a running pass. */
51
+ stop(): Promise<void>;
52
+ /** Drain the queue once. Never throws. */
53
+ runOnce(): Promise<WorkerRunResult>;
54
+ }
55
+ //# sourceMappingURL=outbox-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox-worker.d.ts","sourceRoot":"","sources":["../../src/server/outbox-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,WAAW,sBAAsB;IACtC,MAAM,EAAE,SAAS,CAAC;IAClB,6DAA6D;IAC7D,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,eAAe;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwC;IAC/D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,KAAK,CAA6C;IAC1D,OAAO,CAAC,OAAO,CAAS;IAExB,YAAY,OAAO,EAAE,sBAAsB,EAS1C;IAED,+DAA+D;IAC/D,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGlC;IAED,gCAAgC;IAChC,KAAK,IAAI,IAAI,CAMZ;IAED,2DAA2D;IACrD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ1B;IAED,0CAA0C;IACpC,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC,CAmCxC;CACD"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Retry worker for the server-side outbox.
3
+ *
4
+ * The worker drains the queue in the background, so an R2 outage never
5
+ * blocks the ingest endpoint: the batch waits in the queue and is retried
6
+ * with a bounded exponential backoff.
7
+ *
8
+ * A batch that exhausts its attempts stays in the queue (visible in the
9
+ * diagnostics) instead of being dropped silently.
10
+ */
11
+ export class LogOutboxWorker {
12
+ outbox;
13
+ upload;
14
+ intervalMs;
15
+ maxAttempts;
16
+ baseDelayMs;
17
+ maxDelayMs;
18
+ batchLimit;
19
+ now;
20
+ timer;
21
+ running = false;
22
+ constructor(options) {
23
+ this.outbox = options.outbox;
24
+ this.upload = options.upload;
25
+ this.intervalMs = options.intervalMs ?? 5_000;
26
+ this.maxAttempts = options.maxAttempts ?? 10;
27
+ this.baseDelayMs = options.baseDelayMs ?? 5_000;
28
+ this.maxDelayMs = options.maxDelayMs ?? 300_000;
29
+ this.batchLimit = options.batchLimit ?? 50;
30
+ this.now = options.now ?? (() => Date.now());
31
+ }
32
+ /** Backoff for the given attempt count (1 = first failure). */
33
+ backoffMs(attempts) {
34
+ const factor = 2 ** Math.max(0, attempts - 1);
35
+ return Math.min(this.baseDelayMs * factor, this.maxDelayMs);
36
+ }
37
+ /** Start the periodic drain. */
38
+ start() {
39
+ if (this.timer)
40
+ return;
41
+ this.timer = setInterval(() => {
42
+ void this.runOnce();
43
+ }, this.intervalMs);
44
+ this.timer.unref?.();
45
+ }
46
+ /** Stop the periodic drain and wait for a running pass. */
47
+ async stop() {
48
+ if (this.timer) {
49
+ clearInterval(this.timer);
50
+ this.timer = undefined;
51
+ }
52
+ while (this.running) {
53
+ await new Promise((resolve) => setTimeout(resolve, 10));
54
+ }
55
+ }
56
+ /** Drain the queue once. Never throws. */
57
+ async runOnce() {
58
+ if (this.running)
59
+ return { uploaded: 0, failed: 0, skipped: 0 };
60
+ this.running = true;
61
+ const result = { uploaded: 0, failed: 0, skipped: 0 };
62
+ try {
63
+ const now = this.now();
64
+ for (const entry of this.outbox.listPending(this.batchLimit)) {
65
+ if (entry.nextAttemptAt > now) {
66
+ result.skipped++;
67
+ continue;
68
+ }
69
+ if (entry.attempts >= this.maxAttempts) {
70
+ result.skipped++;
71
+ continue;
72
+ }
73
+ try {
74
+ await this.upload(entry);
75
+ this.outbox.markUploaded(entry);
76
+ result.uploaded++;
77
+ }
78
+ catch (error) {
79
+ const message = error instanceof Error ? error.message : String(error);
80
+ const attempts = entry.attempts + 1;
81
+ this.outbox.markFailed(entry, message, this.now() + this.backoffMs(attempts));
82
+ result.failed++;
83
+ }
84
+ }
85
+ }
86
+ catch {
87
+ // A failing drain must not kill the worker.
88
+ }
89
+ finally {
90
+ this.running = false;
91
+ }
92
+ return result;
93
+ }
94
+ }
95
+ //# sourceMappingURL=outbox-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox-worker.js","sourceRoot":"","sources":["../../src/server/outbox-worker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Durable server-side outbox.
3
+ *
4
+ * The ingest endpoint must not tell a client "accepted" before the batch
5
+ * survived a crash. The outbox is the durable hand-off point between the
6
+ * HTTP request and the R2 upload:
7
+ *
8
+ * request -> validate -> enqueue (durable) -> 202
9
+ * |
10
+ * v
11
+ * worker -> upload -> done
12
+ *
13
+ * The queue is a directory of JSON files, one per accepted batch:
14
+ *
15
+ * <dir>/pending/<key>.json the batch payload
16
+ * <dir>/pending/<key>.meta.json attempts, next attempt, last error
17
+ * <dir>/done/<key>.json uploaded batches (pruned after a while)
18
+ *
19
+ * A plain file queue is used on purpose: the server runs under Bun, which
20
+ * has no `node:sqlite`, and the queue stays inspectable with `ls`/`jq`.
21
+ *
22
+ * The key is derived from (tenant, installation, batch), so the same batch
23
+ * is enqueued only once - the ingest endpoint is idempotent.
24
+ */
25
+ import type { LogRecord } from "../types.js";
26
+ export interface OutboxBatch {
27
+ tenantId: string;
28
+ installationId: string;
29
+ batchId: string;
30
+ app: string;
31
+ appVersion?: string;
32
+ hostLabel?: string;
33
+ sessionId?: string;
34
+ sentAt: string;
35
+ records: LogRecord[];
36
+ }
37
+ export interface OutboxEntry {
38
+ /** File name key of the batch. */
39
+ key: string;
40
+ /** Absolute path of the payload file. */
41
+ path: string;
42
+ batch: OutboxBatch;
43
+ attempts: number;
44
+ nextAttemptAt: number;
45
+ lastError?: string;
46
+ createdAt: number;
47
+ }
48
+ export interface OutboxStats {
49
+ /** Batches waiting for an upload. */
50
+ pending: number;
51
+ /** Age of the oldest pending batch in milliseconds. */
52
+ oldestPendingAgeMs: number;
53
+ /** Batches that failed at least once. */
54
+ failures: number;
55
+ /** Last upload error (message only, never a payload). */
56
+ lastError?: string;
57
+ }
58
+ export interface EnqueueResult {
59
+ accepted: boolean;
60
+ /** True when the same batch was already queued (idempotent replay). */
61
+ duplicate: boolean;
62
+ }
63
+ export interface LogOutboxOptions {
64
+ /** Directory of the queue. */
65
+ dir: string;
66
+ /** How long uploaded batches are kept, in milliseconds. Default 7 days. */
67
+ retentionMs?: number;
68
+ }
69
+ /** Make a value safe to use inside a file name. */
70
+ export declare function safeSegment(value: string, maxLength?: number): string;
71
+ /**
72
+ * Build the queue key of a batch.
73
+ *
74
+ * The key is unique per (tenant, installation, batch) and contains no
75
+ * user controlled path segment.
76
+ */
77
+ export declare function outboxKey(batch: {
78
+ tenantId: string;
79
+ installationId: string;
80
+ batchId: string;
81
+ }): string;
82
+ export declare class LogOutbox {
83
+ private readonly dir;
84
+ private readonly pendingDir;
85
+ private readonly doneDir;
86
+ private readonly retentionMs;
87
+ constructor(options: LogOutboxOptions);
88
+ /** Directory of the queue. */
89
+ get directory(): string;
90
+ /**
91
+ * Store a batch durably.
92
+ *
93
+ * The write is atomic (write to a temp file, then rename), so a crash
94
+ * can never leave a half written batch behind.
95
+ */
96
+ enqueue(batch: OutboxBatch): EnqueueResult;
97
+ /** The batches waiting for an upload, oldest first. */
98
+ listPending(limit?: number): OutboxEntry[];
99
+ /** Mark a batch as uploaded and move it out of the pending directory. */
100
+ markUploaded(entry: OutboxEntry): void;
101
+ /**
102
+ * Record a failed upload and schedule the next attempt.
103
+ */
104
+ markFailed(entry: OutboxEntry, error: string, nextAttemptAt: number): void;
105
+ /** Queue depth, oldest pending age and failure information. */
106
+ stats(now?: number): OutboxStats;
107
+ /** Delete uploaded batches that are older than the retention period. */
108
+ prune(now?: number): number;
109
+ private writeMeta;
110
+ private readEntry;
111
+ }
112
+ //# sourceMappingURL=outbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox.d.ts","sourceRoot":"","sources":["../../src/server/outbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAcH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC3B,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC3B,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,uEAAuE;IACvE,SAAS,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAChC,8BAA8B;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,mDAAmD;AACnD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,SAAK,GAAG,MAAM,CAOjE;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CAMT;AAED,qBAAa,SAAS;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,YAAY,OAAO,EAAE,gBAAgB,EAWpC;IAED,8BAA8B;IAC9B,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;;;;OAKG;IACH,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CA4BzC;IAED,uDAAuD;IACvD,WAAW,CAAC,KAAK,SAAM,GAAG,WAAW,EAAE,CAiBtC;IAED,yEAAyE;IACzE,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAWrC;IAED;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAOzE;IAED,+DAA+D;IAC/D,KAAK,CAAC,GAAG,SAAa,GAAG,WAAW,CAcnC;IAED,wEAAwE;IACxE,KAAK,CAAC,GAAG,SAAa,GAAG,MAAM,CAqB9B;IAED,OAAO,CAAC,SAAS;IAejB,OAAO,CAAC,SAAS;CAsCjB"}