@byok-sdk/client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/dist/adapters/claude/claude-adapter.d.ts +155 -0
- package/dist/adapters/claude/events.d.ts +91 -0
- package/dist/adapters/claude/permission-mapping.d.ts +142 -0
- package/dist/adapters/claude/process-client.d.ts +103 -0
- package/dist/adapters/claude/resolve-approval-mcp-bin.d.ts +45 -0
- package/dist/adapters/claude/resolve-bin.d.ts +27 -0
- package/dist/adapters/codex/codex-adapter.d.ts +97 -0
- package/dist/adapters/codex/events.d.ts +41 -0
- package/dist/adapters/codex/permission-mapping.d.ts +107 -0
- package/dist/adapters/codex/process-runner.d.ts +86 -0
- package/dist/adapters/codex/resolve-bin.d.ts +25 -0
- package/dist/adapters/pi/events.d.ts +58 -0
- package/dist/adapters/pi/permission-mapping.d.ts +46 -0
- package/dist/adapters/pi/pi-adapter.d.ts +29 -0
- package/dist/adapters/pi/resolve-bin.d.ts +44 -0
- package/dist/adapters/pi/rpc-client.d.ts +101 -0
- package/dist/bin/approval-mcp-server.d.ts +69 -0
- package/dist/bin/audit-log.d.ts +212 -0
- package/dist/bin/byok-agent.d.ts +2 -0
- package/dist/bin/byok-agent.js +12347 -0
- package/dist/bin/byok-agent.js.map +1 -0
- package/dist/bin/byok-approval-mcp.d.ts +2 -0
- package/dist/bin/byok-approval-mcp.js +498 -0
- package/dist/bin/byok-approval-mcp.js.map +1 -0
- package/dist/bin/commands/approvals.d.ts +31 -0
- package/dist/bin/commands/approve-reject.d.ts +29 -0
- package/dist/bin/commands/doctor.d.ts +15 -0
- package/dist/bin/commands/pair.d.ts +7 -0
- package/dist/bin/commands/runtimes.d.ts +7 -0
- package/dist/bin/commands/service.d.ts +54 -0
- package/dist/bin/commands/start.d.ts +21 -0
- package/dist/bin/commands/status.d.ts +25 -0
- package/dist/bin/commands/support-bundle.d.ts +7 -0
- package/dist/bin/commands/tasks.d.ts +27 -0
- package/dist/bin/commands/unpair.d.ts +124 -0
- package/dist/bin/commands/workspaces.d.ts +16 -0
- package/dist/bin/config.d.ts +41 -0
- package/dist/bin/control-client.d.ts +50 -0
- package/dist/bin/format.d.ts +67 -0
- package/dist/bin/runtime-probe.d.ts +34 -0
- package/dist/bin/tasks-view.d.ts +49 -0
- package/dist/daemon/approvals.d.ts +87 -0
- package/dist/daemon/auth-manager.d.ts +52 -0
- package/dist/daemon/blob-client.d.ts +22 -0
- package/dist/daemon/connection-manager.d.ts +502 -0
- package/dist/daemon/control-protocol.d.ts +299 -0
- package/dist/daemon/control-server.d.ts +61 -0
- package/dist/daemon/create-daemon.d.ts +364 -0
- package/dist/daemon/cursor-store.d.ts +30 -0
- package/dist/daemon/daemon-owner.d.ts +24 -0
- package/dist/daemon/deterministic-jitter.d.ts +20 -0
- package/dist/daemon/device-keys.d.ts +45 -0
- package/dist/daemon/device-proof-signer.d.ts +41 -0
- package/dist/daemon/environment.d.ts +96 -0
- package/dist/daemon/git-workspace-store.d.ts +49 -0
- package/dist/daemon/git-workspace.d.ts +80 -0
- package/dist/daemon/http-client.d.ts +8 -0
- package/dist/daemon/journal/journal.d.ts +342 -0
- package/dist/daemon/journal/sqlite-journal.d.ts +173 -0
- package/dist/daemon/journal/sqlite-support.d.ts +96 -0
- package/dist/daemon/journal/storage-policy.d.ts +368 -0
- package/dist/daemon/long-poll-transport.d.ts +142 -0
- package/dist/daemon/observer.d.ts +345 -0
- package/dist/daemon/operational-health.d.ts +87 -0
- package/dist/daemon/policy.d.ts +16 -0
- package/dist/daemon/progress-batcher.d.ts +31 -0
- package/dist/daemon/runtime-capabilities.d.ts +36 -0
- package/dist/daemon/session-workspace-store.d.ts +75 -0
- package/dist/daemon/store.d.ts +54 -0
- package/dist/daemon/task-runner.d.ts +862 -0
- package/dist/daemon/truth-memory-client.d.ts +94 -0
- package/dist/daemon/url.d.ts +60 -0
- package/dist/daemon/ws-transport.d.ts +110 -0
- package/dist/diagnostics/diagnostics.d.ts +114 -0
- package/dist/diagnostics/support-bundle.d.ts +81 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +10056 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle/create-service-lifecycle.d.ts +59 -0
- package/dist/lifecycle/exec-runner.d.ts +141 -0
- package/dist/lifecycle/launchd.d.ts +55 -0
- package/dist/lifecycle/service-types.d.ts +177 -0
- package/dist/lifecycle/systemd.d.ts +46 -0
- package/dist/lifecycle/winsw.d.ts +64 -0
- package/dist/types.d.ts +178 -0
- package/dist/util/async-queue.d.ts +45 -0
- package/dist/util/atomic-write.d.ts +86 -0
- package/dist/util/secure-dir.d.ts +124 -0
- package/package.json +66 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
/** Every post-construction boundary crossed before a journal handle is returned to its owner. */
|
|
3
|
+
export type JournalOpenStep = 'after-open' | 'after-auto-vacuum' | 'after-wal' | 'after-foreign-keys' | 'after-synchronous' | 'after-header-read';
|
|
4
|
+
/**
|
|
5
|
+
* Test-only fault seam for proving that every post-open initialization failure
|
|
6
|
+
* closes the native handle before control returns to the journal constructor.
|
|
7
|
+
*/
|
|
8
|
+
export interface JournalOpenFaultSeam {
|
|
9
|
+
onStep?(step: JournalOpenStep): void;
|
|
10
|
+
/** Test-only close override; production always calls `DatabaseSync.close()`. */
|
|
11
|
+
close?(db: DatabaseSync): void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Initialization failed after SQLite returned a native handle, and closing
|
|
15
|
+
* that handle also failed. Callers must retain their cross-process ownership
|
|
16
|
+
* lease because the helper can no longer prove that no writer remains alive.
|
|
17
|
+
*/
|
|
18
|
+
export declare class JournalHandleCleanupError extends Error {
|
|
19
|
+
readonly failures: readonly unknown[];
|
|
20
|
+
constructor(message: string, failures: readonly unknown[]);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Whether `nodeVersion` (a `major.minor.patch` string shaped like
|
|
24
|
+
* `process.versions.node`) is new enough for `node:sqlite` to exist at all.
|
|
25
|
+
* A version-string heuristic only — `node:sqlite` shipped in 22.5.0 behind
|
|
26
|
+
* `--experimental-sqlite` and became usable unflagged later, so a runtime
|
|
27
|
+
* passing this can still fail to load the module. {@link isSqliteAvailable}
|
|
28
|
+
* is the authoritative check; this one only exists to turn the common
|
|
29
|
+
* "Node too old" case into a specific message. Unparsable input returns
|
|
30
|
+
* `true` (the `require` below is the real gate; don't false-negative on a
|
|
31
|
+
* version shape this hasn't seen).
|
|
32
|
+
*/
|
|
33
|
+
export declare function isSqliteCapableNodeVersion(nodeVersion: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Synchronously load `node:sqlite` via `createRequire` rather than a dynamic
|
|
36
|
+
* `import()`. `DatabaseSync`'s entire API is synchronous, and the journal is
|
|
37
|
+
* constructed synchronously from `createDaemonWithAdapters` alongside every
|
|
38
|
+
* other daemon-local store (`DeviceStore`, `CursorStore`, ...) — an async
|
|
39
|
+
* factory here would leak into every one of those call sites for nothing.
|
|
40
|
+
* `node:sqlite` is a built-in, so `require` resolves it even from this
|
|
41
|
+
* `"type": "module"` package. Memoized: the throw is re-derived per call from
|
|
42
|
+
* the version check rather than cached, which keeps the failure message
|
|
43
|
+
* accurate without keeping a rejected value around.
|
|
44
|
+
*/
|
|
45
|
+
export declare function loadSqliteModule(): typeof import('node:sqlite');
|
|
46
|
+
/**
|
|
47
|
+
* Whether `node:sqlite` can ACTUALLY be loaded right now. This is what the
|
|
48
|
+
* journal's own test suites use for `describe.skipIf(!isSqliteAvailable())`
|
|
49
|
+
* (the same idiom `@byok-sdk/server`'s SQLite suites already use), and what
|
|
50
|
+
* `createDaemonWithAdapters` calls before constructing a hosted journal so
|
|
51
|
+
* the refusal is a typed, up-front error instead of a cryptic
|
|
52
|
+
* `Cannot find module 'node:sqlite'` from deep inside the first append.
|
|
53
|
+
*/
|
|
54
|
+
export declare function isSqliteAvailable(): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Open (or create) the journal database at `path` with the durability
|
|
57
|
+
* settings architecture §12.7.2 pins, in the order they have to be applied:
|
|
58
|
+
*
|
|
59
|
+
* 1. `auto_vacuum = INCREMENTAL` — MUST precede table creation. `auto_vacuum`
|
|
60
|
+
* is only settable on a database with no schema yet; setting it afterward
|
|
61
|
+
* is silently ignored, and `compact()`'s `PRAGMA incremental_vacuum` would
|
|
62
|
+
* then be a no-op that reports success.
|
|
63
|
+
* 2. `journal_mode = WAL` — a reader and the single writer proceed without
|
|
64
|
+
* blocking each other, and it is what makes "drop the instance, reopen the
|
|
65
|
+
* same file" (the entire crash-matrix method) reliable.
|
|
66
|
+
* 3. `foreign_keys = ON` — the journal's tables are a real graph
|
|
67
|
+
* (transition -> task -> envelope); an orphan row here is corrupted
|
|
68
|
+
* recovery evidence, not a tolerable inconsistency.
|
|
69
|
+
* 4. `synchronous = FULL` — every ack-critical transaction fsyncs before it
|
|
70
|
+
* reports committed. This is THE setting the "durable append then cursor
|
|
71
|
+
* ack" ordering rests on; NORMAL would let the WAL commit land in the OS
|
|
72
|
+
* page cache, so the cursor could advance over an envelope a power cut
|
|
73
|
+
* then erases. Applied database-wide rather than per-transaction: the
|
|
74
|
+
* journal's whole reason to exist is ack-critical writes, and the
|
|
75
|
+
* maintenance paths that do not need it (checkpoint, incremental vacuum)
|
|
76
|
+
* are off the hot path anyway.
|
|
77
|
+
*
|
|
78
|
+
* `busyTimeoutMs` bounds how long a write waits on the lock before throwing
|
|
79
|
+
* `SQLITE_BUSY` — bounded on purpose, so contention surfaces as an error the
|
|
80
|
+
* caller sees rather than an unbounded stall on the envelope path.
|
|
81
|
+
*
|
|
82
|
+
* Throws whatever `node:sqlite` throws (module load failure, a corrupt file,
|
|
83
|
+
* an unreadable directory). Every caller is `SqliteLocalTaskJournal`'s
|
|
84
|
+
* constructor, which turns each of those into its own typed error — see
|
|
85
|
+
* `sqlite-journal.ts`.
|
|
86
|
+
*/
|
|
87
|
+
export declare function openJournalDatabase(path: string, busyTimeoutMs: number, faults?: JournalOpenFaultSeam): DatabaseSync;
|
|
88
|
+
/**
|
|
89
|
+
* Restrict the journal database and its WAL/SHM siblings to owner-only
|
|
90
|
+
* read/write. The journal holds raw task envelopes (instructions, policy) with
|
|
91
|
+
* no other access-control layer of its own, so it must not be left at whatever
|
|
92
|
+
* the process umask would give it. Call AFTER the schema exists, so the
|
|
93
|
+
* WAL/SHM files SQLite creates lazily on first write are already there; a
|
|
94
|
+
* sibling that does not exist yet is skipped rather than treated as an error.
|
|
95
|
+
*/
|
|
96
|
+
export declare function secureJournalFilePermissions(dbPath: string): void;
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `LocalStoragePolicy`, the watermark state machine, and the classified GC
|
|
3
|
+
* engine (architecture §12.7.2.1).
|
|
4
|
+
*
|
|
5
|
+
* §12.7.2 gives the daemon a database it must not lose. This file is the other
|
|
6
|
+
* half of that promise: a machine that runs out of disk cannot commit an
|
|
7
|
+
* ack-critical transaction, and a daemon that keeps acking mailbox rows it
|
|
8
|
+
* cannot durably record is losing tasks. So local storage stops being an
|
|
9
|
+
* operational afterthought and becomes an admission-control input.
|
|
10
|
+
*
|
|
11
|
+
* Three shapes here are load-bearing:
|
|
12
|
+
*
|
|
13
|
+
* - **The state machine is §12.7.2.1's table, not a threshold check.** Four
|
|
14
|
+
* states with distinct *behaviours*: `normal` runs unhurried maintenance;
|
|
15
|
+
* `pressure` alerts and cleans only what can be rebuilt; `hard-pressure`
|
|
16
|
+
* stops taking NEW work while everything that finishes existing work keeps
|
|
17
|
+
* running; `emergency` refuses to ack at all. The difference between
|
|
18
|
+
* `hard-pressure` and `emergency` is the whole design: one declines offers
|
|
19
|
+
* (a task the dispatcher can place elsewhere), the other freezes the cursor
|
|
20
|
+
* (a task the mailbox will redeliver). Neither deletes anything to make room.
|
|
21
|
+
* - **Cleanup is ordered and bounded, and it cannot name protected data.**
|
|
22
|
+
* The order is §12.7.2.1's 1-5, verbatim. The categories are
|
|
23
|
+
* {@link CleanableCategory}, which has no member for an unacked envelope, a
|
|
24
|
+
* `Running` task, an unconfirmed terminal, a recovery-marked row, a user
|
|
25
|
+
* workspace, or quarantine evidence — so no amount of pressure can express
|
|
26
|
+
* deleting one. Under pressure the order is TRUNCATED to its rebuildable
|
|
27
|
+
* prefix rather than extended: being short of disk is the worst moment to
|
|
28
|
+
* start deleting durable records, and the two cheap categories are the ones
|
|
29
|
+
* that give space back immediately.
|
|
30
|
+
* - **Everything measurable is injected.** Usage comes from the journal, free
|
|
31
|
+
* space from a provider (`fs.statfs` in production), time from a clock, and
|
|
32
|
+
* the cadence from a caller-driven `tick()`. There is no wall-clock race
|
|
33
|
+
* anywhere in this file's behaviour, which is what lets the S3.4 disk-pressure
|
|
34
|
+
* matrix assert state transitions instead of waiting for them.
|
|
35
|
+
*/
|
|
36
|
+
import type { CategoryUsage, CleanableCategory, CleanupCandidate, CleanupResult, CompactResult, LocalStorageUsage, LocalTaskJournal, StorageCategory } from './journal';
|
|
37
|
+
/** §12.7.2.1's recommended soft watermark: "达到约 80% budget". */
|
|
38
|
+
export declare const DEFAULT_SOFT_BUDGET_RATIO = 0.8;
|
|
39
|
+
/** §12.7.2.1's recommended hard watermark: "达到约 90% budget". */
|
|
40
|
+
export declare const DEFAULT_HARD_BUDGET_RATIO = 0.9;
|
|
41
|
+
/**
|
|
42
|
+
* Free bytes below which one ack-critical transaction can no longer be
|
|
43
|
+
* guaranteed, i.e. §12.7.2.1's `emergency` trigger. Sized for a WAL frame
|
|
44
|
+
* batch plus the checkpoint headroom a `synchronous=FULL` commit needs, not
|
|
45
|
+
* for a single row — a commit that cannot grow the WAL fails as surely as one
|
|
46
|
+
* that cannot grow the database.
|
|
47
|
+
*/
|
|
48
|
+
export declare const DEFAULT_ACK_CRITICAL_RESERVE_BYTES: number;
|
|
49
|
+
/** Upper bound on how many candidates one cleanup pass may act on. Bounded so a pass cannot monopolise the journal's single writer. */
|
|
50
|
+
export declare const DEFAULT_CLEANUP_BATCH_LIMIT = 64;
|
|
51
|
+
/** Upper bound on freelist pages one compaction pass returns to the filesystem. */
|
|
52
|
+
export declare const DEFAULT_INCREMENTAL_VACUUM_PAGES = 64;
|
|
53
|
+
/** Unhurried maintenance cadence while `normal` — §12.7.2.1's "常规低频 GC/compaction". */
|
|
54
|
+
export declare const DEFAULT_NORMAL_COMPACTION_INTERVAL_MS: number;
|
|
55
|
+
/** The accelerated cadence §12.7.2.1 asks for at `pressure` and above ("加快 journal compaction"). */
|
|
56
|
+
export declare const DEFAULT_PRESSURE_COMPACTION_INTERVAL_MS: number;
|
|
57
|
+
/**
|
|
58
|
+
* Per-category retention, as milliseconds between something becoming garbage
|
|
59
|
+
* and becoming ELIGIBLE for automatic cleanup (§12.7.5).
|
|
60
|
+
*
|
|
61
|
+
* `confirmed-journal` is deliberately the longest: pruning a journal row also
|
|
62
|
+
* drops its idempotency receipt, so its retention MUST outlast the mailbox
|
|
63
|
+
* redelivery window, or a very late redelivery would re-append an envelope
|
|
64
|
+
* this device already finished. `orphan-artifact` carries §12.7.5's 24-hour
|
|
65
|
+
* grace period, which is the reference scan's safety margin, not a guess.
|
|
66
|
+
*/
|
|
67
|
+
export declare const DEFAULT_RETENTION_MS: Readonly<Record<CleanableCategory, number>>;
|
|
68
|
+
/**
|
|
69
|
+
* Log rotation parameters (§12.7.2.1 lists them among a `LocalStoragePolicy`'s
|
|
70
|
+
* minimum contents).
|
|
71
|
+
*
|
|
72
|
+
* This SDK does not own the daemon's log writer — the host does. These are
|
|
73
|
+
* therefore the CONTRACT a host's rotator reads, and the reason they live here
|
|
74
|
+
* rather than in host config is that the rotated files they produce become
|
|
75
|
+
* `rotated-log` cleanup candidates governed by the same retention above. Two
|
|
76
|
+
* separate numbers for "when to rotate" and "when to delete" is exactly how a
|
|
77
|
+
* disk fills up with files nobody owns.
|
|
78
|
+
*/
|
|
79
|
+
export interface LogRotationPolicy {
|
|
80
|
+
/** Rotate the active log once it exceeds this size. */
|
|
81
|
+
readonly maxFileBytes: number;
|
|
82
|
+
/** How many rotated generations to keep before the oldest becomes a `rotated-log` cleanup candidate. */
|
|
83
|
+
readonly keepFiles: number;
|
|
84
|
+
}
|
|
85
|
+
export declare const DEFAULT_LOG_ROTATION: LogRotationPolicy;
|
|
86
|
+
/** Bounded compaction scheduling — see {@link LocalStoragePressureEngine.tick}. */
|
|
87
|
+
export interface CompactionPolicy {
|
|
88
|
+
readonly incrementalVacuumPages: number;
|
|
89
|
+
readonly normalIntervalMs: number;
|
|
90
|
+
readonly pressureIntervalMs: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The host-injected policy, resolved and validated (§12.7.2.1: "由 host/daemon
|
|
94
|
+
* config 注入,至少包含 `maxStoreBytes`、`minFreeBytes`、soft/hard watermark、
|
|
95
|
+
* 各数据类别的 retention、workspace policy 与 log rotation").
|
|
96
|
+
*/
|
|
97
|
+
export interface LocalStoragePolicy {
|
|
98
|
+
/** Total bytes this daemon's store directory may occupy. The budget the soft/hard ratios apply to. */
|
|
99
|
+
readonly maxStoreBytes: number;
|
|
100
|
+
/** Free bytes on the store's filesystem below which this device is at HARD pressure regardless of its own budget. */
|
|
101
|
+
readonly minFreeBytes: number;
|
|
102
|
+
/** Free bytes below which this device is at SOFT pressure. Defaults to twice {@link minFreeBytes}, so pressure engages one doubling before the floor. */
|
|
103
|
+
readonly softMinFreeBytes: number;
|
|
104
|
+
readonly softBudgetRatio: number;
|
|
105
|
+
readonly hardBudgetRatio: number;
|
|
106
|
+
readonly ackCriticalReserveBytes: number;
|
|
107
|
+
readonly retentionMs: Readonly<Record<CleanableCategory, number>>;
|
|
108
|
+
readonly logRotation: LogRotationPolicy;
|
|
109
|
+
readonly cleanupBatchLimit: number;
|
|
110
|
+
readonly compaction: CompactionPolicy;
|
|
111
|
+
}
|
|
112
|
+
/** What a host actually writes. Everything but the two budget numbers has a §12.7.2.1 default. */
|
|
113
|
+
export interface LocalStoragePolicyInput {
|
|
114
|
+
maxStoreBytes: number;
|
|
115
|
+
minFreeBytes: number;
|
|
116
|
+
softMinFreeBytes?: number;
|
|
117
|
+
softBudgetRatio?: number;
|
|
118
|
+
hardBudgetRatio?: number;
|
|
119
|
+
ackCriticalReserveBytes?: number;
|
|
120
|
+
retentionMs?: Partial<Record<CleanableCategory, number>>;
|
|
121
|
+
logRotation?: Partial<LogRotationPolicy>;
|
|
122
|
+
cleanupBatchLimit?: number;
|
|
123
|
+
compaction?: Partial<CompactionPolicy>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Thrown when a storage policy is internally inconsistent or out of range.
|
|
127
|
+
*
|
|
128
|
+
* Rejected at CONSTRUCTION, before a daemon exists, for the same reason
|
|
129
|
+
* `JournalUnavailableError` is: a policy whose hard watermark sits below its
|
|
130
|
+
* soft one, or whose budget is zero, produces a daemon that behaves plausibly
|
|
131
|
+
* until the day it matters. There is no clamping and no "closest sensible
|
|
132
|
+
* value" here — a misconfigured durability policy is a configuration bug to
|
|
133
|
+
* fix, not a number to guess at.
|
|
134
|
+
*/
|
|
135
|
+
export declare class LocalStoragePolicyError extends Error {
|
|
136
|
+
constructor(field: string, reason: string);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Thrown by {@link LocalStoragePressureEngine.assertAckCriticalAllowed} while
|
|
140
|
+
* the device is in `emergency`.
|
|
141
|
+
*
|
|
142
|
+
* Thrown from the daemon's envelope handler, BEFORE the journal append, which
|
|
143
|
+
* is what makes it §12.7.2.1's "fail-closed,不 ack 新 mailbox row": the
|
|
144
|
+
* handler rejects, so `ConnectionManager` records a stall instead of advancing
|
|
145
|
+
* the cursor, so the mailbox keeps the row and redelivers it. The task is not
|
|
146
|
+
* lost — it is left where it is still safe, which is the cloud.
|
|
147
|
+
*/
|
|
148
|
+
export declare class LocalStorageEmergencyError extends Error {
|
|
149
|
+
constructor(reason: string);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Validate and fill in a host's policy. The ONE place a `LocalStoragePolicy`
|
|
153
|
+
* comes into existence, so every consumer downstream can treat its fields as
|
|
154
|
+
* already checked.
|
|
155
|
+
*/
|
|
156
|
+
export declare function resolveLocalStoragePolicy(input: LocalStoragePolicyInput): LocalStoragePolicy;
|
|
157
|
+
/**
|
|
158
|
+
* When something that became garbage at `since` becomes eligible for automatic
|
|
159
|
+
* cleanup. The ONE place retention turns into a timestamp, so a producer
|
|
160
|
+
* calling `enqueueCleanupCandidate` and this engine consuming it agree by
|
|
161
|
+
* construction rather than by two matching constants.
|
|
162
|
+
*/
|
|
163
|
+
export declare function cleanupEligibleAt(policy: LocalStoragePolicy, category: CleanableCategory, since: Date): string;
|
|
164
|
+
/**
|
|
165
|
+
* §12.7.2.1's four states, verbatim:
|
|
166
|
+
*
|
|
167
|
+
* | state | trigger | behaviour |
|
|
168
|
+
* | --- | --- | --- |
|
|
169
|
+
* | `normal` | below soft | unhurried GC/compaction |
|
|
170
|
+
* | `pressure` | ≥ soft budget, or free below the soft minimum | alert; clean only rebuildable/expired categories; accelerate compaction |
|
|
171
|
+
* | `hard-pressure` | ≥ hard budget, or free below the hard minimum | stop admitting new ordinary tasks; terminal/truth flush, delete, export and recovery all continue |
|
|
172
|
+
* | `emergency` | one ack-critical transaction can no longer be guaranteed | fail closed: do not ack new mailbox rows; preserve existing recovery evidence |
|
|
173
|
+
*/
|
|
174
|
+
export type StoragePressureState = 'normal' | 'pressure' | 'hard-pressure' | 'emergency';
|
|
175
|
+
export interface StorageMeasurement {
|
|
176
|
+
readonly usage: LocalStorageUsage;
|
|
177
|
+
readonly freeBytes: number;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The state machine itself — a pure function of policy, measurement, and
|
|
181
|
+
* whether an ack-critical write has already been observed to fail.
|
|
182
|
+
*
|
|
183
|
+
* Evaluated worst-first: `emergency` is not "very bad pressure", it is a
|
|
184
|
+
* different claim (the next commit may not land), so it is decided before any
|
|
185
|
+
* budget arithmetic. `latchedFailure` exists because the cheapest evidence
|
|
186
|
+
* that a transaction cannot complete is one that already did not: a disk-full
|
|
187
|
+
* error from an ack-critical write is a fact, where free-space arithmetic is
|
|
188
|
+
* an estimate.
|
|
189
|
+
*/
|
|
190
|
+
export declare function computePressureState(policy: LocalStoragePolicy, measurement: StorageMeasurement, latchedFailure?: string): StoragePressureState;
|
|
191
|
+
/**
|
|
192
|
+
* §12.7.2.1's cleanup order, 1-5:
|
|
193
|
+
*
|
|
194
|
+
* 1. expired upload/download temp files and rebuildable caches;
|
|
195
|
+
* 2. rotated logs past their retention;
|
|
196
|
+
* 3. journal rows for tasks whose terminal the cloud confirmed and that carry
|
|
197
|
+
* no recovery marker — **compact first, then batch delete**;
|
|
198
|
+
* 4. host-marked ephemeral workspaces for tasks already terminal;
|
|
199
|
+
* 5. orphan artifacts, after a reference scan plus grace period.
|
|
200
|
+
*
|
|
201
|
+
* Under pressure the order is TRUNCATED to steps 1-2, not extended. Steps 3-5
|
|
202
|
+
* touch durable records, need a compaction or a reference scan first, and give
|
|
203
|
+
* their space back slowly; steps 1-2 are pure rebuildable garbage and give it
|
|
204
|
+
* back immediately. Deleting durable evidence is exactly the wrong reflex when
|
|
205
|
+
* the disk is nearly full, so it stays on the unhurried `normal` cadence where
|
|
206
|
+
* a mistake is recoverable.
|
|
207
|
+
*/
|
|
208
|
+
export declare function cleanupOrderFor(state: StoragePressureState): readonly CleanableCategory[];
|
|
209
|
+
/** What a {@link CleanupExecutor} did with one candidate. Mirrors {@link CleanupResult} minus the bookkeeping the engine fills in. */
|
|
210
|
+
export interface CleanupExecution {
|
|
211
|
+
readonly outcome: 'deleted' | 'skipped' | 'failed';
|
|
212
|
+
readonly bytesReclaimed?: number;
|
|
213
|
+
readonly error?: string;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Performs one candidate's actual deletion.
|
|
217
|
+
*
|
|
218
|
+
* A seam rather than a method because the journal owns metadata and the
|
|
219
|
+
* filesystem owns bytes, and the crash window BETWEEN them (delete the file,
|
|
220
|
+
* die before marking; mark, die before deleting) is S3.4 point 12. Keeping
|
|
221
|
+
* them separate is what lets that window be tested at all.
|
|
222
|
+
*
|
|
223
|
+
* It receives a {@link CleanupCandidate}, whose `category` is a
|
|
224
|
+
* {@link CleanableCategory} — so an executor cannot be handed protected data
|
|
225
|
+
* even by a caller trying to.
|
|
226
|
+
*/
|
|
227
|
+
export type CleanupExecutor = (candidate: CleanupCandidate) => Promise<CleanupExecution>;
|
|
228
|
+
/** `confirmed-journal` candidates address a journal task, not a path. This prefix is that distinction, spelled out. */
|
|
229
|
+
export declare const JOURNAL_TASK_REF_PREFIX = "task:";
|
|
230
|
+
/**
|
|
231
|
+
* The default cleanup worker: filesystem removal for the four path-addressed
|
|
232
|
+
* categories, and a delegated journal prune for `confirmed-journal`.
|
|
233
|
+
*
|
|
234
|
+
* Two deliberate behaviours:
|
|
235
|
+
*
|
|
236
|
+
* - **A missing path is `deleted`, not `failed`.** That is S3.4 point 12's
|
|
237
|
+
* first order (file gone, metadata not yet marked) converging on retry: the
|
|
238
|
+
* retry finds nothing to do and says so, rather than looping forever on a
|
|
239
|
+
* candidate whose work is already done.
|
|
240
|
+
* - **A relative `ref` is refused.** Cleanup resolves paths against nothing —
|
|
241
|
+
* a relative ref would delete whatever the daemon's cwd happens to make it,
|
|
242
|
+
* which is not a bug worth having once.
|
|
243
|
+
*/
|
|
244
|
+
export declare function createFilesystemCleanupExecutor(options?: {
|
|
245
|
+
readonly pruneJournalTask?: (taskId: string) => Promise<boolean>;
|
|
246
|
+
}): CleanupExecutor;
|
|
247
|
+
/** What the status surface renders, and what a `tick()` hands back. */
|
|
248
|
+
export interface StorageStatusSnapshot {
|
|
249
|
+
readonly state: StoragePressureState;
|
|
250
|
+
readonly measuredAt: string;
|
|
251
|
+
readonly budgetBytes: number;
|
|
252
|
+
readonly usedBytes: number;
|
|
253
|
+
readonly freeBytes: number;
|
|
254
|
+
readonly categories: Readonly<Record<StorageCategory, CategoryUsage>>;
|
|
255
|
+
readonly lastCompaction?: CompactResult & {
|
|
256
|
+
readonly at: string;
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
export type StoragePressureEvent =
|
|
260
|
+
/** §12.7.2.1's "发出告警" — emitted on every transition, including back down to `normal`. */
|
|
261
|
+
{
|
|
262
|
+
readonly kind: 'state-changed';
|
|
263
|
+
readonly from: StoragePressureState;
|
|
264
|
+
readonly to: StoragePressureState;
|
|
265
|
+
readonly snapshot: StorageStatusSnapshot;
|
|
266
|
+
} | {
|
|
267
|
+
readonly kind: 'cleanup';
|
|
268
|
+
readonly result: CleanupResult;
|
|
269
|
+
readonly category: CleanableCategory;
|
|
270
|
+
} | {
|
|
271
|
+
readonly kind: 'compaction';
|
|
272
|
+
readonly result: CompactResult;
|
|
273
|
+
};
|
|
274
|
+
export interface StorageTickResult {
|
|
275
|
+
readonly state: StoragePressureState;
|
|
276
|
+
readonly snapshot: StorageStatusSnapshot;
|
|
277
|
+
readonly cleaned: readonly CleanupResult[];
|
|
278
|
+
readonly compaction?: CompactResult;
|
|
279
|
+
}
|
|
280
|
+
export interface TimerLike {
|
|
281
|
+
setInterval(handler: () => void, ms: number): unknown;
|
|
282
|
+
clearInterval(handle: unknown): void;
|
|
283
|
+
}
|
|
284
|
+
export interface LocalStoragePressureEngineOptions {
|
|
285
|
+
readonly policy: LocalStoragePolicy | LocalStoragePolicyInput;
|
|
286
|
+
readonly journal: LocalTaskJournal;
|
|
287
|
+
/** Free bytes on the store's filesystem. `fs.statfs` in production (see {@link createStatfsFreeBytesProvider}); a fixed number under test. */
|
|
288
|
+
readonly freeBytesProvider: () => number | Promise<number>;
|
|
289
|
+
/** Performs deletions. Defaults to {@link createFilesystemCleanupExecutor}. */
|
|
290
|
+
readonly executor?: CleanupExecutor;
|
|
291
|
+
readonly clock?: () => Date;
|
|
292
|
+
readonly onEvent?: (event: StoragePressureEvent) => void;
|
|
293
|
+
/** Reports only the scheduler's own maintenance pass outcome; never includes task-domain failures. */
|
|
294
|
+
readonly onMaintenanceOutcome?: (outcome: 'success' | 'failure') => void;
|
|
295
|
+
/** Injected so `start()`'s periodic driver is substitutable; the matrix never uses it and drives {@link LocalStoragePressureEngine.tick} directly. */
|
|
296
|
+
readonly timers?: TimerLike;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Drives §12.7.2.1: measure, decide the state, clean in order, compact within
|
|
300
|
+
* bounds — and answer the two questions the rest of the daemon asks
|
|
301
|
+
* ({@link admissionGuard}, {@link assertAckCriticalAllowed}).
|
|
302
|
+
*
|
|
303
|
+
* The cadence is a caller-driven {@link tick}. `start()` merely arranges for
|
|
304
|
+
* something to call it periodically, so a host with its own scheduler can skip
|
|
305
|
+
* it entirely, and the disk-pressure matrix can advance state deterministically
|
|
306
|
+
* with no timer at all. Nothing here runs on the envelope or task hot path:
|
|
307
|
+
* the only two things the hot path calls are the two synchronous questions,
|
|
308
|
+
* both of which read a field.
|
|
309
|
+
*/
|
|
310
|
+
export declare class LocalStoragePressureEngine {
|
|
311
|
+
#private;
|
|
312
|
+
readonly policy: LocalStoragePolicy;
|
|
313
|
+
constructor(options: LocalStoragePressureEngineOptions);
|
|
314
|
+
get state(): StoragePressureState;
|
|
315
|
+
/** The last measured snapshot, or `undefined` before the first {@link tick}. */
|
|
316
|
+
snapshot(): StorageStatusSnapshot | undefined;
|
|
317
|
+
/**
|
|
318
|
+
* §12.7.2.1's hard-pressure row, as a `TaskRunnerDeps.admissionGuard`:
|
|
319
|
+
* "停止接收新的普通 task;仍允许 terminal/truth flush、删除、导出、doctor 与
|
|
320
|
+
* 恢复操作".
|
|
321
|
+
*
|
|
322
|
+
* The decline is RETRYABLE because pressure is a property of this device at
|
|
323
|
+
* this moment — a dispatcher re-routing the task to another device genuinely
|
|
324
|
+
* helps, and the same device may take it in a minute. Nothing in this method
|
|
325
|
+
* touches the disk; it reads the state the last tick computed, so an offer
|
|
326
|
+
* arriving between ticks is answered instantly.
|
|
327
|
+
*/
|
|
328
|
+
admissionGuard(): {
|
|
329
|
+
readonly admit: true;
|
|
330
|
+
} | {
|
|
331
|
+
readonly admit: false;
|
|
332
|
+
readonly reason: string;
|
|
333
|
+
readonly retryable: boolean;
|
|
334
|
+
};
|
|
335
|
+
/** Throws {@link LocalStorageEmergencyError} while in `emergency`. Called immediately before every ack-critical journal append. */
|
|
336
|
+
assertAckCriticalAllowed(): void;
|
|
337
|
+
/**
|
|
338
|
+
* Latch `emergency` from an ack-critical write that ACTUALLY failed.
|
|
339
|
+
*
|
|
340
|
+
* A commit that returned `SQLITE_FULL` is stronger evidence than any free-space
|
|
341
|
+
* estimate, and it must not be forgotten on the next tick just because the
|
|
342
|
+
* arithmetic happens to look survivable. The latch clears only when a tick
|
|
343
|
+
* measures a genuinely `normal` device — not `pressure`, not `hard-pressure`:
|
|
344
|
+
* coming back from "the disk refused a write" requires actual headroom, not a
|
|
345
|
+
* borderline reading.
|
|
346
|
+
*/
|
|
347
|
+
noteAckCriticalFailure(reason: string): void;
|
|
348
|
+
/**
|
|
349
|
+
* One maintenance pass: measure, transition, clean in order, compact if due.
|
|
350
|
+
*
|
|
351
|
+
* Ordered this way on purpose — the cleanup pass acts on the state THIS
|
|
352
|
+
* measurement produced, so a device that just crossed into pressure cleans on
|
|
353
|
+
* the same tick it alerts, rather than one cadence later.
|
|
354
|
+
*/
|
|
355
|
+
tick(): Promise<StorageTickResult>;
|
|
356
|
+
/** Begin periodic ticking. A host with its own scheduler need never call this. */
|
|
357
|
+
start(): void;
|
|
358
|
+
stop(): Promise<void>;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Production free-space provider: bytes available to an unprivileged process
|
|
362
|
+
* on the filesystem holding `dir`.
|
|
363
|
+
*
|
|
364
|
+
* `bavail`, not `bfree` — the reserved blocks `bfree` includes are not space
|
|
365
|
+
* this daemon can write into, and treating them as headroom is how a device
|
|
366
|
+
* discovers it is out of disk at commit time instead of at measurement time.
|
|
367
|
+
*/
|
|
368
|
+
export declare function createStatfsFreeBytesProvider(dir: string): () => Promise<number>;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { type Envelope } from '@byok-sdk/protocol';
|
|
2
|
+
import { AuthManager } from './auth-manager';
|
|
3
|
+
export interface LongPollClientOptions {
|
|
4
|
+
serverUrl: string;
|
|
5
|
+
auth: AuthManager;
|
|
6
|
+
getCursor: () => number | undefined;
|
|
7
|
+
onEnvelope: (envelope: Envelope) => void;
|
|
8
|
+
/** Called once the device is found to be revoked (401 surfaced through {@link AuthManager}) — the loop stops itself rather than retrying. */
|
|
9
|
+
onRevoked?: () => void;
|
|
10
|
+
/**
|
|
11
|
+
* M4 Phase 4 (version-negotiation drill fix), scope narrowed by finding F1:
|
|
12
|
+
* called ONLY for a batch entry that failed to parse because its `type`
|
|
13
|
+
* is entirely unrecognized (`parseMessage` throwing
|
|
14
|
+
* {@link UnknownMessageTypeError} — mirrors `ws-transport.ts`'s identical
|
|
15
|
+
* per-frame tolerance for that SPECIFIC failure) and which still carries a
|
|
16
|
+
* numeric envelope-level `seq` AND a recognizably task-class `type` (a
|
|
17
|
+
* `task.` prefix — see `extractSkippableSeq`'s own doc comment for why a
|
|
18
|
+
* `conn.*`-shaped or type-less entry is deliberately excluded, mirroring
|
|
19
|
+
* F2's "conn.* is never cursor-tracked" rule), so the caller can advance
|
|
20
|
+
* its cursor/watermark past it even though there is no real `Envelope` to
|
|
21
|
+
* hand to `onEnvelope`. Without this, a persistently-redelivered
|
|
22
|
+
* unrecognized-type entry (the real server retains and redelivers an
|
|
23
|
+
* un-acked envelope, protocol §9) would keep reappearing at the same
|
|
24
|
+
* cursor position forever.
|
|
25
|
+
*
|
|
26
|
+
* Finding F1: a RECOGNIZED type that fails schema validation
|
|
27
|
+
* ({@link EnvelopeValidationError} — e.g. a `task.offer` whose
|
|
28
|
+
* `PermissionPolicy` rejects an unknown constraint) is deliberately NOT
|
|
29
|
+
* reported here. That failure is a genuinely malformed control message,
|
|
30
|
+
* not forward-compat tolerance — forwarding its `seq` here would
|
|
31
|
+
* permanently ack a message the daemon never actually understood (the
|
|
32
|
+
* server would stop redelivering it, silently stranding whatever it was
|
|
33
|
+
* offering). The WS path never had this hazard (an unparseable WS frame
|
|
34
|
+
* has no skip-side cursor bookkeeping at all — see
|
|
35
|
+
* `ws-transport.ts` — so it simply gets redelivered later); this callback
|
|
36
|
+
* being scoped to `UnknownMessageTypeError` only is what makes long-poll
|
|
37
|
+
* match that same "no silent permanent ack" property for real. Optional
|
|
38
|
+
* only for constructor/test convenience — `ConnectionManager` always
|
|
39
|
+
* supplies it.
|
|
40
|
+
*/
|
|
41
|
+
onSkippedSeq?: (seq: number) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Finding R1 (cross-model re-review — the F1 fix alone was NOT-CLOSED):
|
|
44
|
+
* called for a batch entry whose `type` WAS recognized but whose payload
|
|
45
|
+
* failed schema validation ({@link EnvelopeValidationError}) — a genuine
|
|
46
|
+
* delivery failure at that specific seq, not forward-compat tolerance
|
|
47
|
+
* (contrast {@link onSkippedSeq}, which is scoped to the opposite case,
|
|
48
|
+
* an entirely unrecognized type). F1's own fix — simply not forwarding
|
|
49
|
+
* this seq to `onSkippedSeq` — turned out to be insufficient on its own:
|
|
50
|
+
* a LATER valid envelope in the same or a later batch would still
|
|
51
|
+
* silently advance the durable cursor PAST this seq once its own handler
|
|
52
|
+
* succeeded, since nothing had told `ConnectionManager` this seq needed
|
|
53
|
+
* the same stall treatment a thrown handler failure already gets — an
|
|
54
|
+
* INDIRECT permanent ack, one hop removed from the exact bug F1 set out
|
|
55
|
+
* to fix. `ConnectionManager` (`noteValidationFailure`) engages
|
|
56
|
+
* `stalledAtSeq` for this seq the same way `process()`'s own catch block
|
|
57
|
+
* does for a real thrown handler — freezing `dedupWatermark()` at the
|
|
58
|
+
* durable cursor (so the server's retain-and-redeliver semantics,
|
|
59
|
+
* protocol §9, keep this seq alive) and, via that SAME existing
|
|
60
|
+
* machinery, holding back the cursor for anything else delivered after it
|
|
61
|
+
* in the same batch too, exactly as a real handler failure already would.
|
|
62
|
+
* Optional only for constructor/test convenience — `ConnectionManager`
|
|
63
|
+
* always supplies it.
|
|
64
|
+
*/
|
|
65
|
+
onValidationFailedSeq?: (seq: number) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Finding P2 (Fix 2a): true while a `task.*` envelope's handler has failed
|
|
68
|
+
* and hasn't yet been successfully reprocessed
|
|
69
|
+
* (`ConnectionManager.stalledAtSeq`). While true, `getCursor()` stays
|
|
70
|
+
* frozen below the actual delivery watermark (see
|
|
71
|
+
* `ConnectionManager.dedupWatermark`'s own doc comment) — so a non-empty
|
|
72
|
+
* response here doesn't mean "new events arrived", it can just as well
|
|
73
|
+
* mean "the whole post-cursor backlog got re-pulled again with no
|
|
74
|
+
* progress". Without a backoff for that case (distinct from "zero
|
|
75
|
+
* events"), a persistently-failing handler made this loop spin at RTT
|
|
76
|
+
* against the server. Optional only for constructor/test convenience —
|
|
77
|
+
* `ConnectionManager` always supplies it.
|
|
78
|
+
*/
|
|
79
|
+
isStalled?: () => boolean;
|
|
80
|
+
/** Backoff between failed poll attempts (network/HTTP errors), AND between cycles that made no cursor progress while stalled (finding P2/Fix 2a — see {@link isStalled}). The reference server holds each successful, non-stalled request open ~50s itself (protocol §8), so this only matters when a request errors outright or is stalled. Default 2s. */
|
|
81
|
+
retryDelayMs?: number;
|
|
82
|
+
/** Deterministic delay authority for automatic failed/stalled cycles. */
|
|
83
|
+
retryDelayForAttempt?: (attempt: number, baseDelayMs: number) => number;
|
|
84
|
+
onOperationalOutcome?: (outcome: 'success' | 'failure') => void;
|
|
85
|
+
/**
|
|
86
|
+
* Minimum delay before the next request when a poll comes back with zero
|
|
87
|
+
* events. The reference server holds each request open ~50s waiting for
|
|
88
|
+
* something to happen, which throttles the loop for free; a server that
|
|
89
|
+
* (like this SDK's own test stub) responds immediately instead would
|
|
90
|
+
* otherwise make this a tight busy-loop. Default 250ms.
|
|
91
|
+
*/
|
|
92
|
+
idleDelayMs?: number;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Protocol §8 long-poll fallback: `GET /byok/events?cursor=N` in a loop,
|
|
96
|
+
* used while WS connectivity is unavailable (see `ConnectionManager`), plus
|
|
97
|
+
* `POST /byok/messages` for the daemon's own outbound envelopes while in
|
|
98
|
+
* this mode (finding F6 — long-poll is a full transport, not receive-only:
|
|
99
|
+
* see docs/protocol.md §8).
|
|
100
|
+
*
|
|
101
|
+
* Design B (finding N4): this is a stateless drainer, symmetric with
|
|
102
|
+
* `WsTransport.sendNow` — it holds no outbound queue of its own.
|
|
103
|
+
* `ConnectionManager` owns the single shared outbox both transports drain
|
|
104
|
+
* from (so a transport switch never strands a queued envelope);
|
|
105
|
+
* `postBatch` is a single POST attempt, reporting back whether the server
|
|
106
|
+
* accepted it. All retry/backoff policy (and re-checking which transport is
|
|
107
|
+
* currently active) lives in the caller (`ConnectionManager.drainOutbox`).
|
|
108
|
+
*/
|
|
109
|
+
export declare class LongPollClient {
|
|
110
|
+
private readonly opts;
|
|
111
|
+
private running;
|
|
112
|
+
/**
|
|
113
|
+
* Finding R1: seqs this loop has already `console.warn`'d about for a
|
|
114
|
+
* validation-failed (recognized-type, invalid-payload) entry — a poison
|
|
115
|
+
* entry is redelivered on every poll cycle for as long as it stalls the
|
|
116
|
+
* cursor (protocol §9), so without this the SAME warning would repeat
|
|
117
|
+
* every ~poll-interval, forever, for one persistently-malformed message.
|
|
118
|
+
* Never cleared: once a seq is fixed (a corrected redelivery is
|
|
119
|
+
* processed), the server never redelivers that seq again, so there is
|
|
120
|
+
* nothing left to re-warn about for it either. Soft-capped — this is a
|
|
121
|
+
* pathological/rare path (unlike a per-task hot structure), so on the
|
|
122
|
+
* rare chance a connection somehow accumulates an unreasonable number of
|
|
123
|
+
* distinct poisoned seqs, this is simply cleared outright (accepting a
|
|
124
|
+
* handful of possible re-warnings) rather than carrying any per-entry
|
|
125
|
+
* eviction bookkeeping for a case this unlikely.
|
|
126
|
+
*/
|
|
127
|
+
private readonly warnedValidationFailureSeqs;
|
|
128
|
+
constructor(opts: LongPollClientOptions);
|
|
129
|
+
start(): void;
|
|
130
|
+
stop(): void;
|
|
131
|
+
/**
|
|
132
|
+
* POST one batch of envelopes to `/byok/messages` (finding F6/protocol
|
|
133
|
+
* §8.2) — a single attempt, no internal retry loop. Every envelope in
|
|
134
|
+
* `envelopes` is routed through the server's single inbound gate
|
|
135
|
+
* (`ConnectionHub.handleInbound`), so a resend of the SAME batch (same
|
|
136
|
+
* envelope `id`s — the caller must never rebuild them) is deduped
|
|
137
|
+
* server-side into a safe no-op rather than reprocessed (§9). Returns
|
|
138
|
+
* `true` once the server has accepted the batch.
|
|
139
|
+
*/
|
|
140
|
+
postBatch(envelopes: Envelope[]): Promise<boolean>;
|
|
141
|
+
private loop;
|
|
142
|
+
}
|