@cleocode/contracts 2026.5.56 → 2026.5.58

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.
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Sub-accessor interfaces for UmbrellaDataAccessor role-specific databases.
3
+ *
4
+ * Each interface provides typed access to a specific CLEO database role.
5
+ * These are returned by UmbrellaDataAccessor.getSubAccessor(role).
6
+ *
7
+ * Implementors:
8
+ * - BrainAccessor → brain.db (memory observations, semantic graph)
9
+ * - ConduitAccessor → conduit.db (project-scoped messaging)
10
+ * - NexusAccessor → nexus.db (code intelligence graph)
11
+ * - SignaldockAccessor → signaldock.db (global agent identity)
12
+ * - TelemetryAccessor → (future — telemetry collection)
13
+ *
14
+ * See DocsAccessor in docs-accessor.ts for the docs/llmtxt sub-accessor.
15
+ *
16
+ * @task T9188
17
+ * @epic T9048
18
+ * @see ADR-068 (DB Charter — per-DB write ownership)
19
+ * @see ADR-069 (Coordination Layers — Storage Layer contract)
20
+ */
21
+ /**
22
+ * Parameters for {@link BrainAccessor.observe}.
23
+ */
24
+ export interface BrainObserveParams {
25
+ /** Free-text observation content. */
26
+ text: string;
27
+ /** Human-readable title. */
28
+ title?: string;
29
+ /** Memory type / entry type. */
30
+ type?: string;
31
+ /** Source session ID linking this observation to a session. */
32
+ sourceSessionId?: string;
33
+ /** Agent identifier that produced this observation. */
34
+ agent?: string;
35
+ }
36
+ /**
37
+ * A memory hit returned by {@link BrainAccessor.find}.
38
+ */
39
+ export interface BrainMemoryHit {
40
+ /** Unique entry ID. */
41
+ readonly id: string;
42
+ /** Entry text. */
43
+ readonly text: string;
44
+ /** Entry title. */
45
+ readonly title: string | null;
46
+ /** Similarity or relevance score (0–1). */
47
+ readonly score: number;
48
+ /** Entry type. */
49
+ readonly type: string | null;
50
+ }
51
+ /**
52
+ * BrainAccessor — typed interface for brain.db (memory / observation store).
53
+ *
54
+ * Consumers depend on BrainAccessor, NOT on brain-retrieval internals.
55
+ *
56
+ * @task T9188
57
+ * @epic T9048
58
+ */
59
+ export interface BrainAccessor {
60
+ /**
61
+ * Store a new observation in brain.db.
62
+ *
63
+ * @param text - Observation text (required, non-empty).
64
+ * @param params - Additional observation metadata.
65
+ * @returns ID of the stored observation.
66
+ */
67
+ observe(text: string, params?: Omit<BrainObserveParams, 'text'>): Promise<string>;
68
+ /**
69
+ * Find memory entries matching the given query.
70
+ *
71
+ * @param query - Free-text search query.
72
+ * @param limit - Maximum results (default: 10).
73
+ * @returns Ranked memory hits.
74
+ */
75
+ find(query: string, limit?: number): Promise<BrainMemoryHit[]>;
76
+ /**
77
+ * Release resources held by this accessor.
78
+ */
79
+ close(): Promise<void>;
80
+ }
81
+ /**
82
+ * ConduitAccessor — typed interface for conduit.db (project-scoped messaging).
83
+ *
84
+ * Minimal surface for T9188; full messaging API lives in conduit-sqlite.ts.
85
+ *
86
+ * @task T9188
87
+ * @epic T9048
88
+ */
89
+ export interface ConduitAccessor {
90
+ /**
91
+ * Publish a message to a conduit topic.
92
+ *
93
+ * @param topic - Topic identifier.
94
+ * @param payload - Message payload (JSON-serializable).
95
+ */
96
+ publish(topic: string, payload: unknown): Promise<void>;
97
+ /**
98
+ * Check whether conduit.db is open and accessible.
99
+ *
100
+ * @returns True if the database is healthy.
101
+ */
102
+ ping(): Promise<boolean>;
103
+ /**
104
+ * Release resources held by this accessor.
105
+ */
106
+ close(): Promise<void>;
107
+ }
108
+ /**
109
+ * NexusAccessor — typed interface for nexus.db (code intelligence).
110
+ *
111
+ * Minimal surface for T9188; full nexus API lives in nexus-sqlite.ts.
112
+ *
113
+ * @task T9188
114
+ * @epic T9048
115
+ */
116
+ export interface NexusAccessor {
117
+ /**
118
+ * Check whether nexus.db is open and accessible.
119
+ *
120
+ * @returns True if the database is healthy.
121
+ */
122
+ ping(): Promise<boolean>;
123
+ /**
124
+ * Release resources held by this accessor.
125
+ */
126
+ close(): Promise<void>;
127
+ }
128
+ /**
129
+ * SignaldockAccessor — typed interface for signaldock.db (global agent identity).
130
+ *
131
+ * Minimal surface for T9188; full signaldock API lives in signaldock-sqlite.ts.
132
+ *
133
+ * @task T9188
134
+ * @epic T9048
135
+ */
136
+ export interface SignaldockAccessor {
137
+ /**
138
+ * Check whether signaldock.db is open and accessible.
139
+ *
140
+ * @returns True if the database is healthy.
141
+ */
142
+ ping(): Promise<boolean>;
143
+ /**
144
+ * Release resources held by this accessor.
145
+ */
146
+ close(): Promise<void>;
147
+ }
148
+ /**
149
+ * TelemetryAccessor — typed interface for telemetry collection (future DB).
150
+ *
151
+ * Placeholder for T9188 contract completeness. Full implementation deferred.
152
+ *
153
+ * @task T9188
154
+ * @epic T9048
155
+ */
156
+ export interface TelemetryAccessor {
157
+ /**
158
+ * Record a telemetry event.
159
+ *
160
+ * @param event - Event name.
161
+ * @param data - Event payload (JSON-serializable).
162
+ */
163
+ record(event: string, data?: Record<string, unknown>): Promise<void>;
164
+ /**
165
+ * Check whether telemetry collection is available.
166
+ *
167
+ * @returns True if collection is operational.
168
+ */
169
+ ping(): Promise<boolean>;
170
+ /**
171
+ * Release resources held by this accessor.
172
+ */
173
+ close(): Promise<void>;
174
+ }
175
+ //# sourceMappingURL=sub-accessors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sub-accessors.d.ts","sourceRoot":"","sources":["../src/sub-accessors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,uBAAuB;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kBAAkB;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mBAAmB;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kBAAkB;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;OAMG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAElF;;;;;;OAMG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAE/D;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAMD;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExD;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAEzB;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAMD;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAEzB;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAMD;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAEzB;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAMD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErE;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAEzB;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Sub-accessor interfaces for UmbrellaDataAccessor role-specific databases.
3
+ *
4
+ * Each interface provides typed access to a specific CLEO database role.
5
+ * These are returned by UmbrellaDataAccessor.getSubAccessor(role).
6
+ *
7
+ * Implementors:
8
+ * - BrainAccessor → brain.db (memory observations, semantic graph)
9
+ * - ConduitAccessor → conduit.db (project-scoped messaging)
10
+ * - NexusAccessor → nexus.db (code intelligence graph)
11
+ * - SignaldockAccessor → signaldock.db (global agent identity)
12
+ * - TelemetryAccessor → (future — telemetry collection)
13
+ *
14
+ * See DocsAccessor in docs-accessor.ts for the docs/llmtxt sub-accessor.
15
+ *
16
+ * @task T9188
17
+ * @epic T9048
18
+ * @see ADR-068 (DB Charter — per-DB write ownership)
19
+ * @see ADR-069 (Coordination Layers — Storage Layer contract)
20
+ */
21
+ export {};
22
+ //# sourceMappingURL=sub-accessors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sub-accessors.js","sourceRoot":"","sources":["../src/sub-accessors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/contracts",
3
- "version": "2026.5.56",
3
+ "version": "2026.5.58",
4
4
  "description": "Domain types, interfaces, and contracts for the CLEO ecosystem",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -82,7 +82,7 @@
82
82
  "dependencies": {
83
83
  "zod": "^4.3.6",
84
84
  "zod-to-json-schema": "^3.25.2",
85
- "@cleocode/lafs": "2026.5.56"
85
+ "@cleocode/lafs": "2026.5.58"
86
86
  },
87
87
  "scripts": {
88
88
  "build": "tsc -b --force && node scripts/emit-schemas.mjs",
@@ -0,0 +1,208 @@
1
+ /**
2
+ * DocsAccessor — unified interface for all document operations in CLEO.
3
+ *
4
+ * Abstracts the scattered document surfaces behind a single typed API:
5
+ * - llmtxt/sdk (AgentSession, receipt, version patches)
6
+ * - llmtxt/blob (content-addressed blob store)
7
+ * - llmtxt/graph (KnowledgeGraph)
8
+ * - raw filesystem agent-outputs (migrated via T9064)
9
+ *
10
+ * Consumers depend on DocsAccessor, NOT on llmtxt/* subpaths directly.
11
+ * The llmtxt SDK becomes an implementation detail of DocsAccessorImpl.
12
+ *
13
+ * DB storage split (ADR-068 — DB Charter):
14
+ * - storeDoc(kind=session-receipt | transcript) → writes to llmtxt.db
15
+ * - storeDoc(kind=adr | agent-output | attachment) → writes to manifest.db (blob store)
16
+ * - searchDocs() → queries llmtxt.db via llmtxt/similarity
17
+ * - listDocs(kind=knowledge-graph-node) → queries llmtxt.db graph tables
18
+ *
19
+ * Coordination model (ADR-069 — Coordination Layers):
20
+ * - DocsAccessor is a read/write accessor in the Storage Layer.
21
+ * - CLI commands go through the Dispatch Layer; they accept a DocsAccessor
22
+ * injected via the existing createDataAccessor() → UmbrellaDataAccessor chain.
23
+ * - No direct SQLite access in consumers — all queries route through DocsAccessor.
24
+ *
25
+ * @task T9063
26
+ * @epic T9048
27
+ * @see ADR-068 (DB Charter — per-DB write ownership)
28
+ * @see ADR-069 (Coordination Layers — Storage Layer contract)
29
+ * @see T1824 (Decision Storage Consolidation — DocsAccessor becomes the write path for ADRs)
30
+ * @see T1825 (ADR migration — ADR files ingest via storeDoc(kind='adr'))
31
+ */
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Document kind discriminated union
35
+ // ---------------------------------------------------------------------------
36
+
37
+ /**
38
+ * All document kinds that DocsAccessor can store and retrieve.
39
+ *
40
+ * - `adr` — Architecture Decision Records (docs/adr/*.md)
41
+ * - `agent-output` — Agent session markdown outputs (.cleo/agent-outputs/*.md)
42
+ * - `transcript` — Full agent conversation transcripts (llmtxt.db)
43
+ * - `attachment` — Task attachments (manifest.db blob store)
44
+ * - `session-receipt` — llmtxt session receipts (llmtxt.db sessions table)
45
+ * - `knowledge-graph-node` — Nodes from llmtxt/graph KnowledgeGraph (llmtxt.db)
46
+ */
47
+ export type DocKind =
48
+ | 'adr'
49
+ | 'agent-output'
50
+ | 'transcript'
51
+ | 'attachment'
52
+ | 'session-receipt'
53
+ | 'knowledge-graph-node';
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Core document record
57
+ // ---------------------------------------------------------------------------
58
+
59
+ /**
60
+ * A stored document record returned by getDoc / listDocs.
61
+ */
62
+ export interface DocRecord {
63
+ /** Content-addressed identifier (SHA-256 hex for blob-backed; UUID for DB-backed). */
64
+ readonly id: string;
65
+ /** Document kind. */
66
+ readonly kind: DocKind;
67
+ /** Raw content of the document (UTF-8 text or base64 for binary). */
68
+ readonly content: string;
69
+ /** Human-readable title or filename. */
70
+ readonly title: string | null;
71
+ /** ISO 8601 creation timestamp. */
72
+ readonly createdAt: string;
73
+ /** Linked task IDs (e.g. the task this agent-output was produced for). */
74
+ readonly linkedTaskIds: string[];
75
+ /** Arbitrary metadata blob (kind-specific). */
76
+ readonly meta: Record<string, unknown>;
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Method parameter types
81
+ // ---------------------------------------------------------------------------
82
+
83
+ /**
84
+ * Parameters for {@link DocsAccessor.storeDoc}.
85
+ */
86
+ export interface StoreDocParams {
87
+ /** Document kind determining which backing store is used. */
88
+ kind: DocKind;
89
+ /** Raw document content (UTF-8). */
90
+ content: string;
91
+ /** Human-readable title or filename. */
92
+ title?: string;
93
+ /** Task IDs to link to this document. */
94
+ linkedTaskIds?: string[];
95
+ /** Arbitrary metadata to attach (kind-specific). */
96
+ meta?: Record<string, unknown>;
97
+ }
98
+
99
+ /**
100
+ * Result from {@link DocsAccessor.storeDoc}.
101
+ */
102
+ export interface StoreDocResult {
103
+ /** Assigned document ID (SHA-256 for blob-backed, UUID for DB-backed). */
104
+ readonly id: string;
105
+ /** The backing store that received the write. */
106
+ readonly backend: 'llmtxt.db' | 'manifest.db';
107
+ }
108
+
109
+ /**
110
+ * Filters for {@link DocsAccessor.listDocs}.
111
+ */
112
+ export interface ListDocsFilters {
113
+ /** Restrict to one document kind. */
114
+ kind?: DocKind;
115
+ /** Restrict to documents linked to this task ID. */
116
+ linkedTaskId?: string;
117
+ /** Maximum number of results. Default: 100. */
118
+ limit?: number;
119
+ /** Offset for pagination. */
120
+ offset?: number;
121
+ /** Order by. Default: 'createdAt'. */
122
+ orderBy?: 'createdAt' | 'title';
123
+ }
124
+
125
+ /**
126
+ * A search hit from {@link DocsAccessor.searchDocs}.
127
+ */
128
+ export interface DocSearchHit {
129
+ /** The matching document record. */
130
+ readonly doc: DocRecord;
131
+ /** Similarity score (0–1, higher is more relevant). */
132
+ readonly score: number;
133
+ }
134
+
135
+ /**
136
+ * Export format for {@link DocsAccessor.exportDoc}.
137
+ */
138
+ export type DocExportFormat = 'markdown' | 'json' | 'plain';
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // DocsAccessor interface
142
+ // ---------------------------------------------------------------------------
143
+
144
+ /**
145
+ * DocsAccessor — unified interface for all CLEO document operations.
146
+ *
147
+ * Backed by llmtxt.db (sessions + receipts) and manifest.db (blob attachments).
148
+ * Both backing stores are opaque to consumers — all access goes through this
149
+ * interface.
150
+ *
151
+ * DB write ownership (ADR-068):
152
+ * - `session-receipt`, `transcript`, `knowledge-graph-node` → llmtxt.db
153
+ * - `adr`, `agent-output`, `attachment` → manifest.db (blob store)
154
+ *
155
+ * @see ADR-068 — DB Charter per-DB write ownership table
156
+ * @see ADR-069 — Coordination Layers: DocsAccessor is a Storage Layer contract
157
+ */
158
+ export interface DocsAccessor {
159
+ /**
160
+ * Store a document, routing to the correct backing store by kind.
161
+ *
162
+ * @param params - Document content, kind, and metadata.
163
+ * @returns The assigned ID and backend that received the write.
164
+ */
165
+ storeDoc(params: StoreDocParams): Promise<StoreDocResult>;
166
+
167
+ /**
168
+ * Retrieve a document by ID or content hash.
169
+ *
170
+ * @param idOrHash - Document ID (UUID or SHA-256 hex).
171
+ * @returns The document record, or null if not found.
172
+ */
173
+ getDoc(idOrHash: string): Promise<DocRecord | null>;
174
+
175
+ /**
176
+ * List documents matching filters with pagination support.
177
+ *
178
+ * @param filters - Optional filter bag.
179
+ * @returns Array of matching document records.
180
+ */
181
+ listDocs(filters?: ListDocsFilters): Promise<DocRecord[]>;
182
+
183
+ /**
184
+ * Search documents by semantic similarity via llmtxt/similarity.
185
+ *
186
+ * Queries the llmtxt.db embeddings index. Only documents with embeddings
187
+ * are returned (agent-outputs, ADRs, transcripts ingested via storeDoc).
188
+ *
189
+ * @param query - Natural language query string.
190
+ * @param limit - Maximum hits to return. Default: 10.
191
+ * @returns Ranked search results with similarity scores.
192
+ */
193
+ searchDocs(query: string, limit?: number): Promise<DocSearchHit[]>;
194
+
195
+ /**
196
+ * Export a document in the requested format.
197
+ *
198
+ * @param id - Document ID.
199
+ * @param format - Output format. Default: 'markdown'.
200
+ * @returns Formatted document content string, or null if not found.
201
+ */
202
+ exportDoc(id: string, format?: DocExportFormat): Promise<string | null>;
203
+
204
+ /**
205
+ * Release any resources held by this accessor (close DB connections).
206
+ */
207
+ close(): Promise<void>;
208
+ }
package/src/index.ts CHANGED
@@ -254,6 +254,17 @@ export type {
254
254
  DependencySpec,
255
255
  } from './dependency.js';
256
256
  export type { AdapterManifest, DetectionPattern } from './discovery.js';
257
+ // === DocsAccessor Contracts (T9063) ===
258
+ export type {
259
+ DocExportFormat,
260
+ DocKind,
261
+ DocRecord,
262
+ DocSearchHit,
263
+ DocsAccessor,
264
+ ListDocsFilters,
265
+ StoreDocParams,
266
+ StoreDocResult,
267
+ } from './docs-accessor.js';
257
268
  // === Error Utilities ===
258
269
  export {
259
270
  ClassifierUnregisteredAgentError,
@@ -1024,6 +1035,17 @@ export type {
1024
1035
  PlaybookRun,
1025
1036
  PlaybookRunStatus,
1026
1037
  } from './playbook.js';
1038
+ // === PostgresDataAccessor Contracts — cloud-sync scaffold (T9062) ===
1039
+ export type {
1040
+ CreatePostgresDataAccessorFn,
1041
+ PostgresDataAccessor,
1042
+ PostgresDataAccessorOptions,
1043
+ PostgresSyncDirection,
1044
+ PostgresTenantNamespace,
1045
+ PostgresTenantStrategy,
1046
+ SyncResult,
1047
+ SyncStatus,
1048
+ } from './postgres-data-accessor.js';
1027
1049
  export type { AdapterPathProvider } from './provider-paths.js';
1028
1050
  // === Release Pipeline (T1597 / ADR-063) ===
1029
1051
  export type {
@@ -1131,6 +1153,16 @@ export {
1131
1153
  // Terminal state sets
1132
1154
  TERMINAL_TASK_STATUSES,
1133
1155
  } from './status-registry.js';
1156
+ // === Sub-Accessor Contracts (T9188) ===
1157
+ export type {
1158
+ BrainAccessor,
1159
+ BrainMemoryHit,
1160
+ BrainObserveParams,
1161
+ ConduitAccessor,
1162
+ NexusAccessor,
1163
+ SignaldockAccessor,
1164
+ TelemetryAccessor,
1165
+ } from './sub-accessors.js';
1134
1166
  // === Task Types ===
1135
1167
  export type {
1136
1168
  AcceptanceItem,
@@ -0,0 +1,199 @@
1
+ /**
2
+ * PostgresDataAccessor — interface stub for cloud-sync backend.
3
+ *
4
+ * SCAFFOLD ONLY (T9062). This file defines the type surface and namespacing
5
+ * contract for the PostgreSQL-backed DataAccessor. Full implementation
6
+ * (sync commands, migration, multi-tenant namespacing, auth) is deferred
7
+ * to T9062 child tasks.
8
+ *
9
+ * Architecture overview (ADR-TBD — cloud-sync):
10
+ * - CLEO is local-first SQLite for portability and offline operation.
11
+ * - A PostgresDataAccessor provides an engine-neutral drop-in replacement
12
+ * for SqliteDataAccessor — same DataAccessor interface, different engine.
13
+ * - Multi-tenant namespacing: each CLEO project maps to a Postgres
14
+ * "tenant namespace" (schema or row-level tenant key) inside a shared
15
+ * cluster. Cross-tenant isolation is enforced at the DB layer.
16
+ * - Sync model: pull/push with last-write-wins per row (initial design).
17
+ * CRDT merge (cr-sqlite) is opt-in per ADR from T947.
18
+ * - Auth: each tenant identified by SignalDock identity (Ed25519 keypair).
19
+ * Push receipts signed with the same primitive as llmtxt/sdk receipts.
20
+ *
21
+ * Engine-neutral proof:
22
+ * The DataAccessor interface in @cleocode/contracts carries `engine: 'sqlite'`.
23
+ * PostgresDataAccessor will carry `engine: 'postgres'`. Both satisfy the
24
+ * same interface contract, proving the abstraction from T9050 is real.
25
+ *
26
+ * @task T9062
27
+ * @epic T9048
28
+ * @see packages/contracts/src/data-accessor.ts (DataAccessor interface)
29
+ * @see packages/contracts/src/postgres-sync-spec.ts (sync semantics spec)
30
+ * @see docs/adr/ (ADR-TBD: cloud-sync architecture — to be authored in T9062)
31
+ */
32
+
33
+ import type { DataAccessor } from './data-accessor.js';
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Multi-tenant namespacing
37
+ // ---------------------------------------------------------------------------
38
+
39
+ /**
40
+ * Strategy for isolating tenant data in a shared Postgres cluster.
41
+ *
42
+ * - `schema`: Each project gets a dedicated Postgres schema
43
+ * (`cleo_<projectHash>`). Strongest isolation; higher overhead.
44
+ * - `row-level`: All projects share tables; tenant key (`project_id`) filters
45
+ * every query. Simpler to manage; requires RLS enforcement.
46
+ */
47
+ export type PostgresTenantStrategy = 'schema' | 'row-level';
48
+
49
+ /**
50
+ * Identifies a CLEO project namespace inside the shared Postgres cluster.
51
+ *
52
+ * Generated deterministically from the project root hash (the same
53
+ * `projectHash` used in worktree paths) to ensure stable cross-machine
54
+ * identity without requiring a central registry lookup.
55
+ */
56
+ export interface PostgresTenantNamespace {
57
+ /**
58
+ * Postgres-safe schema name (schema strategy) or tenant key (row-level).
59
+ * Format: `cleo_<projectHash>` where projectHash is the 16-char hex used
60
+ * in worktree directory names (e.g. `cleo_1e3146b7352ba279`).
61
+ */
62
+ readonly namespaceName: string;
63
+ /** Original hex project hash. */
64
+ readonly projectHash: string;
65
+ /** Isolation strategy in use for this cluster. */
66
+ readonly strategy: PostgresTenantStrategy;
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Connection options
71
+ // ---------------------------------------------------------------------------
72
+
73
+ /**
74
+ * Connection options for the PostgresDataAccessor.
75
+ *
76
+ * All credentials are passed at construction time. The accessor never
77
+ * reads environment variables directly — callers are responsible for
78
+ * resolving the connection string from their config/secret store.
79
+ */
80
+ export interface PostgresDataAccessorOptions {
81
+ /**
82
+ * PostgreSQL connection string (libpq format).
83
+ * Example: `postgresql://user:pass@host:5432/cleo_sync`.
84
+ */
85
+ connectionString: string;
86
+ /** Project namespace within the cluster. */
87
+ namespace: PostgresTenantNamespace;
88
+ /**
89
+ * Optional connection pool size. Default: 5.
90
+ * Keep small — CLI invocations are short-lived, not long-running servers.
91
+ */
92
+ poolSize?: number;
93
+ /**
94
+ * Optional Ed25519 signing keypair for authenticated push receipts.
95
+ * When present, every push batch is signed with this key.
96
+ * Aligns with llmtxt/sdk ContributionReceipt signing.
97
+ */
98
+ signingKeyHex?: string;
99
+ }
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Sync semantics
103
+ // ---------------------------------------------------------------------------
104
+
105
+ /**
106
+ * Direction of a sync operation.
107
+ *
108
+ * - `push`: Write local SQLite state → Postgres.
109
+ * - `pull`: Read Postgres state → update local SQLite.
110
+ * - `bidirectional`: Pull then push (last-write-wins per row).
111
+ */
112
+ export type PostgresSyncDirection = 'push' | 'pull' | 'bidirectional';
113
+
114
+ /**
115
+ * Result from a sync operation.
116
+ */
117
+ export interface SyncResult {
118
+ /** Direction that was executed. */
119
+ direction: PostgresSyncDirection;
120
+ /** Number of rows written to Postgres (push) or read from Postgres (pull). */
121
+ rowsSynced: number;
122
+ /** Number of conflicts resolved (last-write-wins). */
123
+ conflictsResolved: number;
124
+ /** ISO-8601 timestamp of sync completion. */
125
+ completedAt: string;
126
+ }
127
+
128
+ /**
129
+ * Status report from `getStatus()`.
130
+ */
131
+ export interface SyncStatus {
132
+ /** Whether the Postgres connection is healthy. */
133
+ connected: boolean;
134
+ /** ISO-8601 timestamp of the last successful sync, or null if never synced. */
135
+ lastSyncedAt: string | null;
136
+ /** Number of local changes not yet pushed. */
137
+ pendingPushCount: number;
138
+ /** Whether the remote has changes not yet pulled. */
139
+ remoteAhead: boolean;
140
+ }
141
+
142
+ // ---------------------------------------------------------------------------
143
+ // PostgresDataAccessor interface stub
144
+ // ---------------------------------------------------------------------------
145
+
146
+ /**
147
+ * PostgresDataAccessor — Postgres-backed implementation of DataAccessor.
148
+ *
149
+ * INTERFACE STUB — full implementation deferred to T9062 child tasks.
150
+ *
151
+ * Extends the base DataAccessor interface with `engine: 'postgres'` and
152
+ * adds cloud-sync-specific methods (sync, getStatus, close).
153
+ *
154
+ * All methods from DataAccessor are inherited and must be implemented
155
+ * using Postgres queries (pg or postgres.js driver). The implementation
156
+ * will live in @cleocode/core (NOT in @cleocode/cleo — CLI-layer only).
157
+ *
158
+ * @see DataAccessor for the full list of inherited methods.
159
+ */
160
+ export interface PostgresDataAccessor extends Omit<DataAccessor, 'engine'> {
161
+ /**
162
+ * The storage engine backing this accessor.
163
+ * Discriminates from SqliteDataAccessor at the type level.
164
+ */
165
+ readonly engine: 'postgres';
166
+
167
+ /**
168
+ * Sync local state with the Postgres backend.
169
+ *
170
+ * @param direction - Which direction(s) to sync.
171
+ * @returns Summary of the sync operation.
172
+ */
173
+ sync(direction?: PostgresSyncDirection): Promise<SyncResult>;
174
+
175
+ /**
176
+ * Get the current sync status (connection health + divergence metrics).
177
+ *
178
+ * @returns Sync status report.
179
+ */
180
+ getStatus(): Promise<SyncStatus>;
181
+ }
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // Factory signature (implementation deferred)
185
+ // ---------------------------------------------------------------------------
186
+
187
+ /**
188
+ * Factory type for creating a PostgresDataAccessor.
189
+ *
190
+ * The concrete implementation (`createPostgresDataAccessor`) lives in
191
+ * @cleocode/core and will be exported from @cleocode/core/internal once
192
+ * the full implementation is complete (T9062 child tasks).
193
+ *
194
+ * @param options - Connection and namespace options.
195
+ * @returns A fully initialized PostgresDataAccessor.
196
+ */
197
+ export type CreatePostgresDataAccessorFn = (
198
+ options: PostgresDataAccessorOptions,
199
+ ) => Promise<PostgresDataAccessor>;