borgmcp 5.4.0 → 5.5.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 (72) hide show
  1. package/README.md +12 -0
  2. package/dist/assimilate-cmd.d.ts +8 -1
  3. package/dist/assimilate-cmd.d.ts.map +1 -1
  4. package/dist/assimilate-cmd.js +54 -21
  5. package/dist/assimilate-cmd.js.map +1 -1
  6. package/dist/claude.d.ts.map +1 -1
  7. package/dist/claude.js +22 -0
  8. package/dist/claude.js.map +1 -1
  9. package/dist/cli-help.d.ts +1 -0
  10. package/dist/cli-help.d.ts.map +1 -1
  11. package/dist/cli-help.js +42 -0
  12. package/dist/cli-help.js.map +1 -1
  13. package/dist/docs-sections.d.ts.map +1 -1
  14. package/dist/docs-sections.js +8 -0
  15. package/dist/docs-sections.js.map +1 -1
  16. package/dist/local-server-cursor.d.ts +1 -1
  17. package/dist/local-server-cursor.d.ts.map +1 -1
  18. package/dist/local-server-cursor.js +14 -4
  19. package/dist/local-server-cursor.js.map +1 -1
  20. package/dist/remote-client.d.ts +14 -0
  21. package/dist/remote-client.d.ts.map +1 -1
  22. package/dist/remote-client.js +30 -14
  23. package/dist/remote-client.js.map +1 -1
  24. package/dist/representative-cmd.d.ts +87 -0
  25. package/dist/representative-cmd.d.ts.map +1 -0
  26. package/dist/representative-cmd.js +286 -0
  27. package/dist/representative-cmd.js.map +1 -0
  28. package/dist/representative-core.d.ts +197 -0
  29. package/dist/representative-core.d.ts.map +1 -0
  30. package/dist/representative-core.js +493 -0
  31. package/dist/representative-core.js.map +1 -0
  32. package/dist/representative-mcp.d.ts +30 -0
  33. package/dist/representative-mcp.d.ts.map +1 -0
  34. package/dist/representative-mcp.js +182 -0
  35. package/dist/representative-mcp.js.map +1 -0
  36. package/dist/representative-owner.d.ts +10 -0
  37. package/dist/representative-owner.d.ts.map +1 -0
  38. package/dist/representative-owner.js +107 -0
  39. package/dist/representative-owner.js.map +1 -0
  40. package/dist/representative-store.d.ts +61 -0
  41. package/dist/representative-store.d.ts.map +1 -0
  42. package/dist/representative-store.js +158 -0
  43. package/dist/representative-store.js.map +1 -0
  44. package/dist/seat-store.d.ts +13 -0
  45. package/dist/seat-store.d.ts.map +1 -1
  46. package/dist/seat-store.js +55 -10
  47. package/dist/seat-store.js.map +1 -1
  48. package/dist/stream-owner.d.ts +10 -0
  49. package/dist/stream-owner.d.ts.map +1 -1
  50. package/dist/stream-owner.js +98 -19
  51. package/dist/stream-owner.js.map +1 -1
  52. package/dist/unknown-subcommand.d.ts +1 -1
  53. package/dist/unknown-subcommand.d.ts.map +1 -1
  54. package/dist/unknown-subcommand.js +1 -0
  55. package/dist/unknown-subcommand.js.map +1 -1
  56. package/docs/HUMAN_REPRESENTATIVE.md +269 -0
  57. package/docs/RELEASING.md +2 -2
  58. package/package.json +2 -2
  59. package/src/assimilate-cmd.ts +73 -22
  60. package/src/claude.ts +22 -0
  61. package/src/cli-help.ts +45 -0
  62. package/src/docs-sections.ts +8 -0
  63. package/src/local-server-cursor.ts +11 -3
  64. package/src/remote-client.ts +45 -12
  65. package/src/representative-cmd.ts +363 -0
  66. package/src/representative-core.ts +699 -0
  67. package/src/representative-mcp.ts +209 -0
  68. package/src/representative-owner.ts +105 -0
  69. package/src/representative-store.ts +208 -0
  70. package/src/seat-store.ts +61 -10
  71. package/src/stream-owner.ts +96 -19
  72. package/src/unknown-subcommand.ts +1 -0
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Restricted stdio MCP facade for the human representative.
3
+ *
4
+ * A Borg server speaks pinned-TLS HTTPS, not MCP, so a generic MCP host reaches
5
+ * it through this local stdio process. The surface is four tools: status, send
6
+ * (to the ONE bound Coordinator), read (that Coordinator's replies) and ack.
7
+ * There is deliberately no log/broadcast/dispatch, roster-management, grant,
8
+ * evict, release, regen or server-lifecycle tool, and no dispatcher escape hatch.
9
+ *
10
+ * The connection context is resolved per call and injected, so the same facade
11
+ * runs over the real seat-scoped backend or a controlled test backend.
12
+ */
13
+ import { ErrorCode } from 'borgmcp-shared/protocol';
14
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
15
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
16
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
17
+ import { REPRESENTATIVE_DELIVERY_NOTE, REPRESENTATIVE_MESSAGE_LIMIT_BYTES, RepresentativeError, ackRepresentativeReply, readRepresentativeReplies, representativeStatus, sendRepresentativeMessage, } from './representative-core.js';
18
+ import { RepresentativeStoreError } from './representative-store.js';
19
+ import { createRepresentativeOwner } from './representative-owner.js';
20
+ export const REPRESENTATIVE_TOOL_NAMES = [
21
+ 'borg_representative-status',
22
+ 'borg_representative-send',
23
+ 'borg_representative-read',
24
+ 'borg_representative-ack',
25
+ ];
26
+ export const REPRESENTATIVE_INSTRUCTIONS = 'You are connected as the human representative of one Borg cube: an automated delegate that relays the ' +
27
+ 'human\'s requests, questions and decisions to ONE bound Coordinator drone and reads its replies. You are not the ' +
28
+ 'Coordinator and not the human: never plan or dispatch work for other drones — the Coordinator does that. ' +
29
+ 'Mark content user_authorized ONLY when the human explicitly said it; everything you originate is model_advice. ' +
30
+ 'A relayed decision authorizes only its own text, never broader approval. ' +
31
+ REPRESENTATIVE_DELIVERY_NOTE;
32
+ const TOOLS = [
33
+ {
34
+ name: 'borg_representative-status',
35
+ description: 'Show which cube, representative drone and Coordinator this connection is bound to, whether the live cube still matches, ' +
36
+ 'any unresolved (ambiguous) sends, process ownership and the delivery limits. Read-only; never takes ownership.',
37
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false },
38
+ },
39
+ {
40
+ name: 'borg_representative-send',
41
+ description: 'Relay ONE human request, question or decision to the bound Coordinator. The recipient is fixed; workers and broadcast ' +
42
+ 'are not addressable. Returns a request_id. If the outcome is "ambiguous" (the result names the cause), retry only ' +
43
+ 'with the SAME request_id and identical content (server-deduplicated); never re-send under a new id. A ' +
44
+ 'SEND_REJECTED error names the refusal and its recovery. Do not issue overlapping sends of the same content.',
45
+ inputSchema: {
46
+ type: 'object',
47
+ properties: {
48
+ kind: { type: 'string', enum: ['request', 'question', 'decision'] },
49
+ authorization: {
50
+ type: 'string',
51
+ enum: ['user_authorized', 'model_advice'],
52
+ description: 'user_authorized: the human explicitly said/approved this exact content. model_advice: your own suggestion. ' +
53
+ 'A decision must be user_authorized.',
54
+ },
55
+ message: { type: 'string', description: `Plain text, at most ${REPRESENTATIVE_MESSAGE_LIMIT_BYTES} bytes.` },
56
+ request_id: {
57
+ type: 'string',
58
+ description: 'Omit for a new request. To retry, pass the request_id returned earlier with identical content.',
59
+ },
60
+ },
61
+ required: ['kind', 'authorization', 'message'],
62
+ additionalProperties: false,
63
+ },
64
+ },
65
+ {
66
+ name: 'borg_representative-read',
67
+ description: 'Read unread replies from the bound Coordinator addressed to this representative. Replies arrive only when this tool ' +
68
+ 'is called; there is no background wake. Each call DRAINS the whole fetched unread page for this drone — returned ' +
69
+ 'replies and ignored entries alike (other drones\' entries are counted, never returned) — so they will not appear ' +
70
+ 'unread again: persist the result, then route each reply by in_reply_to to its conversation or hold it for the human. If the host stops before persisting, the reply ' +
71
+ 'is no longer in the unread view.',
72
+ inputSchema: {
73
+ type: 'object',
74
+ properties: {
75
+ include_broadcast: { type: 'boolean', description: 'Also return the Coordinator\'s cube-wide broadcasts (marked as such).' },
76
+ limit: {
77
+ type: 'integer', minimum: 1, maximum: 200,
78
+ description: 'Page-size hint, not a hard cap: a large unread backlog may return (and drain) more entries.',
79
+ },
80
+ },
81
+ additionalProperties: false,
82
+ },
83
+ },
84
+ {
85
+ name: 'borg_representative-ack',
86
+ description: 'Signal to the Coordinator that one of its direct replies (entry_id from borg_representative-read) was received. ' +
87
+ 'It is only that signal: it does not make delivery reliable, does not move or restore the unread cursor, and is ' +
88
+ 'not needed for reading.',
89
+ inputSchema: {
90
+ type: 'object',
91
+ properties: { entry_id: { type: 'string' } },
92
+ required: ['entry_id'],
93
+ additionalProperties: false,
94
+ },
95
+ },
96
+ ];
97
+ function errorBody(error) {
98
+ if (error instanceof RepresentativeError || error instanceof RepresentativeStoreError) {
99
+ const details = error instanceof RepresentativeError ? error.details : undefined;
100
+ return { error: { code: error.code, message: error.message, ...(details ? { details } : {}) } };
101
+ }
102
+ const code = error?.code;
103
+ return {
104
+ error: {
105
+ code: typeof code === 'string' ? code : 'BACKEND_ERROR',
106
+ message: error instanceof Error ? error.message : 'Unknown error',
107
+ },
108
+ };
109
+ }
110
+ const toolResult = (body, isError = false) => ({
111
+ content: [{ type: 'text', text: JSON.stringify(body, null, 2) }],
112
+ ...(isError ? { isError: true } : {}),
113
+ });
114
+ export async function serveRepresentativeMcp(options) {
115
+ const owner = createRepresentativeOwner(options.heartbeatIntervalMs);
116
+ const server = new Server({ name: 'borg-human-representative', version: options.version }, { capabilities: { tools: {} }, instructions: REPRESENTATIVE_INSTRUCTIONS });
117
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS.map((tool) => ({ ...tool })) }));
118
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
119
+ const name = request.params.name;
120
+ const args = request.params.arguments ?? {};
121
+ try {
122
+ if (!REPRESENTATIVE_TOOL_NAMES.includes(name)) {
123
+ throw new RepresentativeError(ErrorCode.INVALID_INPUT, `Unknown tool ${JSON.stringify(name)}; this connection exposes only the representative tools.`);
124
+ }
125
+ const ctx = await options.context();
126
+ if (name === 'borg_representative-status') {
127
+ return toolResult({ ...await representativeStatus(ctx), ownership: await owner.snapshot(ctx.binding) });
128
+ }
129
+ await owner.ensure(ctx.binding);
130
+ // Recheck at each network boundary, not just at tool dispatch: a process
131
+ // may have paused or lost its lease while awaiting live verification.
132
+ const backend = ctx.backend;
133
+ const guarded = { ...ctx, backend: Object.fromEntries(['whoami', 'roster', 'append', 'readUnread', 'readEntry', 'ack'].map((key) => [key, async (...args) => {
134
+ await owner.ensure(ctx.binding);
135
+ if (key === 'readUnread')
136
+ args[1] = () => owner.ensure(ctx.binding);
137
+ const result = await backend[key].apply(backend, args);
138
+ await owner.ensure(ctx.binding);
139
+ return result;
140
+ }])) };
141
+ switch (name) {
142
+ case 'borg_representative-send': {
143
+ const sent = await sendRepresentativeMessage(guarded, args);
144
+ return toolResult(sent, sent.outcome === 'ambiguous');
145
+ }
146
+ case 'borg_representative-read':
147
+ return toolResult(await readRepresentativeReplies(guarded, args));
148
+ default:
149
+ return toolResult(await ackRepresentativeReply(guarded, args));
150
+ }
151
+ }
152
+ catch (error) {
153
+ return toolResult(errorBody(error), true);
154
+ }
155
+ });
156
+ const transport = new StdioServerTransport(options.stdin, options.stdout);
157
+ let finish;
158
+ const closed = new Promise((resolve) => { finish = resolve; });
159
+ const shutdown = async () => {
160
+ process.removeListener('SIGTERM', signalClose);
161
+ process.removeListener('SIGINT', signalClose);
162
+ try {
163
+ await owner.close();
164
+ }
165
+ finally {
166
+ finish();
167
+ }
168
+ };
169
+ const signalClose = () => { void server.close(); };
170
+ server.onclose = () => { void shutdown().catch(() => { }); };
171
+ process.once('SIGTERM', signalClose);
172
+ process.once('SIGINT', signalClose);
173
+ try {
174
+ await server.connect(transport);
175
+ }
176
+ catch (error) {
177
+ await shutdown();
178
+ throw error;
179
+ }
180
+ return { close: async () => { await server.close(); await closed; }, closed };
181
+ }
182
+ //# sourceMappingURL=representative-mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"representative-mcp.js","sourceRoot":"","sources":["../src/representative-mcp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type StreamOwnerDeps, type StreamOwnershipSnapshot } from './stream-owner.js';
2
+ import type { RepresentativeBinding } from './representative-store.js';
3
+ export declare function representativeOwnerDeps(binding: RepresentativeBinding): StreamOwnerDeps;
4
+ export declare function representativeOwnership(binding: RepresentativeBinding): Promise<StreamOwnershipSnapshot>;
5
+ export declare function createRepresentativeOwner(heartbeatIntervalMs?: number): {
6
+ snapshot: (binding: RepresentativeBinding) => Promise<StreamOwnershipSnapshot>;
7
+ ensure: (binding: RepresentativeBinding) => Promise<undefined>;
8
+ close: () => Promise<void>;
9
+ };
10
+ //# sourceMappingURL=representative-owner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"representative-owner.d.ts","sourceRoot":"","sources":["../src/representative-owner.ts"],"names":[],"mappings":"AAIA,OAAO,EAEa,KAAK,eAAe,EAAE,KAAK,uBAAuB,EACrE,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAEvE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,eAAe,CASvF;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAExG;AAoBD,wBAAgB,yBAAyB,CAAC,mBAAmB,SAAS;wBAezC,qBAAqB;sBAsB5B,qBAAqB;;EAuB1C"}
@@ -0,0 +1,107 @@
1
+ /** Lifecycle adapter for the existing stream lease; no separate lock protocol. */
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { join } from 'node:path';
4
+ import { borgConfigRoot, borgHomeRoot } from './private-root.js';
5
+ import { acquireStreamLease, readOwnershipSnapshot, STREAM_OWNER_STALE_MS, } from './stream-owner.js';
6
+ import { RepresentativeError } from './representative-core.js';
7
+ export function representativeOwnerDeps(binding) {
8
+ const authority = createHash('sha256').update(JSON.stringify([binding.origin, binding.trustIdentity])).digest('hex');
9
+ return {
10
+ // Separate from stream-locks, which borg_stream-status inspects. One lease
11
+ // across all worktrees using this authority/cube/drone, never one per host.
12
+ locksDir: join(borgConfigRoot(), 'representative-host-locks', authority),
13
+ privateRoot: { root: borgConfigRoot(), boundary: borgHomeRoot() },
14
+ worktree: binding.worktree, droneLabel: binding.representativeLabel, cubeName: binding.cubeName,
15
+ };
16
+ }
17
+ export function representativeOwnership(binding) {
18
+ return privateStorage(() => readOwnershipSnapshot(binding.cubeId, binding.representativeDroneId, representativeOwnerDeps(binding)));
19
+ }
20
+ async function privateStorage(operation) {
21
+ try {
22
+ return await operation();
23
+ }
24
+ catch (error) {
25
+ if (error instanceof RepresentativeError)
26
+ throw error;
27
+ const message = error instanceof Error ? error.message : 'invalid private state';
28
+ // Only this exact directory-mode refusal permits manual permission recovery.
29
+ // Symlink, ownership and other failures must not suggest chmod through a path.
30
+ const looseDirectory = /^Borg credential store root (.+) has insecure permissions; expected 0700$/s.exec(message);
31
+ const detail = looseDirectory
32
+ ? `directory ${looseDirectory[1]} has insecure permissions; expected 0700. ` +
33
+ 'Check that the named path is a real directory you own and not a symlink, then set it to 0700 and retry. ' +
34
+ 'Restart a process that had already lost ownership.'
35
+ : message.replace(/^Borg credential store /, '');
36
+ throw new RepresentativeError('REPRESENTATIVE_OWNERSHIP_REQUIRED', 'Representative lease storage refused: ' + detail);
37
+ }
38
+ }
39
+ export function createRepresentativeOwner(heartbeatIntervalMs = 20_000) {
40
+ let lease = null;
41
+ let selected;
42
+ let deps;
43
+ let lost = false, closed = false;
44
+ let timer;
45
+ let tail = Promise.resolve();
46
+ const processNonce = randomUUID();
47
+ // Serialize lease mutations, including heartbeats, but NOT tool execution:
48
+ // overlapping sends still use the existing atomic request reservation.
49
+ const serial = (op) => {
50
+ const result = tail.then(op);
51
+ tail = result.catch(() => { });
52
+ return result;
53
+ };
54
+ const snapshot = (binding) => privateStorage(() => readOwnershipSnapshot(binding.cubeId, binding.representativeDroneId, { ...representativeOwnerDeps(binding), processNonce }));
55
+ const refuse = async (binding) => {
56
+ const owner = await snapshot(binding);
57
+ throw new RepresentativeError('REPRESENTATIVE_OWNERSHIP_REQUIRED', `This representative is owned by another MCP process (pid ${owner.pid ?? 'unknown'}, started ${owner.startedAt ?? 'unknown'}), ` +
58
+ 'or this process lost its lease. Use the owning host; after it exits or its lease expires, retry from the intended host. ' +
59
+ 'Restart a process that lost ownership before using it again. Status remains available.', { owner });
60
+ };
61
+ const refresh = async () => {
62
+ try {
63
+ if (!lease || !await lease.refresh())
64
+ lost = true;
65
+ }
66
+ catch {
67
+ lost = true;
68
+ }
69
+ if (lost && timer) {
70
+ clearInterval(timer);
71
+ timer = undefined;
72
+ }
73
+ };
74
+ return {
75
+ snapshot,
76
+ ensure: (binding) => serial(async () => {
77
+ if (selected && (selected.origin !== binding.origin || selected.trustIdentity !== binding.trustIdentity ||
78
+ selected.cubeId !== binding.cubeId || selected.representativeDroneId !== binding.representativeDroneId))
79
+ lost = true;
80
+ if (closed || lost)
81
+ return refuse(binding);
82
+ if (lease) {
83
+ await refresh();
84
+ if (lost)
85
+ return refuse(binding);
86
+ return;
87
+ }
88
+ deps = { ...representativeOwnerDeps(binding), processNonce };
89
+ lease = await privateStorage(() => acquireStreamLease(binding.cubeId, binding.representativeDroneId, STREAM_OWNER_STALE_MS, deps));
90
+ if (!lease)
91
+ return refuse(binding);
92
+ selected = binding;
93
+ timer = setInterval(() => { void serial(refresh); }, heartbeatIntervalMs);
94
+ timer.unref();
95
+ }),
96
+ close: () => serial(async () => {
97
+ closed = true;
98
+ if (timer) {
99
+ clearInterval(timer);
100
+ timer = undefined;
101
+ }
102
+ await lease?.release();
103
+ lease = null;
104
+ }),
105
+ };
106
+ }
107
+ //# sourceMappingURL=representative-owner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"representative-owner.js","sourceRoot":"","sources":["../src/representative-owner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Private on-disk state for the human representative connection.
3
+ *
4
+ * One 0600 file under the Borg config root holds, per representative worktree:
5
+ * - the BINDING: the one explicit cube + Coordinator drone this worktree's
6
+ * dedicated seat may talk to. Changing it requires an explicit operator rebind.
7
+ * - the REQUEST LEDGER: one record per sent request id, so a retry never becomes
8
+ * a second message and an ambiguous send survives a reconnect.
9
+ *
10
+ * The file never holds a bearer (the seat store owns credentials) and never
11
+ * holds message text (only a payload digest).
12
+ */
13
+ export declare function isRepresentativeUuid(value: unknown): value is string;
14
+ export interface RepresentativeBinding {
15
+ /** Canonical worktree holding the dedicated representative seat. */
16
+ worktree: string;
17
+ origin: string;
18
+ trustIdentity: string;
19
+ cubeId: string;
20
+ cubeName: string;
21
+ representativeDroneId: string;
22
+ representativeLabel: string;
23
+ representativeRoleName: string;
24
+ coordinatorDroneId: string;
25
+ coordinatorLabel: string;
26
+ coordinatorRoleName: string;
27
+ repositoryOrigin?: string;
28
+ boundAt: string;
29
+ }
30
+ export declare function representativeRecoveryCommand(binding: RepresentativeBinding): string;
31
+ export type RepresentativeRequestState = 'pending' | 'ambiguous' | 'sent' | 'rejected';
32
+ export interface RepresentativeRequestRecord {
33
+ /** Stable request identity; also the protocol `post_id` idempotency key. */
34
+ requestId: string;
35
+ /** sha256 of kind, authorization, message, cube and Coordinator — never the text. */
36
+ payloadDigest: string;
37
+ kind: string;
38
+ authorization: string;
39
+ state: RepresentativeRequestState;
40
+ entryId?: string;
41
+ /** An attempt under this id may have reached the log; a later refusal must not clear that. */
42
+ maybeStored?: boolean;
43
+ createdAt: string;
44
+ updatedAt: string;
45
+ }
46
+ export declare class RepresentativeStoreError extends Error {
47
+ readonly code: 'BINDING_CONFLICT';
48
+ constructor(code: 'BINDING_CONFLICT', message: string);
49
+ }
50
+ export interface RepresentativeStore {
51
+ getBinding(worktree: string): Promise<RepresentativeBinding | null>;
52
+ readRequests(worktree: string): Promise<RepresentativeRequestRecord[]>;
53
+ saveBinding(binding: RepresentativeBinding, options: {
54
+ rebind: boolean;
55
+ }): Promise<'created' | 'unchanged' | 'rebound'>;
56
+ /** Read-compare-write over one worktree's ledger under the store lock. */
57
+ transactRequests<T>(worktree: string, op: (records: RepresentativeRequestRecord[]) => T): Promise<T>;
58
+ }
59
+ export declare function representativeStorePath(): string;
60
+ export declare function createRepresentativeStore(storePath?: string): RepresentativeStore;
61
+ //# sourceMappingURL=representative-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"representative-store.d.ts","sourceRoot":"","sources":["../src/representative-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAQH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEpE;AAGD,MAAM,WAAW,qBAAqB;IACpC,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAEpF;AAED,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,UAAU,CAAC;AAEvF,MAAM,WAAW,2BAA2B;IAC1C,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,0BAA0B,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8FAA8F;IAC9F,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAQD,qBAAa,wBAAyB,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,kBAAkB;gBAAxB,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM;CAI/D;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IACpE,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,EAAE,CAAC,CAAC;IACvE,WAAW,CACT,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAC3B,OAAO,CAAC,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC,CAAC;IAChD,0EAA0E;IAC1E,gBAAgB,CAAC,CAAC,EAChB,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,CAAC,OAAO,EAAE,2BAA2B,EAAE,KAAK,CAAC,GAChD,OAAO,CAAC,CAAC,CAAC,CAAC;CACf;AAoED,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD;AAED,wBAAgB,yBAAyB,CAAC,SAAS,GAAE,MAAkC,GAAG,mBAAmB,CAgD5G"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Private on-disk state for the human representative connection.
3
+ *
4
+ * One 0600 file under the Borg config root holds, per representative worktree:
5
+ * - the BINDING: the one explicit cube + Coordinator drone this worktree's
6
+ * dedicated seat may talk to. Changing it requires an explicit operator rebind.
7
+ * - the REQUEST LEDGER: one record per sent request id, so a retry never becomes
8
+ * a second message and an ambiguous send survives a reconnect.
9
+ *
10
+ * The file never holds a bearer (the seat store owns credentials) and never
11
+ * holds message text (only a payload digest).
12
+ */
13
+ import { decodeUuid } from 'borgmcp-shared/protocol';
14
+ import { join } from 'node:path';
15
+ import { borgConfigRoot } from './private-root.js';
16
+ import { shellEscape } from './shell-escape.js';
17
+ import { readStoreFile, withStore } from './seat-store.js';
18
+ export function isRepresentativeUuid(value) {
19
+ try {
20
+ decodeUuid(value);
21
+ return true;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ const SETTLED_REQUEST_LIMIT = 200;
28
+ export function representativeRecoveryCommand(binding) {
29
+ return `cd ${shellEscape(binding.worktree)} && borg representative prepare --coordinator ${shellEscape(binding.coordinatorLabel)} --role ${shellEscape(binding.representativeRoleName)} --rebind`;
30
+ }
31
+ export class RepresentativeStoreError extends Error {
32
+ code;
33
+ constructor(code, message) {
34
+ super(message);
35
+ this.code = code;
36
+ this.name = 'RepresentativeStoreError';
37
+ }
38
+ }
39
+ const BINDING_STRING_FIELDS = [
40
+ 'worktree', 'origin', 'trustIdentity', 'cubeId', 'cubeName',
41
+ 'representativeDroneId', 'representativeLabel', 'representativeRoleName',
42
+ 'coordinatorDroneId', 'coordinatorLabel', 'coordinatorRoleName', 'boundAt',
43
+ ];
44
+ function validBinding(value, key) {
45
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
46
+ return false;
47
+ const binding = value;
48
+ return BINDING_STRING_FIELDS.every((field) => typeof binding[field] === 'string' && binding[field] !== '') &&
49
+ binding.worktree === key &&
50
+ isRepresentativeUuid(binding.cubeId) &&
51
+ isRepresentativeUuid(binding.representativeDroneId) &&
52
+ isRepresentativeUuid(binding.coordinatorDroneId) &&
53
+ binding.representativeDroneId !== binding.coordinatorDroneId &&
54
+ (binding.repositoryOrigin === undefined || typeof binding.repositoryOrigin === 'string');
55
+ }
56
+ function validRequest(value) {
57
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
58
+ return false;
59
+ const record = value;
60
+ return isRepresentativeUuid(String(record.requestId ?? '')) &&
61
+ typeof record.payloadDigest === 'string' &&
62
+ typeof record.kind === 'string' &&
63
+ typeof record.authorization === 'string' &&
64
+ ['pending', 'ambiguous', 'sent', 'rejected'].includes(record.state) &&
65
+ (record.entryId === undefined || typeof record.entryId === 'string') &&
66
+ (record.maybeStored === undefined || typeof record.maybeStored === 'boolean') &&
67
+ typeof record.createdAt === 'string' &&
68
+ typeof record.updatedAt === 'string';
69
+ }
70
+ function parseFile(raw) {
71
+ const parsed = JSON.parse(raw);
72
+ if (parsed === null || typeof parsed !== 'object' || parsed.version !== 1 ||
73
+ parsed.bindings === null || typeof parsed.bindings !== 'object' || Array.isArray(parsed.bindings) ||
74
+ parsed.requests === null || typeof parsed.requests !== 'object' || Array.isArray(parsed.requests))
75
+ return null;
76
+ for (const [key, binding] of Object.entries(parsed.bindings)) {
77
+ if (!validBinding(binding, key))
78
+ return null;
79
+ }
80
+ for (const records of Object.values(parsed.requests)) {
81
+ if (!Array.isArray(records) || !records.every(validRequest))
82
+ return null;
83
+ }
84
+ return parsed;
85
+ }
86
+ const emptyFile = () => ({ version: 1, bindings: {}, requests: {} });
87
+ function sameSelection(a, b) {
88
+ return a.origin === b.origin &&
89
+ a.trustIdentity === b.trustIdentity &&
90
+ a.cubeId === b.cubeId &&
91
+ a.representativeDroneId === b.representativeDroneId &&
92
+ a.coordinatorDroneId === b.coordinatorDroneId;
93
+ }
94
+ /** Settled history is bounded; unresolved (pending/ambiguous) records are never dropped. */
95
+ function pruneSettled(records) {
96
+ const settled = records.filter((record) => record.state === 'sent' || record.state === 'rejected');
97
+ if (settled.length <= SETTLED_REQUEST_LIMIT)
98
+ return records;
99
+ const drop = new Set(settled.slice(0, settled.length - SETTLED_REQUEST_LIMIT).map((record) => record.requestId));
100
+ return records.filter((record) => !drop.has(record.requestId));
101
+ }
102
+ export function representativeStorePath() {
103
+ return join(borgConfigRoot(), 'representative.json');
104
+ }
105
+ export function createRepresentativeStore(storePath = representativeStorePath()) {
106
+ // Atomic writer renames make a single read a complete snapshot, without a
107
+ // store-lock write or pruning settled history during read-only status.
108
+ const read = async () => {
109
+ const raw = await readStoreFile(storePath);
110
+ if (raw === null)
111
+ return emptyFile();
112
+ let data;
113
+ try {
114
+ data = parseFile(raw);
115
+ }
116
+ catch {
117
+ data = null;
118
+ }
119
+ if (!data)
120
+ throw new Error('Borg representative store is malformed or unsupported; refusing to read it');
121
+ return data;
122
+ };
123
+ return {
124
+ getBinding: async (worktree) => (await read()).bindings[worktree] ?? null,
125
+ readRequests: async (worktree) => (await read()).requests[worktree] ?? [],
126
+ saveBinding: (binding, options) => withStore(storePath, emptyFile, parseFile, async (txn) => {
127
+ if (!validBinding(binding, binding.worktree)) {
128
+ throw new Error('Refusing to save an invalid representative binding');
129
+ }
130
+ const existing = txn.data.bindings[binding.worktree];
131
+ if (existing && sameSelection(existing, binding))
132
+ return 'unchanged';
133
+ if (existing && !options.rebind) {
134
+ throw new RepresentativeStoreError('BINDING_CONFLICT', `This worktree is already bound to Coordinator ${existing.coordinatorLabel} in cube ${existing.cubeName}. ` +
135
+ `To confirm the new selection, run \`${representativeRecoveryCommand(binding)}\`.`);
136
+ }
137
+ txn.data.bindings[binding.worktree] = binding;
138
+ // A different selection invalidates the old ledger: its post ids belong to
139
+ // another cube/Coordinator conversation.
140
+ if (existing)
141
+ delete txn.data.requests[binding.worktree];
142
+ await txn.commit();
143
+ return existing ? 'rebound' : 'created';
144
+ }),
145
+ transactRequests: (worktree, op) => withStore(storePath, emptyFile, parseFile, async (txn) => {
146
+ const records = txn.data.requests[worktree] ?? [];
147
+ const before = JSON.stringify(records);
148
+ const result = op(records);
149
+ const next = pruneSettled(records);
150
+ if (JSON.stringify(next) !== before) {
151
+ txn.data.requests[worktree] = next;
152
+ await txn.commit();
153
+ }
154
+ return result;
155
+ }),
156
+ };
157
+ }
158
+ //# sourceMappingURL=representative-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"representative-store.js","sourceRoot":"","sources":["../src/representative-store.ts"],"names":[],"mappings":"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"}
@@ -33,7 +33,15 @@
33
33
  export interface SecureStoreOptions {
34
34
  secureRoot?: string;
35
35
  rootMode?: 'private' | 'owner-controlled';
36
+ /** Harden leaf publication for private lease records; default store behavior is unchanged. */
37
+ verifyLeafIdentity?: boolean;
38
+ /** Publish without replacing an existing leaf (lease takeover marker). */
39
+ exclusive?: boolean;
40
+ /** Read-only lease inspection must not recreate a directory moved by its owner. */
41
+ createRoot?: boolean;
36
42
  }
43
+ /** Shared directory policy; create=false validates without mutating and returns false if absent. */
44
+ export declare function assertSecureRoot(root: string, rootMode?: SecureStoreOptions['rootMode'], create?: boolean): Promise<boolean>;
37
45
  /**
38
46
  * Atomic 0600 write (checklist #1 + #2): write to a same-dir temp opened
39
47
  * O_CREAT|O_EXCL at mode 0600 (never write-then-chmod), fsync the data, then
@@ -41,6 +49,11 @@ export interface SecureStoreOptions {
41
49
  * cleans up the temp so no leftover file ever holds the secret.
42
50
  */
43
51
  export declare function atomicWrite0600(filePath: string, data: string, options?: SecureStoreOptions): Promise<void>;
52
+ /** Identity drift is retryable only when inspecting an acquisition lock. */
53
+ export declare class StoreFileIdentityChangedError extends Error {
54
+ readonly code = "STORE_FILE_IDENTITY_CHANGED";
55
+ constructor();
56
+ }
44
57
  /**
45
58
  * Read the store file, or null when it does not exist (ONLY the missing-file
46
59
  * no-op path initializes empty). When the file exists, the 0600-store + 0700-parent
@@ -1 +1 @@
1
- {"version":3,"file":"seat-store.d.ts","sourceRoot":"","sources":["../src/seat-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAiBH,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,SAAS,GAAG,kBAAkB,CAAC;CAC3C;AA+JD;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAkCD;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmDxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,kBAAuB,GACrE,OAAO,CAAC,CAAC,CAAC,CAkFZ;AAED,2DAA2D;AAC3D,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,gFAAgF;IAChF,IAAI,EAAE,CAAC,CAAC;IACR,6EAA6E;IAC7E,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,CAAC,EAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,CAAC,EACnB,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,IAAI,EAChC,EAAE,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EACpC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,CAAC,CAAC,CAgCZ"}
1
+ {"version":3,"file":"seat-store.d.ts","sourceRoot":"","sources":["../src/seat-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAiBH,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,SAAS,GAAG,kBAAkB,CAAC;IAC1C,8FAA8F;IAC9F,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mFAAmF;IACnF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAMD,oGAAoG;AACpG,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,kBAAkB,CAAC,UAAU,CAAa,EACpD,MAAM,UAAO,GACZ,OAAO,CAAC,OAAO,CAAC,CA+BlB;AA2HD;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAwDf;AAkCD,4EAA4E;AAC5E,qBAAa,6BAA8B,SAAQ,KAAK;IACtD,QAAQ,CAAC,IAAI,iCAAiC;;CAK/C;AAOD;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmDxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,kBAAuB,GACrE,OAAO,CAAC,CAAC,CAAC,CAkFZ;AAED,2DAA2D;AAC3D,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,gFAAgF;IAChF,IAAI,EAAE,CAAC,CAAC;IACR,6EAA6E;IAC7E,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,CAAC,EAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,CAAC,EACnB,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,IAAI,EAChC,EAAE,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EACpC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,CAAC,CAAC,CAgCZ"}