@ours.network/fleet 0.11.1 → 0.13.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.
Files changed (43) hide show
  1. package/README.md +225 -0
  2. package/dist/briefing.js +25 -0
  3. package/dist/cli.js +408 -1
  4. package/dist/config.d.ts +31 -1
  5. package/dist/config.js +123 -2
  6. package/dist/docs.d.ts +1 -1
  7. package/dist/docs.js +143 -0
  8. package/dist/duration.js +7 -3
  9. package/dist/index.d.ts +2 -1
  10. package/dist/index.js +1 -0
  11. package/dist/loops/config.d.ts +30 -0
  12. package/dist/loops/config.js +135 -0
  13. package/dist/loops/manager.d.ts +48 -0
  14. package/dist/loops/manager.js +237 -0
  15. package/dist/loops/state.d.ts +54 -0
  16. package/dist/loops/state.js +148 -0
  17. package/dist/monitor.js +26 -2
  18. package/dist/owner-channel/attachments.d.ts +74 -0
  19. package/dist/owner-channel/attachments.js +378 -0
  20. package/dist/owner-channel/channel.d.ts +167 -0
  21. package/dist/owner-channel/channel.js +874 -0
  22. package/dist/owner-channel/mcp.d.ts +24 -0
  23. package/dist/owner-channel/mcp.js +123 -0
  24. package/dist/owner-channel/notices.d.ts +21 -0
  25. package/dist/owner-channel/notices.js +66 -0
  26. package/dist/owner-channel/state.d.ts +44 -0
  27. package/dist/owner-channel/state.js +184 -0
  28. package/dist/owner-channel/tasks.d.ts +62 -0
  29. package/dist/owner-channel/tasks.js +246 -0
  30. package/dist/resolved-plan.js +12 -0
  31. package/dist/runner.d.ts +3 -0
  32. package/dist/runner.js +112 -5
  33. package/dist/session/acp.d.ts +4 -2
  34. package/dist/session/acp.js +82 -25
  35. package/dist/session/arbiter.d.ts +42 -0
  36. package/dist/session/arbiter.js +72 -0
  37. package/dist/session/control.d.ts +12 -1
  38. package/dist/session/control.js +56 -3
  39. package/dist/session/types.d.ts +28 -2
  40. package/dist/session/types.js +5 -2
  41. package/dist/spawn.js +7 -3
  42. package/dist/supervisor/systemd.js +12 -2
  43. package/package.json +1 -1
@@ -0,0 +1,24 @@
1
+ export declare class OursMcpError extends Error {
2
+ }
3
+ export interface OursToolClient {
4
+ start(): Promise<void>;
5
+ callTool(name: string, args?: Record<string, unknown>): Promise<unknown>;
6
+ close(): Promise<void>;
7
+ }
8
+ /** Minimal MCP stdio client. One instance owns exactly one ours identity binding. */
9
+ export declare class OursMcpClient implements OursToolClient {
10
+ private readonly command;
11
+ private readonly env;
12
+ private readonly log;
13
+ private child?;
14
+ private nextId;
15
+ private tail;
16
+ constructor(command?: string, env?: Record<string, string>, log?: (line: string) => void);
17
+ start(): Promise<void>;
18
+ callTool(name: string, args?: Record<string, unknown>): Promise<unknown>;
19
+ close(): Promise<void>;
20
+ private request;
21
+ private requestNow;
22
+ private notify;
23
+ private write;
24
+ }
@@ -0,0 +1,123 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { createInterface } from 'node:readline';
4
+ export class OursMcpError extends Error {
5
+ }
6
+ /** Minimal MCP stdio client. One instance owns exactly one ours identity binding. */
7
+ export class OursMcpClient {
8
+ command;
9
+ env;
10
+ log;
11
+ child;
12
+ nextId = 0;
13
+ tail = Promise.resolve();
14
+ constructor(command = 'ours-mcp', env = {}, log = () => undefined) {
15
+ this.command = command;
16
+ this.env = env;
17
+ this.log = log;
18
+ }
19
+ async start() {
20
+ if (this.child && this.child.exitCode === null)
21
+ return;
22
+ const child = spawn(this.command, ['proxy'], {
23
+ env: {
24
+ ...process.env,
25
+ ...this.env,
26
+ // Bindings are keyed by this value. Sharing it would silently rebind a
27
+ // role's normal mailbox or another owner channel.
28
+ CLAUDE_CODE_SESSION_ID: `ours-fleet-owner-${process.pid}-${randomUUID()}`,
29
+ },
30
+ stdio: ['pipe', 'pipe', 'pipe'],
31
+ });
32
+ await new Promise((resolve, reject) => {
33
+ child.once('spawn', resolve);
34
+ child.once('error', reject);
35
+ });
36
+ this.child = child;
37
+ child.stdin.on('error', error => this.log(`ours-mcp stdin: ${error.message}`));
38
+ createInterface({ input: child.stderr }).on('line', line => this.log(`ours-mcp: ${line}`));
39
+ try {
40
+ await this.request('initialize', {
41
+ protocolVersion: '2025-03-26', capabilities: {},
42
+ clientInfo: { name: 'ours-fleet-owner-channel', version: '1' },
43
+ });
44
+ await this.notify('notifications/initialized', {});
45
+ }
46
+ catch (error) {
47
+ await this.close();
48
+ throw error;
49
+ }
50
+ }
51
+ async callTool(name, args = {}) {
52
+ const result = await this.request('tools/call', { name, arguments: args });
53
+ const text = (result.content ?? [])
54
+ .filter(item => item.type === 'text').map(item => item.text ?? '').join('\n').trim();
55
+ if (result.isError)
56
+ throw new OursMcpError(text || `ours tool ${name} failed`);
57
+ if (result.structuredContent !== undefined)
58
+ return result.structuredContent;
59
+ if (!text)
60
+ return {};
61
+ try {
62
+ return JSON.parse(text);
63
+ }
64
+ catch {
65
+ return text;
66
+ }
67
+ }
68
+ async close() {
69
+ const child = this.child;
70
+ this.child = undefined;
71
+ if (!child || child.exitCode !== null)
72
+ return;
73
+ child.kill('SIGTERM');
74
+ await new Promise(resolve => {
75
+ const timer = setTimeout(() => { child.kill('SIGKILL'); resolve(); }, 5_000);
76
+ child.once('exit', () => { clearTimeout(timer); resolve(); });
77
+ });
78
+ }
79
+ request(method, params) {
80
+ const run = this.tail.then(() => this.requestNow(method, params));
81
+ this.tail = run.then(() => undefined, () => undefined);
82
+ return run;
83
+ }
84
+ async requestNow(method, params) {
85
+ const child = this.child;
86
+ if (!child || child.exitCode !== null)
87
+ throw new OursMcpError('ours-mcp proxy is not running');
88
+ const id = ++this.nextId;
89
+ await this.write(child, { jsonrpc: '2.0', id, method, params });
90
+ const lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
91
+ try {
92
+ for await (const line of lines) {
93
+ let response;
94
+ try {
95
+ response = JSON.parse(line);
96
+ }
97
+ catch {
98
+ continue;
99
+ }
100
+ if (response.id !== id)
101
+ continue;
102
+ if (response.error !== undefined)
103
+ throw new OursMcpError(JSON.stringify(response.error));
104
+ return response.result ?? {};
105
+ }
106
+ throw new OursMcpError('ours-mcp proxy closed its output');
107
+ }
108
+ finally {
109
+ lines.close();
110
+ }
111
+ }
112
+ async notify(method, params) {
113
+ const child = this.child;
114
+ if (!child || child.exitCode !== null)
115
+ throw new OursMcpError('ours-mcp proxy is not running');
116
+ await this.write(child, { jsonrpc: '2.0', method, params });
117
+ }
118
+ write(child, value) {
119
+ return new Promise((resolve, reject) => {
120
+ child.stdin.write(JSON.stringify(value) + '\n', error => error ? reject(error) : resolve());
121
+ });
122
+ }
123
+ }
@@ -0,0 +1,21 @@
1
+ import type { SessionSnapshot, TurnOutcome } from '../session/types.js';
2
+ import type { OwnerTaskPhase } from './tasks.js';
3
+ export type OwnerUpdatePhase = 'working' | 'approval' | 'blocked';
4
+ export type OwnerProgressPhase = 'starting request' | 'waiting behind earlier requests' | 'working on request' | 'planning next step' | 'using tools' | 'reviewing tool results' | 'drafting response' | 'waiting for approval' | 'resuming after permission decision' | 'recovering from session error';
5
+ export declare const ownerNotices: {
6
+ receivedStarted: () => string;
7
+ receivedQueued: (queuedBehind: number) => string;
8
+ receivedInterrupting: () => string;
9
+ status: (role: string, snapshot: SessionSnapshot) => string;
10
+ interrupted: (role: string) => string;
11
+ interruptFailed: (role: string) => string;
12
+ attachmentRejected: (reason: string) => string;
13
+ attachmentFailed: () => string;
14
+ deliveryFailed: (role: string) => string;
15
+ progress: (elapsedMs: number, phase: OwnerProgressPhase, started: number, completed: number, activityUpdates?: number) => string;
16
+ authoredUpdate: (phase: OwnerUpdatePhase, message: string) => string;
17
+ taskReport: (phase: OwnerTaskPhase, message: string) => string;
18
+ completedWithoutText: () => string;
19
+ terminal: (outcome: TurnOutcome) => "✅ Request completed." | "🛑 Request was cancelled before completion." | "⚠️ The agent declined this request." | "⚠️ Request failed before completion." | "⚠️ Request ended without a confirmed completion.";
20
+ chunk: (part: number, total: number) => string;
21
+ };
@@ -0,0 +1,66 @@
1
+ const duration = (elapsedMs) => {
2
+ const seconds = Math.max(0, Math.floor(elapsedMs / 1_000));
3
+ const hours = Math.floor(seconds / 3_600);
4
+ const minutes = Math.floor((seconds % 3_600) / 60);
5
+ const remainder = seconds % 60;
6
+ return [hours ? `${hours}h` : '', minutes ? `${minutes}m` : '', remainder ? `${remainder}s` : '']
7
+ .filter(Boolean).join(' ') || '0s';
8
+ };
9
+ const count = (value, singular, plural = `${singular}s`) => `${value} ${value === 1 ? singular : plural}`;
10
+ const joinCounts = (parts) => parts.length < 2
11
+ ? parts[0] ?? ''
12
+ : `${parts.slice(0, -1).join(', ')} and ${parts.at(-1)}`;
13
+ export const ownerNotices = {
14
+ receivedStarted: () => 'ℹ️ Message received. The agent has started working on this request now. '
15
+ + 'The response will arrive in this channel when ready.',
16
+ receivedQueued: (queuedBehind) => `ℹ️ Message received. The agent is finishing ${queuedBehind} earlier request(s) first; `
17
+ + 'this request will start as soon as they complete. '
18
+ + 'The response will arrive in this channel when ready.',
19
+ receivedInterrupting: () => "ℹ️ Message received. The agent's previous task was interrupted to prioritize "
20
+ + 'this request, and it is now working on a response. '
21
+ + 'The response will arrive in this channel when ready.',
22
+ status: (role, snapshot) => `📊 ${role} status: ${snapshot.readiness}; session is ${snapshot.alive ? 'online' : 'offline'}.`,
23
+ interrupted: (role) => `🛑 Interrupt sent to ${role}'s active turn.`,
24
+ interruptFailed: (role) => `⚠️ Could not interrupt ${role}'s active turn.`,
25
+ attachmentRejected: (reason) => `⚠️ Attachment rejected: ${reason}.`,
26
+ attachmentFailed: () => '⚠️ Could not securely retrieve or admit this attachment request.',
27
+ deliveryFailed: (role) => `⚠️ Could not deliver this request to ${role}.`,
28
+ progress: (elapsedMs, phase, started, completed, activityUpdates = 0) => {
29
+ const counts = [];
30
+ if (started)
31
+ counts.push(`${count(started, 'tool action')} started`);
32
+ if (completed)
33
+ counts.push(`${count(completed, 'tool action')} completed`);
34
+ if (activityUpdates)
35
+ counts.push(`${count(activityUpdates, counts.length ? 'additional activity update' : 'activity update')} observed`);
36
+ const activity = counts.length
37
+ ? `${joinCounts(counts)} since the last update.`
38
+ : 'no new reportable activity since the last update.';
39
+ return `⏳ Working for ${duration(elapsedMs)} · ${phase} · ${activity}`;
40
+ },
41
+ authoredUpdate: (phase, message) => {
42
+ switch (phase) {
43
+ case 'working': return `🔄 Update: ${message}`;
44
+ case 'approval': return `🔐 Approval needed: ${message}`;
45
+ case 'blocked': return `🚧 Blocked: ${message}`;
46
+ }
47
+ },
48
+ taskReport: (phase, message) => {
49
+ switch (phase) {
50
+ case 'progress': return `🔄 Follow-up: ${message}`;
51
+ case 'done': return `✅ Follow-up complete: ${message}`;
52
+ case 'blocked': return `🚧 Follow-up blocked: ${message}`;
53
+ }
54
+ },
55
+ completedWithoutText: () => '✅ Request completed, but the agent returned no text.',
56
+ terminal: (outcome) => {
57
+ switch (outcome) {
58
+ case 'completed': return '✅ Request completed.';
59
+ case 'cancelled': return '🛑 Request was cancelled before completion.';
60
+ case 'refused': return '⚠️ The agent declined this request.';
61
+ case 'failed': return '⚠️ Request failed before completion.';
62
+ case 'inconclusive': return '⚠️ Request ended without a confirmed completion.';
63
+ }
64
+ },
65
+ chunk: (part, total) => `ℹ️ Response part ${part} of ${total}:\n`,
66
+ };
@@ -0,0 +1,44 @@
1
+ /** Durable bounded dedupe containing wire IDs only — never message or reply plaintext. */
2
+ export declare class OwnerChannelState {
3
+ private readonly path;
4
+ private readonly limit;
5
+ private handled;
6
+ private readonly seen;
7
+ constructor(path: string, limit?: number);
8
+ has(wireId: string): boolean;
9
+ remember(wireId: string): void;
10
+ }
11
+ export type OwnerSource = 'baseline' | 'dynamic';
12
+ export interface OwnerEntry {
13
+ cid: string;
14
+ source: OwnerSource;
15
+ effective: boolean;
16
+ }
17
+ /**
18
+ * Configured owners remain the declared baseline. This durable overlay adds
19
+ * owners or revokes either source without rewriting fleet.yaml. A corrupt
20
+ * overlay authorizes nobody and refuses mutation until the operator repairs or
21
+ * removes the bad file; silently falling back could resurrect a revoked owner.
22
+ */
23
+ export declare class OwnerAuthorizationState {
24
+ private readonly path;
25
+ private readonly baseline;
26
+ private added;
27
+ private revoked;
28
+ private audit;
29
+ private corruptReason?;
30
+ constructor(path: string, baseline: string[]);
31
+ integrity(): {
32
+ ok: boolean;
33
+ error?: string;
34
+ };
35
+ effective(): Set<string>;
36
+ entries(): OwnerEntry[];
37
+ authorize(cid: string): OwnerEntry;
38
+ revoke(cid: string): OwnerEntry;
39
+ private assertHealthy;
40
+ private record;
41
+ private snapshot;
42
+ private restore;
43
+ private persist;
44
+ }
@@ -0,0 +1,184 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { replaceFileAtomically } from '../atomic-file.js';
4
+ /** Durable bounded dedupe containing wire IDs only — never message or reply plaintext. */
5
+ export class OwnerChannelState {
6
+ path;
7
+ limit;
8
+ handled = [];
9
+ seen = new Set();
10
+ constructor(path, limit = 5_000) {
11
+ this.path = path;
12
+ this.limit = limit;
13
+ try {
14
+ if (!existsSync(path))
15
+ return;
16
+ const state = JSON.parse(readFileSync(path, 'utf8'));
17
+ if (state.version !== 1 || !Array.isArray(state.handled))
18
+ return;
19
+ this.handled = state.handled.filter(id => typeof id === 'string').slice(-limit);
20
+ for (const id of this.handled)
21
+ this.seen.add(id);
22
+ }
23
+ catch { /* a corrupt cache safely degrades to at-least-once delivery */ }
24
+ }
25
+ has(wireId) { return this.seen.has(wireId); }
26
+ remember(wireId) {
27
+ if (this.seen.has(wireId))
28
+ return;
29
+ this.handled.push(wireId);
30
+ this.seen.add(wireId);
31
+ while (this.handled.length > this.limit)
32
+ this.seen.delete(this.handled.shift());
33
+ mkdirSync(dirname(this.path), { recursive: true });
34
+ const tmp = `${this.path}.tmp-${process.pid}`;
35
+ writeFileSync(tmp, JSON.stringify({ version: 1, handled: this.handled }) + '\n', { mode: 0o600 });
36
+ renameSync(tmp, this.path);
37
+ }
38
+ }
39
+ const MAX_OVERLAY_CIDS = 1_000;
40
+ const MAX_AUDIT_ENTRIES = 500;
41
+ /**
42
+ * Configured owners remain the declared baseline. This durable overlay adds
43
+ * owners or revokes either source without rewriting fleet.yaml. A corrupt
44
+ * overlay authorizes nobody and refuses mutation until the operator repairs or
45
+ * removes the bad file; silently falling back could resurrect a revoked owner.
46
+ */
47
+ export class OwnerAuthorizationState {
48
+ path;
49
+ baseline;
50
+ added = new Set();
51
+ revoked = new Set();
52
+ audit = [];
53
+ corruptReason;
54
+ constructor(path, baseline) {
55
+ this.path = path;
56
+ this.baseline = new Set(baseline);
57
+ if (!existsSync(path))
58
+ return;
59
+ try {
60
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
61
+ if (raw.version !== 1 || !Array.isArray(raw.added) || !Array.isArray(raw.revoked)
62
+ || !Array.isArray(raw.audit))
63
+ throw new Error('unsupported or incomplete state');
64
+ const validCid = (cid) => typeof cid === 'string' && /^[A-Fa-f0-9]{64}$/.test(cid);
65
+ if (!raw.added.every(validCid) || !raw.revoked.every(validCid)
66
+ || raw.added.length + raw.revoked.length > MAX_OVERLAY_CIDS
67
+ || raw.audit.length > MAX_AUDIT_ENTRIES
68
+ || raw.audit.some(entry => !entry || typeof entry !== 'object'
69
+ || !validCid(entry.cid)
70
+ || !['authorize', 'revoke'].includes(entry.action)
71
+ || typeof entry.at !== 'string'))
72
+ throw new Error('invalid or unbounded state');
73
+ this.added = new Set(raw.added);
74
+ this.revoked = new Set(raw.revoked);
75
+ this.audit = raw.audit;
76
+ if (this.added.size !== raw.added.length || this.revoked.size !== raw.revoked.length)
77
+ throw new Error('duplicate overlay CID');
78
+ if ([...this.added].some(cid => this.revoked.has(cid)))
79
+ throw new Error('CID appears in both added and revoked overlays');
80
+ chmodSync(path, 0o600);
81
+ }
82
+ catch {
83
+ this.corruptReason = 'invalid persisted authorization overlay';
84
+ this.added.clear();
85
+ this.revoked.clear();
86
+ this.audit = [];
87
+ try {
88
+ chmodSync(path, 0o600);
89
+ }
90
+ catch { /* still fail closed */ }
91
+ }
92
+ }
93
+ integrity() {
94
+ return this.corruptReason ? { ok: false, error: this.corruptReason } : { ok: true };
95
+ }
96
+ effective() {
97
+ if (this.corruptReason)
98
+ return new Set();
99
+ const effective = new Set([...this.baseline, ...this.added]);
100
+ for (const cid of this.revoked)
101
+ effective.delete(cid);
102
+ return effective;
103
+ }
104
+ entries() {
105
+ const effective = this.effective();
106
+ return [...new Set([...this.baseline, ...this.added, ...this.revoked])]
107
+ .sort().map(cid => ({
108
+ cid,
109
+ source: this.baseline.has(cid) ? 'baseline' : 'dynamic',
110
+ effective: effective.has(cid),
111
+ }));
112
+ }
113
+ authorize(cid) {
114
+ this.assertHealthy();
115
+ if (this.effective().has(cid))
116
+ throw new Error(`owner '${cid}' is already authorized`);
117
+ const rollback = this.snapshot();
118
+ if (this.baseline.has(cid))
119
+ this.revoked.delete(cid);
120
+ else {
121
+ if (this.added.size + this.revoked.size >= MAX_OVERLAY_CIDS)
122
+ throw new Error(`owner authorization overlay is limited to ${MAX_OVERLAY_CIDS} CIDs`);
123
+ this.added.add(cid);
124
+ this.revoked.delete(cid);
125
+ }
126
+ this.record('authorize', cid);
127
+ try {
128
+ this.persist();
129
+ }
130
+ catch (error) {
131
+ this.restore(rollback);
132
+ throw error;
133
+ }
134
+ return { cid, source: this.baseline.has(cid) ? 'baseline' : 'dynamic', effective: true };
135
+ }
136
+ revoke(cid) {
137
+ this.assertHealthy();
138
+ const effective = this.effective();
139
+ if (!effective.has(cid))
140
+ throw new Error(`owner '${cid}' is not authorized`);
141
+ if (effective.size === 1)
142
+ throw new Error('refusing to revoke the last effective owner');
143
+ const rollback = this.snapshot();
144
+ if (this.baseline.has(cid))
145
+ this.revoked.add(cid);
146
+ else
147
+ this.added.delete(cid);
148
+ this.record('revoke', cid);
149
+ try {
150
+ this.persist();
151
+ }
152
+ catch (error) {
153
+ this.restore(rollback);
154
+ throw error;
155
+ }
156
+ return { cid, source: this.baseline.has(cid) ? 'baseline' : 'dynamic', effective: false };
157
+ }
158
+ assertHealthy() {
159
+ if (this.corruptReason)
160
+ throw new Error(`owner authorization state is corrupt; refusing mutation: ${this.corruptReason}`);
161
+ }
162
+ record(action, cid) {
163
+ this.audit.push({ at: new Date().toISOString(), action, cid });
164
+ this.audit = this.audit.slice(-MAX_AUDIT_ENTRIES);
165
+ }
166
+ snapshot() {
167
+ return { added: new Set(this.added), revoked: new Set(this.revoked), audit: [...this.audit] };
168
+ }
169
+ restore(snapshot) {
170
+ this.added = snapshot.added;
171
+ this.revoked = snapshot.revoked;
172
+ this.audit = snapshot.audit;
173
+ }
174
+ persist() {
175
+ const state = {
176
+ version: 1,
177
+ added: [...this.added].sort(),
178
+ revoked: [...this.revoked].sort(),
179
+ audit: this.audit,
180
+ };
181
+ replaceFileAtomically(this.path, JSON.stringify(state) + '\n', 0o600);
182
+ chmodSync(this.path, 0o600);
183
+ }
184
+ }
@@ -0,0 +1,62 @@
1
+ export type OwnerTaskPhase = 'progress' | 'done' | 'blocked';
2
+ export type OwnerTaskTerminalState = 'closed' | 'expired' | 'revoked';
3
+ export interface OwnerTaskRoute {
4
+ requestId: string;
5
+ contact: string;
6
+ wireId: string;
7
+ }
8
+ interface PendingReport {
9
+ digest: string;
10
+ phase: OwnerTaskPhase;
11
+ chars: number;
12
+ bytes: number;
13
+ }
14
+ export interface OwnerTaskRecord extends OwnerTaskRoute {
15
+ id: string;
16
+ createdAt: number;
17
+ expiresAt: number;
18
+ status: 'open' | 'sending' | 'uncertain';
19
+ sequence: number;
20
+ reportCount: number;
21
+ lastReportAt?: number;
22
+ digests: string[];
23
+ pending?: PendingReport;
24
+ }
25
+ export declare const OWNER_TASK_TTL_MS: number;
26
+ export declare const OWNER_TASK_MAX_OPEN = 32;
27
+ export declare const OWNER_TASK_MAX_PER_OWNER = 8;
28
+ export declare const OWNER_TASK_MAX_REPORTS = 20;
29
+ export declare const OWNER_TASK_REPORT_MIN_INTERVAL_MS = 5000;
30
+ /**
31
+ * Durable proactive-task routing. It deliberately stores no report body: only
32
+ * the authenticated route, bounded hashes/counters, and delivery state.
33
+ */
34
+ export declare class OwnerTaskState {
35
+ private readonly path;
36
+ private tasks;
37
+ private tombstones;
38
+ private corruptReason?;
39
+ constructor(path: string);
40
+ integrity(): {
41
+ ok: boolean;
42
+ error?: string;
43
+ };
44
+ open(route: OwnerTaskRoute, now?: number): OwnerTaskRecord;
45
+ route(taskId: string, now?: number): OwnerTaskRecord;
46
+ beginReport(taskId: string, phase: OwnerTaskPhase, digest: string, chars: number, bytes: number, now?: number): OwnerTaskRecord;
47
+ delivered(taskId: string, digest: string, terminal: boolean, now?: number): number;
48
+ uncertain(taskId: string, digest: string): void;
49
+ revoke(contact: string, now?: number): number;
50
+ cleanup(now?: number, effectiveOwners?: Set<string>): number;
51
+ private findOpen;
52
+ private remove;
53
+ private mutate;
54
+ private persist;
55
+ private assertHealthy;
56
+ private validTask;
57
+ private validPending;
58
+ private validTombstone;
59
+ }
60
+ export declare const ownerTaskDigest: (phase: OwnerTaskPhase, message: string) => string;
61
+ export declare const ownerTaskAuditId: (value: string) => string;
62
+ export {};