@geraldmaron/construct 1.0.23 → 1.0.24
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/README.md +0 -2
- package/bin/construct +15 -215
- package/lib/embed/inbox.mjs +6 -3
- package/lib/embed/recommendation-store.mjs +7 -289
- package/lib/embed/reconcile.mjs +2 -2
- package/lib/hooks/config-protection.mjs +4 -4
- package/lib/hooks/session-reflect.mjs +5 -1
- package/lib/intake/git-queue.mjs +195 -0
- package/lib/intake/queue.mjs +9 -16
- package/lib/mcp/tools/storage.mjs +2 -3
- package/lib/mcp-catalog.json +3 -3
- package/lib/mcp-manager.mjs +59 -3
- package/lib/observation-store.mjs +38 -166
- package/lib/orchestration/runtime.mjs +3 -2
- package/lib/reconcile/index.mjs +0 -2
- package/lib/service-manager.mjs +38 -256
- package/lib/setup.mjs +26 -426
- package/lib/status.mjs +3 -6
- package/lib/storage/admin.mjs +48 -325
- package/lib/storage/backend.mjs +10 -57
- package/lib/storage/hybrid-query.mjs +15 -196
- package/lib/storage/sync.mjs +36 -177
- package/lib/storage/vector-client.mjs +256 -235
- package/lib/strategy-store.mjs +35 -286
- package/lib/worker/entrypoint.mjs +6 -14
- package/package.json +6 -5
- package/platforms/claude/settings.template.json +0 -7
- package/scripts/sync-specialists.mjs +46 -12
- package/specialists/prompts/cx-qa.md +1 -1
- package/specialists/registry.json +0 -8
- package/templates/docs/construct_guide.md +1 -1
- package/db/schema/001_init.sql +0 -40
- package/db/schema/002_pgvector.sql +0 -182
- package/db/schema/003_intake.sql +0 -47
- package/db/schema/003_observation_reconciliation.sql +0 -14
- package/db/schema/004_recommendations.sql +0 -46
- package/db/schema/005_strategy.sql +0 -21
- package/db/schema/006_graph.sql +0 -24
- package/db/schema/007_tags.sql +0 -30
- package/db/schema/008_skill_usage.sql +0 -24
- package/db/schema/009_scheduler.sql +0 -14
- package/db/schema/010_cx_scores.sql +0 -51
- package/lib/intake/postgres-queue.mjs +0 -240
- package/lib/reconcile/postgres-namespace.mjs +0 -102
- package/lib/services/local-postgres.mjs +0 -15
- package/lib/storage/backup.mjs +0 -347
- package/lib/storage/migrations.mjs +0 -187
- package/lib/storage/postgres-backup.mjs +0 -124
- package/lib/storage/sql-store.mjs +0 -45
- package/lib/storage/store-version.mjs +0 -115
- package/lib/storage/unified-storage.mjs +0 -550
- package/lib/storage/vector-store.mjs +0 -100
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/intake/postgres-queue.mjs — Postgres adapter for the IntakeQueue interface.
|
|
3
|
-
*
|
|
4
|
-
* Backs team and enterprise deployment modes. Implements the same six-method
|
|
5
|
-
* contract as FilesystemIntakeQueue (enqueue, listPending, count, read,
|
|
6
|
-
* markProcessed, markSkipped, reopen) plus a claim() method that uses
|
|
7
|
-
* SELECT ... FOR UPDATE SKIP LOCKED so concurrent workers cannot grab the
|
|
8
|
-
* same pending item.
|
|
9
|
-
*
|
|
10
|
-
* Triage fields (intake_type, rd_stage, primary_owner, recommended_action,
|
|
11
|
-
* risk, requires_approval, confidence) are flattened into typed columns for
|
|
12
|
-
* efficient filtering; the full packet — including suggestion / related /
|
|
13
|
-
* excerpt / query — lives in payload jsonb so the on-disk and in-table
|
|
14
|
-
* shapes round-trip without loss.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import path from 'node:path';
|
|
18
|
-
import { shouldQuarantine } from './quarantine.mjs';
|
|
19
|
-
|
|
20
|
-
function slugify(value) {
|
|
21
|
-
return String(value || 'untitled')
|
|
22
|
-
.toLowerCase()
|
|
23
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
24
|
-
.replace(/^-+|-+$/g, '')
|
|
25
|
-
.slice(0, 60) || 'untitled';
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let counter = 0;
|
|
29
|
-
function timestamp() {
|
|
30
|
-
// Milliseconds + a per-process counter to guarantee uniqueness even when
|
|
31
|
-
// two enqueues fire in the same millisecond (test loops, batched ingests).
|
|
32
|
-
counter = (counter + 1) % 1000;
|
|
33
|
-
const c = String(counter).padStart(3, '0');
|
|
34
|
-
return `${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 23)}-${c}`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function rowToEntry(row) {
|
|
38
|
-
if (!row) return null;
|
|
39
|
-
const payload = row.payload || {};
|
|
40
|
-
return {
|
|
41
|
-
id: row.id,
|
|
42
|
-
createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : row.created_at,
|
|
43
|
-
status: row.status,
|
|
44
|
-
...payload,
|
|
45
|
-
processedAt: row.processed_at instanceof Date ? row.processed_at.toISOString() : row.processed_at || undefined,
|
|
46
|
-
processedBy: row.processed_by || undefined,
|
|
47
|
-
notes: row.notes || undefined,
|
|
48
|
-
skippedAt: row.skipped_at instanceof Date ? row.skipped_at.toISOString() : row.skipped_at || undefined,
|
|
49
|
-
skippedBy: row.skipped_by || undefined,
|
|
50
|
-
reason: row.skip_reason || undefined,
|
|
51
|
-
claimedAt: row.claimed_at instanceof Date ? row.claimed_at.toISOString() : row.claimed_at || undefined,
|
|
52
|
-
claimedBy: row.claimed_by || undefined,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export class PostgresIntakeQueue {
|
|
57
|
-
constructor({ sql, project, tenantId = null } = {}) {
|
|
58
|
-
if (!sql) throw new Error('PostgresIntakeQueue: sql client is required');
|
|
59
|
-
if (!project) throw new Error('PostgresIntakeQueue: project is required');
|
|
60
|
-
this.sql = sql;
|
|
61
|
-
this.project = project;
|
|
62
|
-
this.tenantId = tenantId;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async enqueue(entry) {
|
|
66
|
-
if (!entry?.intake?.sourcePath) throw new Error('enqueue: entry.intake.sourcePath is required');
|
|
67
|
-
|
|
68
|
-
const ts = timestamp();
|
|
69
|
-
const slug = slugify(path.basename(entry.intake.sourcePath, path.extname(entry.intake.sourcePath)));
|
|
70
|
-
const id = `${ts}-${slug}`;
|
|
71
|
-
const triage = entry.triage || {};
|
|
72
|
-
|
|
73
|
-
// Quarantine routing: low-confidence packets land in status='quarantined'
|
|
74
|
-
// instead of 'pending' so worker claim() never picks them up. Human
|
|
75
|
-
// reroute via rerouteQuarantined() promotes to 'pending'.
|
|
76
|
-
const quarantineDecision = shouldQuarantine(triage);
|
|
77
|
-
const status = quarantineDecision.quarantine ? 'quarantined' : 'pending';
|
|
78
|
-
const augmentedEntry = quarantineDecision.quarantine
|
|
79
|
-
? { ...entry, quarantineReason: quarantineDecision.reason }
|
|
80
|
-
: entry;
|
|
81
|
-
|
|
82
|
-
await this.sql`
|
|
83
|
-
INSERT INTO construct_intake_items (
|
|
84
|
-
id, project, tenant_id, status,
|
|
85
|
-
intake_type, rd_stage, primary_owner, recommended_action,
|
|
86
|
-
risk, requires_approval, confidence, payload
|
|
87
|
-
) VALUES (
|
|
88
|
-
${id}, ${this.project}, ${this.tenantId}, ${status},
|
|
89
|
-
${triage.intakeType || null}, ${triage.rdStage || null},
|
|
90
|
-
${triage.primaryOwner || null}, ${triage.recommendedAction || null},
|
|
91
|
-
${triage.risk || null}, ${triage.requiresApproval || false},
|
|
92
|
-
${typeof triage.confidence === 'number' ? triage.confidence : null},
|
|
93
|
-
${this.sql.json(augmentedEntry)}
|
|
94
|
-
)
|
|
95
|
-
`;
|
|
96
|
-
return { id, route: quarantineDecision.quarantine ? 'quarantine' : 'pending', reason: quarantineDecision.reason };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async listQuarantine({ limit = 100 } = {}) {
|
|
100
|
-
const rows = await this.sql`
|
|
101
|
-
SELECT * FROM construct_intake_items
|
|
102
|
-
WHERE project = ${this.project}
|
|
103
|
-
AND (${this.tenantId}::text IS NULL OR tenant_id IS NOT DISTINCT FROM ${this.tenantId})
|
|
104
|
-
AND status = 'quarantined'
|
|
105
|
-
ORDER BY created_at ASC
|
|
106
|
-
LIMIT ${limit}
|
|
107
|
-
`;
|
|
108
|
-
return rows.map(rowToEntry);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async rerouteQuarantined(id, newType, { reroutedBy = 'unknown', reason = '' } = {}) {
|
|
112
|
-
const result = await this.sql`
|
|
113
|
-
UPDATE construct_intake_items
|
|
114
|
-
SET status = 'pending',
|
|
115
|
-
intake_type = ${newType},
|
|
116
|
-
payload = jsonb_set(
|
|
117
|
-
jsonb_set(payload, '{triage,originalIntakeType}', to_jsonb(intake_type::text)),
|
|
118
|
-
'{triage,intakeType}', to_jsonb(${newType}::text)
|
|
119
|
-
),
|
|
120
|
-
updated_at = now(),
|
|
121
|
-
notes = ${`rerouted by ${reroutedBy}${reason ? `: ${reason}` : ''}`}
|
|
122
|
-
WHERE id = ${id}
|
|
123
|
-
AND project = ${this.project}
|
|
124
|
-
AND status = 'quarantined'
|
|
125
|
-
RETURNING id
|
|
126
|
-
`;
|
|
127
|
-
if (result.count === 0) throw new Error(`rerouteQuarantined: no quarantined entry ${id}`);
|
|
128
|
-
return { id, route: 'pending' };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async listPending({ limit = 100 } = {}) {
|
|
132
|
-
const rows = await this.sql`
|
|
133
|
-
SELECT * FROM construct_intake_items
|
|
134
|
-
WHERE project = ${this.project}
|
|
135
|
-
AND (${this.tenantId}::text IS NULL OR tenant_id IS NOT DISTINCT FROM ${this.tenantId})
|
|
136
|
-
AND status = 'pending'
|
|
137
|
-
ORDER BY created_at ASC
|
|
138
|
-
LIMIT ${limit}
|
|
139
|
-
`;
|
|
140
|
-
return rows.map(rowToEntry);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async count() {
|
|
144
|
-
const rows = await this.sql`
|
|
145
|
-
SELECT COUNT(*)::int AS n FROM construct_intake_items
|
|
146
|
-
WHERE project = ${this.project}
|
|
147
|
-
AND (${this.tenantId}::text IS NULL OR tenant_id IS NOT DISTINCT FROM ${this.tenantId})
|
|
148
|
-
AND status = 'pending'
|
|
149
|
-
`;
|
|
150
|
-
return rows[0]?.n ?? 0;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async read(id) {
|
|
154
|
-
const rows = await this.sql`
|
|
155
|
-
SELECT * FROM construct_intake_items
|
|
156
|
-
WHERE id = ${id}
|
|
157
|
-
AND project = ${this.project}
|
|
158
|
-
AND (${this.tenantId}::text IS NULL OR tenant_id IS NOT DISTINCT FROM ${this.tenantId})
|
|
159
|
-
LIMIT 1
|
|
160
|
-
`;
|
|
161
|
-
return rows[0] ? rowToEntry(rows[0]) : null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async markProcessed(id, { processedBy = 'unknown', notes = '' } = {}) {
|
|
165
|
-
const result = await this.sql`
|
|
166
|
-
UPDATE construct_intake_items
|
|
167
|
-
SET status = 'processed', processed_at = now(), processed_by = ${processedBy},
|
|
168
|
-
notes = ${notes || null}, updated_at = now()
|
|
169
|
-
WHERE id = ${id}
|
|
170
|
-
AND project = ${this.project}
|
|
171
|
-
AND status IN ('pending', 'claimed')
|
|
172
|
-
RETURNING id
|
|
173
|
-
`;
|
|
174
|
-
if (result.count === 0) throw new Error(`markProcessed: no pending entry ${id}`);
|
|
175
|
-
return { id };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async markSkipped(id, { skippedBy = 'unknown', reason = '' } = {}) {
|
|
179
|
-
const result = await this.sql`
|
|
180
|
-
UPDATE construct_intake_items
|
|
181
|
-
SET status = 'skipped', skipped_at = now(), skipped_by = ${skippedBy},
|
|
182
|
-
skip_reason = ${reason || null}, updated_at = now()
|
|
183
|
-
WHERE id = ${id}
|
|
184
|
-
AND project = ${this.project}
|
|
185
|
-
AND status IN ('pending', 'claimed')
|
|
186
|
-
RETURNING id
|
|
187
|
-
`;
|
|
188
|
-
if (result.count === 0) throw new Error(`markSkipped: no pending entry ${id}`);
|
|
189
|
-
return { id };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async reopen(id) {
|
|
193
|
-
const result = await this.sql`
|
|
194
|
-
UPDATE construct_intake_items
|
|
195
|
-
SET status = 'pending',
|
|
196
|
-
processed_at = NULL, processed_by = NULL, notes = NULL,
|
|
197
|
-
skipped_at = NULL, skipped_by = NULL, skip_reason = NULL,
|
|
198
|
-
claimed_at = NULL, claimed_by = NULL,
|
|
199
|
-
updated_at = now()
|
|
200
|
-
WHERE id = ${id}
|
|
201
|
-
AND project = ${this.project}
|
|
202
|
-
AND status IN ('processed', 'skipped')
|
|
203
|
-
RETURNING id, (CASE WHEN processed_at IS NOT NULL THEN 'processed' ELSE 'skipped' END) AS from_status
|
|
204
|
-
`;
|
|
205
|
-
if (result.count === 0) throw new Error(`reopen: no processed or skipped entry ${id}`);
|
|
206
|
-
return { id, from: result[0]?.from_status || 'unknown' };
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Atomically claim the next pending intake item for a worker.
|
|
211
|
-
* Uses SELECT ... FOR UPDATE SKIP LOCKED so concurrent workers across
|
|
212
|
-
* processes / containers cannot grab the same item. Returns the claimed
|
|
213
|
-
* entry (with status='claimed'), or null when the queue is empty.
|
|
214
|
-
*/
|
|
215
|
-
async claim({ claimedBy }) {
|
|
216
|
-
if (!claimedBy) throw new Error('claim: claimedBy is required');
|
|
217
|
-
return await this.sql.begin(async (tx) => {
|
|
218
|
-
const rows = await tx`
|
|
219
|
-
SELECT * FROM construct_intake_items
|
|
220
|
-
WHERE project = ${this.project}
|
|
221
|
-
AND (${this.tenantId}::text IS NULL OR tenant_id IS NOT DISTINCT FROM ${this.tenantId})
|
|
222
|
-
AND status = 'pending'
|
|
223
|
-
ORDER BY created_at ASC
|
|
224
|
-
FOR UPDATE SKIP LOCKED
|
|
225
|
-
LIMIT 1
|
|
226
|
-
`;
|
|
227
|
-
const row = rows[0];
|
|
228
|
-
if (!row) return null;
|
|
229
|
-
await tx`
|
|
230
|
-
UPDATE construct_intake_items
|
|
231
|
-
SET status = 'claimed', claimed_at = now(), claimed_by = ${claimedBy}, updated_at = now()
|
|
232
|
-
WHERE id = ${row.id}
|
|
233
|
-
`;
|
|
234
|
-
row.status = 'claimed';
|
|
235
|
-
row.claimed_by = claimedBy;
|
|
236
|
-
row.claimed_at = new Date();
|
|
237
|
-
return rowToEntry(row);
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/reconcile/postgres-namespace.mjs — migrate a local Postgres compose that
|
|
3
|
-
* predates per-home identifier derivation (ADR-0027 / construct-lb7b).
|
|
4
|
-
*
|
|
5
|
-
* Earlier versions wrote a singular `construct-postgres` container on the fixed
|
|
6
|
-
* 54329 port. Two isolated HOMEs sharing that container clobber each other's
|
|
7
|
-
* data and fight over the port. The derivation namespaces both per home; the
|
|
8
|
-
* repair rewrites a legacy compose file to the namespaced form.
|
|
9
|
-
*
|
|
10
|
-
* apply() is strictly data-safe: it only rewrites the compose via
|
|
11
|
-
* writeLocalPostgresCompose(home). It runs no docker command, removes no
|
|
12
|
-
* container, and deletes no volume — the existing volume and any data inside it
|
|
13
|
-
* are left intact. The returned summary instructs the operator to run
|
|
14
|
-
* `construct down && construct up` to recreate the container against the
|
|
15
|
-
* namespaced compose.
|
|
16
|
-
*
|
|
17
|
-
* Safety: `ask`. The rewrite changes the container/port a running stack binds
|
|
18
|
-
* to, so it runs only on explicit consent. detect() reads only; apply() is
|
|
19
|
-
* idempotent because the namespaced compose fails to match the legacy markers.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import fs from 'node:fs';
|
|
23
|
-
|
|
24
|
-
import { homeDir } from '../paths.mjs';
|
|
25
|
-
import {
|
|
26
|
-
LEGACY_PG_CONTAINER,
|
|
27
|
-
LEGACY_PG_PORT,
|
|
28
|
-
postgresContainerName,
|
|
29
|
-
postgresPort,
|
|
30
|
-
} from '../home-namespace.mjs';
|
|
31
|
-
import { localPostgresComposePath, writeLocalPostgresCompose } from '../setup.mjs';
|
|
32
|
-
|
|
33
|
-
// The container marker anchors to end-of-line so the namespaced form
|
|
34
|
-
// `construct-postgres-<suffix>` does not match the singular legacy name as a
|
|
35
|
-
// prefix (which would defeat idempotency).
|
|
36
|
-
|
|
37
|
-
const LEGACY_CONTAINER_RE = new RegExp(`container_name:\\s*${LEGACY_PG_CONTAINER}\\s*$`, 'm');
|
|
38
|
-
const LEGACY_PORT_RE = new RegExp(`:${LEGACY_PG_PORT}:`);
|
|
39
|
-
|
|
40
|
-
function legacyMarkers(content, home) {
|
|
41
|
-
const derivedContainer = postgresContainerName(process.env, home);
|
|
42
|
-
const derivedPort = postgresPort(process.env, home);
|
|
43
|
-
|
|
44
|
-
// The legacy singular container only counts when the derivation would name it
|
|
45
|
-
// differently; an explicit CONSTRUCT_PG_CONTAINER override pinned to the
|
|
46
|
-
// singular name is the intended state, not drift.
|
|
47
|
-
|
|
48
|
-
const hasLegacyContainer = LEGACY_CONTAINER_RE.test(content) && derivedContainer !== LEGACY_PG_CONTAINER;
|
|
49
|
-
const hasLegacyPort = LEGACY_PORT_RE.test(content) && derivedPort !== LEGACY_PG_PORT;
|
|
50
|
-
return { hasLegacyContainer, hasLegacyPort, derivedContainer, derivedPort };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function detect() {
|
|
54
|
-
const home = homeDir();
|
|
55
|
-
const composePath = localPostgresComposePath(home);
|
|
56
|
-
if (!fs.existsSync(composePath)) {
|
|
57
|
-
return { needsRepair: false, summary: 'No local Postgres compose file present.' };
|
|
58
|
-
}
|
|
59
|
-
let content = '';
|
|
60
|
-
try {
|
|
61
|
-
content = fs.readFileSync(composePath, 'utf8');
|
|
62
|
-
} catch (err) {
|
|
63
|
-
return { needsRepair: false, summary: `Could not read compose file: ${err.message}` };
|
|
64
|
-
}
|
|
65
|
-
const markers = legacyMarkers(content, home);
|
|
66
|
-
if (!markers.hasLegacyContainer && !markers.hasLegacyPort) {
|
|
67
|
-
return { needsRepair: false, summary: 'Local Postgres compose already uses per-home identifiers.' };
|
|
68
|
-
}
|
|
69
|
-
const reasons = [];
|
|
70
|
-
if (markers.hasLegacyContainer) reasons.push(`container ${LEGACY_PG_CONTAINER} → ${markers.derivedContainer}`);
|
|
71
|
-
if (markers.hasLegacyPort) reasons.push(`port ${LEGACY_PG_PORT} → ${markers.derivedPort}`);
|
|
72
|
-
return {
|
|
73
|
-
needsRepair: true,
|
|
74
|
-
summary: `Local Postgres compose predates per-home derivation (${reasons.join(', ')}).`,
|
|
75
|
-
details: { composePath, ...markers },
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function apply() {
|
|
80
|
-
const home = homeDir();
|
|
81
|
-
const composePath = localPostgresComposePath(home);
|
|
82
|
-
if (!fs.existsSync(composePath)) return { summary: 'No compose file to migrate.' };
|
|
83
|
-
|
|
84
|
-
// Rewrite the compose to the per-home form only. No docker invocation, no
|
|
85
|
-
// container removal, no volume deletion — the operator recreates the
|
|
86
|
-
// container themselves so data handling stays under their control.
|
|
87
|
-
|
|
88
|
-
writeLocalPostgresCompose(home);
|
|
89
|
-
const container = postgresContainerName(process.env, home);
|
|
90
|
-
const port = postgresPort(process.env, home);
|
|
91
|
-
return {
|
|
92
|
-
summary: `Rewrote local Postgres compose to per-home identifiers (container ${container}, port ${port}). Run \`construct down && construct up\` to recreate the container; existing data and volume are untouched.`,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export default {
|
|
97
|
-
id: 'postgres-namespace',
|
|
98
|
-
description: 'Rewrite a legacy local Postgres compose to per-home container/port identifiers (data-safe).',
|
|
99
|
-
safety: 'ask',
|
|
100
|
-
detect,
|
|
101
|
-
apply,
|
|
102
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/services/local-postgres.mjs — local Postgres helper exports.
|
|
3
|
-
*
|
|
4
|
-
* Compatibility wrappers live in telemetry-backend.mjs until downstream imports
|
|
5
|
-
* move, but new service-management code should use this name.
|
|
6
|
-
*/
|
|
7
|
-
export {
|
|
8
|
-
POSTGRES_LOCAL_PORT,
|
|
9
|
-
isRemoteTelemetry,
|
|
10
|
-
servicesComposePath,
|
|
11
|
-
pruneStashDir,
|
|
12
|
-
verifyPostgresHealth,
|
|
13
|
-
verifyTelemetryKeys,
|
|
14
|
-
startManagedServices,
|
|
15
|
-
} from './telemetry-backend.mjs';
|