pi-harness-runtime 1.1.396 → 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.
- package/AGENTS.md +54 -7
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/logging/README.md +41 -6
- package/packages/logging/dist/classification.d.ts +65 -0
- package/packages/logging/dist/classification.d.ts.map +1 -0
- package/packages/logging/dist/classification.js +155 -0
- package/packages/logging/dist/classification.js.map +1 -0
- package/packages/logging/dist/config.d.ts +9 -0
- package/packages/logging/dist/config.d.ts.map +1 -1
- package/packages/logging/dist/config.js +40 -3
- package/packages/logging/dist/config.js.map +1 -1
- package/packages/logging/dist/index.d.ts +9 -1
- package/packages/logging/dist/index.d.ts.map +1 -1
- package/packages/logging/dist/index.js +10 -1
- package/packages/logging/dist/index.js.map +1 -1
- package/packages/logging/dist/ingest/schema.d.ts +60 -0
- package/packages/logging/dist/ingest/schema.d.ts.map +1 -0
- package/packages/logging/dist/ingest/schema.js +245 -0
- package/packages/logging/dist/ingest/schema.js.map +1 -0
- package/packages/logging/dist/installation.d.ts +32 -0
- package/packages/logging/dist/installation.d.ts.map +1 -0
- package/packages/logging/dist/installation.js +64 -0
- package/packages/logging/dist/installation.js.map +1 -0
- package/packages/logging/dist/sensitive-keys.d.ts +12 -0
- package/packages/logging/dist/sensitive-keys.d.ts.map +1 -0
- package/packages/logging/dist/sensitive-keys.js +39 -0
- package/packages/logging/dist/sensitive-keys.js.map +1 -0
- package/packages/logging/dist/server/credentials.d.ts +85 -0
- package/packages/logging/dist/server/credentials.d.ts.map +1 -0
- package/packages/logging/dist/server/credentials.js +147 -0
- package/packages/logging/dist/server/credentials.js.map +1 -0
- package/packages/logging/dist/server/http.d.ts +36 -0
- package/packages/logging/dist/server/http.d.ts.map +1 -0
- package/packages/logging/dist/server/http.js +139 -0
- package/packages/logging/dist/server/http.js.map +1 -0
- package/packages/logging/dist/server/index-store.d.ts +95 -0
- package/packages/logging/dist/server/index-store.d.ts.map +1 -0
- package/packages/logging/dist/server/index-store.js +190 -0
- package/packages/logging/dist/server/index-store.js.map +1 -0
- package/packages/logging/dist/server/index.d.ts +22 -0
- package/packages/logging/dist/server/index.d.ts.map +1 -0
- package/packages/logging/dist/server/index.js +15 -0
- package/packages/logging/dist/server/index.js.map +1 -0
- package/packages/logging/dist/server/keys.d.ts +22 -0
- package/packages/logging/dist/server/keys.d.ts.map +1 -0
- package/packages/logging/dist/server/keys.js +24 -0
- package/packages/logging/dist/server/keys.js.map +1 -0
- package/packages/logging/dist/server/outbox-worker.d.ts +55 -0
- package/packages/logging/dist/server/outbox-worker.d.ts.map +1 -0
- package/packages/logging/dist/server/outbox-worker.js +95 -0
- package/packages/logging/dist/server/outbox-worker.js.map +1 -0
- package/packages/logging/dist/server/outbox.d.ts +112 -0
- package/packages/logging/dist/server/outbox.d.ts.map +1 -0
- package/packages/logging/dist/server/outbox.js +247 -0
- package/packages/logging/dist/server/outbox.js.map +1 -0
- package/packages/logging/dist/server/service.d.ts +83 -0
- package/packages/logging/dist/server/service.d.ts.map +1 -0
- package/packages/logging/dist/server/service.js +144 -0
- package/packages/logging/dist/server/service.js.map +1 -0
- package/packages/logging/dist/shipper.d.ts +20 -4
- package/packages/logging/dist/shipper.d.ts.map +1 -1
- package/packages/logging/dist/shipper.js +31 -5
- package/packages/logging/dist/shipper.js.map +1 -1
- package/packages/logging/dist/singleton.d.ts.map +1 -1
- package/packages/logging/dist/singleton.js +3 -0
- package/packages/logging/dist/singleton.js.map +1 -1
- package/packages/logging/dist/types.d.ts +28 -1
- package/packages/logging/dist/types.d.ts.map +1 -1
- package/packages/logging/dist/types.js +8 -0
- package/packages/logging/dist/types.js.map +1 -1
- package/packages/logging/package.json +1 -1
- package/packages/logging/src/classification.ts +175 -0
- package/packages/logging/src/config.ts +60 -3
- package/packages/logging/src/index.ts +45 -1
- package/packages/logging/src/ingest/schema.ts +295 -0
- package/packages/logging/src/installation.ts +76 -0
- package/packages/logging/src/sensitive-keys.ts +40 -0
- package/packages/logging/src/server/credentials.ts +198 -0
- package/packages/logging/src/server/http.ts +170 -0
- package/packages/logging/src/server/index-store.ts +242 -0
- package/packages/logging/src/server/index.ts +56 -0
- package/packages/logging/src/server/keys.ts +32 -0
- package/packages/logging/src/server/outbox-worker.ts +124 -0
- package/packages/logging/src/server/outbox.ts +316 -0
- package/packages/logging/src/server/service.ts +196 -0
- package/packages/logging/src/shipper.ts +54 -8
- package/packages/logging/src/singleton.ts +3 -0
- package/packages/logging/src/types.ts +29 -1
- package/packages/logging/test/classification.test.ts +137 -0
- package/packages/logging/test/identity-transport.test.ts +197 -0
- package/packages/logging/test/ingest-schema.test.ts +184 -0
- package/packages/logging/test/integration.test.ts +9 -4
- package/packages/logging/test/log-http.test.ts +246 -0
- package/packages/logging/test/server.test.ts +444 -0
- package/packages/logging/test/shipper.test.ts +11 -11
- package/packages/logging/test/spool.test.ts +4 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/unified-webhook-server.ts +86 -81
|
@@ -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"}
|
|
@@ -0,0 +1,247 @@
|
|
|
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 { createHash } from "node:crypto";
|
|
26
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
/** Make a value safe to use inside a file name. */
|
|
29
|
+
export function safeSegment(value, maxLength = 48) {
|
|
30
|
+
const cleaned = value
|
|
31
|
+
.replace(/\.\./g, "_")
|
|
32
|
+
.replace(/[^A-Za-z0-9._-]/g, "_")
|
|
33
|
+
.slice(0, maxLength)
|
|
34
|
+
.replace(/^\.+/, "_");
|
|
35
|
+
return cleaned.length > 0 ? cleaned : "unknown";
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build the queue key of a batch.
|
|
39
|
+
*
|
|
40
|
+
* The key is unique per (tenant, installation, batch) and contains no
|
|
41
|
+
* user controlled path segment.
|
|
42
|
+
*/
|
|
43
|
+
export function outboxKey(batch) {
|
|
44
|
+
const digest = createHash("sha256")
|
|
45
|
+
.update(`${batch.tenantId}|${batch.installationId}|${batch.batchId}`, "utf8")
|
|
46
|
+
.digest("hex")
|
|
47
|
+
.slice(0, 12);
|
|
48
|
+
return `${safeSegment(batch.tenantId)}--${safeSegment(batch.batchId)}--${digest}`;
|
|
49
|
+
}
|
|
50
|
+
export class LogOutbox {
|
|
51
|
+
dir;
|
|
52
|
+
pendingDir;
|
|
53
|
+
doneDir;
|
|
54
|
+
retentionMs;
|
|
55
|
+
constructor(options) {
|
|
56
|
+
this.dir = options.dir;
|
|
57
|
+
this.pendingDir = join(options.dir, "pending");
|
|
58
|
+
this.doneDir = join(options.dir, "done");
|
|
59
|
+
this.retentionMs = options.retentionMs ?? 7 * 24 * 60 * 60 * 1000;
|
|
60
|
+
try {
|
|
61
|
+
mkdirSync(this.pendingDir, { recursive: true });
|
|
62
|
+
mkdirSync(this.doneDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// The enqueue call reports the failure with a retryable error.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Directory of the queue. */
|
|
69
|
+
get directory() {
|
|
70
|
+
return this.dir;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Store a batch durably.
|
|
74
|
+
*
|
|
75
|
+
* The write is atomic (write to a temp file, then rename), so a crash
|
|
76
|
+
* can never leave a half written batch behind.
|
|
77
|
+
*/
|
|
78
|
+
enqueue(batch) {
|
|
79
|
+
const key = outboxKey(batch);
|
|
80
|
+
const payloadPath = join(this.pendingDir, `${key}.json`);
|
|
81
|
+
if (existsSync(payloadPath) || existsSync(join(this.doneDir, `${key}.json`))) {
|
|
82
|
+
return { accepted: true, duplicate: true };
|
|
83
|
+
}
|
|
84
|
+
const tempPath = `${payloadPath}.tmp`;
|
|
85
|
+
try {
|
|
86
|
+
writeFileSync(tempPath, `${JSON.stringify(batch)}\n`, { mode: 0o600 });
|
|
87
|
+
renameSync(tempPath, payloadPath);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
try {
|
|
91
|
+
rmSync(tempPath, { force: true });
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Nothing else to do.
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
this.writeMeta(key, {
|
|
99
|
+
attempts: 0,
|
|
100
|
+
nextAttemptAt: Date.now(),
|
|
101
|
+
createdAt: Date.now(),
|
|
102
|
+
});
|
|
103
|
+
return { accepted: true, duplicate: false };
|
|
104
|
+
}
|
|
105
|
+
/** The batches waiting for an upload, oldest first. */
|
|
106
|
+
listPending(limit = 100) {
|
|
107
|
+
const entries = [];
|
|
108
|
+
let names;
|
|
109
|
+
try {
|
|
110
|
+
names = readdirSync(this.pendingDir);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return entries;
|
|
114
|
+
}
|
|
115
|
+
for (const name of names) {
|
|
116
|
+
if (!name.endsWith(".json") || name.endsWith(".meta.json"))
|
|
117
|
+
continue;
|
|
118
|
+
const entry = this.readEntry(join(this.pendingDir, name));
|
|
119
|
+
if (entry)
|
|
120
|
+
entries.push(entry);
|
|
121
|
+
}
|
|
122
|
+
entries.sort((a, b) => a.createdAt - b.createdAt);
|
|
123
|
+
return entries.slice(0, limit);
|
|
124
|
+
}
|
|
125
|
+
/** Mark a batch as uploaded and move it out of the pending directory. */
|
|
126
|
+
markUploaded(entry) {
|
|
127
|
+
try {
|
|
128
|
+
renameSync(entry.path, join(this.doneDir, `${entry.key}.json`));
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// The file may have been moved already; nothing to do.
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
rmSync(join(this.pendingDir, `${entry.key}.meta.json`), { force: true });
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// Ignore.
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Record a failed upload and schedule the next attempt.
|
|
142
|
+
*/
|
|
143
|
+
markFailed(entry, error, nextAttemptAt) {
|
|
144
|
+
this.writeMeta(entry.key, {
|
|
145
|
+
attempts: entry.attempts + 1,
|
|
146
|
+
nextAttemptAt,
|
|
147
|
+
lastError: error,
|
|
148
|
+
createdAt: entry.createdAt,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/** Queue depth, oldest pending age and failure information. */
|
|
152
|
+
stats(now = Date.now()) {
|
|
153
|
+
const entries = this.listPending(Number.MAX_SAFE_INTEGER);
|
|
154
|
+
let oldestPendingAgeMs = 0;
|
|
155
|
+
let failures = 0;
|
|
156
|
+
let lastError;
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
const age = now - entry.createdAt;
|
|
159
|
+
if (oldestPendingAgeMs === 0 || age > oldestPendingAgeMs)
|
|
160
|
+
oldestPendingAgeMs = age;
|
|
161
|
+
if (entry.attempts > 0)
|
|
162
|
+
failures++;
|
|
163
|
+
if (entry.lastError)
|
|
164
|
+
lastError = entry.lastError;
|
|
165
|
+
}
|
|
166
|
+
return { pending: entries.length, oldestPendingAgeMs, failures, lastError };
|
|
167
|
+
}
|
|
168
|
+
/** Delete uploaded batches that are older than the retention period. */
|
|
169
|
+
prune(now = Date.now()) {
|
|
170
|
+
let removed = 0;
|
|
171
|
+
let names;
|
|
172
|
+
try {
|
|
173
|
+
names = readdirSync(this.doneDir);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
for (const name of names) {
|
|
179
|
+
const path = join(this.doneDir, name);
|
|
180
|
+
try {
|
|
181
|
+
const age = now - statSync(path).mtimeMs;
|
|
182
|
+
if (age > this.retentionMs) {
|
|
183
|
+
rmSync(path, { force: true });
|
|
184
|
+
removed++;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Ignore a file that vanished.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return removed;
|
|
192
|
+
}
|
|
193
|
+
writeMeta(key, meta) {
|
|
194
|
+
const path = join(this.pendingDir, `${key}.meta.json`);
|
|
195
|
+
const tempPath = `${path}.tmp`;
|
|
196
|
+
try {
|
|
197
|
+
writeFileSync(tempPath, `${JSON.stringify(meta)}\n`, { mode: 0o600 });
|
|
198
|
+
renameSync(tempPath, path);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// The meta file is an optimisation: without it the batch is still
|
|
202
|
+
// retried, only without the backoff hint.
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
readEntry(path) {
|
|
206
|
+
const key = path.slice(path.lastIndexOf("/") + 1).replace(/\.json$/, "");
|
|
207
|
+
try {
|
|
208
|
+
const batch = JSON.parse(readFileSync(path, "utf8"));
|
|
209
|
+
let meta = {
|
|
210
|
+
attempts: 0,
|
|
211
|
+
nextAttemptAt: 0,
|
|
212
|
+
createdAt: 0,
|
|
213
|
+
};
|
|
214
|
+
try {
|
|
215
|
+
const metaPath = join(this.pendingDir, `${key}.meta.json`);
|
|
216
|
+
if (existsSync(metaPath)) {
|
|
217
|
+
meta = { ...meta, ...JSON.parse(readFileSync(metaPath, "utf8")) };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Fall back to the defaults.
|
|
222
|
+
}
|
|
223
|
+
let createdAt = meta.createdAt;
|
|
224
|
+
if (!createdAt) {
|
|
225
|
+
try {
|
|
226
|
+
createdAt = statSync(path).mtimeMs;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
createdAt = Date.now();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
key,
|
|
234
|
+
path,
|
|
235
|
+
batch,
|
|
236
|
+
attempts: meta.attempts ?? 0,
|
|
237
|
+
nextAttemptAt: meta.nextAttemptAt ?? 0,
|
|
238
|
+
lastError: meta.lastError,
|
|
239
|
+
createdAt,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=outbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../src/server/outbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The log ingest service.
|
|
3
|
+
*
|
|
4
|
+
* The HTTP handler is a thin adapter around this class, so the whole ingest
|
|
5
|
+
* and query policy is testable without a socket:
|
|
6
|
+
*
|
|
7
|
+
* - authentication happens BEFORE the body is parsed,
|
|
8
|
+
* - the tenant comes from the credential, never from the request body,
|
|
9
|
+
* - a batch is only acknowledged after it is durably queued,
|
|
10
|
+
* - a query is always scoped to the tenant of the caller.
|
|
11
|
+
*/
|
|
12
|
+
import { type BatchValidationLimits } from "../ingest/schema.js";
|
|
13
|
+
import { type AuthenticatedCredential, type LogCredentialStore } from "./credentials.js";
|
|
14
|
+
import type { LogIndexStore, LogQueryParams, LogQueryResult } from "./index-store.js";
|
|
15
|
+
import type { LogOutbox } from "./outbox.js";
|
|
16
|
+
export interface IngestSuccess {
|
|
17
|
+
accepted: number;
|
|
18
|
+
batchId: string;
|
|
19
|
+
duplicate: boolean;
|
|
20
|
+
tenantId: string;
|
|
21
|
+
queued: true;
|
|
22
|
+
}
|
|
23
|
+
export interface IngestFailure {
|
|
24
|
+
error: string;
|
|
25
|
+
code: string;
|
|
26
|
+
}
|
|
27
|
+
export interface IngestResult {
|
|
28
|
+
status: number;
|
|
29
|
+
body: IngestSuccess | IngestFailure;
|
|
30
|
+
}
|
|
31
|
+
export interface QuerySuccess {
|
|
32
|
+
entries: LogQueryResult["entries"];
|
|
33
|
+
nextCursor?: string;
|
|
34
|
+
hasMore: boolean;
|
|
35
|
+
scannedFiles: number;
|
|
36
|
+
truncated: boolean;
|
|
37
|
+
tenantId: string;
|
|
38
|
+
}
|
|
39
|
+
export interface LogIngestServiceOptions {
|
|
40
|
+
credentials: LogCredentialStore;
|
|
41
|
+
outbox: LogOutbox;
|
|
42
|
+
index: LogIndexStore;
|
|
43
|
+
/** Validation limits for an inbound batch. */
|
|
44
|
+
limits?: Partial<BatchValidationLimits>;
|
|
45
|
+
}
|
|
46
|
+
export declare class LogIngestService {
|
|
47
|
+
private readonly credentials;
|
|
48
|
+
private readonly outbox;
|
|
49
|
+
private readonly index;
|
|
50
|
+
private readonly limits;
|
|
51
|
+
constructor(options: LogIngestServiceOptions);
|
|
52
|
+
/** True when the ingest endpoint has at least one usable credential. */
|
|
53
|
+
get configured(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Authenticate a presented bearer token.
|
|
56
|
+
*
|
|
57
|
+
* The HTTP layer uses it to reject a request before the body is read.
|
|
58
|
+
*/
|
|
59
|
+
authenticate(token: string | undefined): AuthenticatedCredential | null;
|
|
60
|
+
/**
|
|
61
|
+
* Accept a batch.
|
|
62
|
+
*
|
|
63
|
+
* The status codes matter:
|
|
64
|
+
* 503 - no credential is configured (fail closed) or the queue is
|
|
65
|
+
* unavailable. The client keeps the batch in its spool.
|
|
66
|
+
* 401 - the credential is missing or unknown.
|
|
67
|
+
* 403 - the credential may not write.
|
|
68
|
+
* 400 - the batch does not match the schema.
|
|
69
|
+
* 202 - the batch is durably queued.
|
|
70
|
+
*/
|
|
71
|
+
ingest(body: unknown, bearerToken: string | undefined): IngestResult;
|
|
72
|
+
/**
|
|
73
|
+
* Query the metadata index for the tenant of the caller.
|
|
74
|
+
*
|
|
75
|
+
* The `tenantId` of the request is ignored on purpose - only the
|
|
76
|
+
* credential decides whose records are visible.
|
|
77
|
+
*/
|
|
78
|
+
query(params: Omit<LogQueryParams, "tenantId">, bearerToken: string | undefined): {
|
|
79
|
+
status: number;
|
|
80
|
+
body: QuerySuccess | IngestFailure;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/server/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAiB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EAGN,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,aAAa,CAAC;AAE1D,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,aAAa,GAAG,aAAa,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACvC,WAAW,EAAE,kBAAkB,CAAC;IAChC,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,aAAa,CAAC;IACrB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACxC;AAED,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IAExD,YAAY,OAAO,EAAE,uBAAuB,EAK3C;IAED,wEAAwE;IACxE,IAAI,UAAU,IAAI,OAAO,CAExB;IAED;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,uBAAuB,GAAG,IAAI,CAEtE;IAED;;;;;;;;;;OAUG;IACH,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,CA6DnE;IAED;;;;;OAKG;IACH,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG;QACjF,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,YAAY,GAAG,aAAa,CAAC;KACnC,CA0BA;CACD"}
|