wendkeep 0.74.0 → 0.75.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.
@@ -1,8 +1,8 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { buildProjectSnapshot } from './observer-snapshot.mjs';
4
- import { publishObserverSql } from './observer-sql-publish.mjs';
3
+ import { publishObserverSqlIncremental } from './observer-sql-publish.mjs';
5
4
  import { observerAuthHeaders, resolveObserverToken } from './observer-auth.mjs';
5
+ import { readProjectForValidation } from '../packages/vault/src/validate-memory.mjs';
6
6
 
7
7
  const OUTBOX_REL = join('.brain', 'observer-outbox');
8
8
  const REQUEST_TIMEOUT_MS = 500;
@@ -88,51 +88,26 @@ export async function retryObserverOutbox({ vaultBase, url, token = process.env.
88
88
 
89
89
  export async function publishObserverSnapshot({
90
90
  vaultBase,
91
- projectRoot,
92
91
  url = process.env.WENDKEEP_OBSERVER_URL || '',
93
92
  now = new Date(),
94
93
  input = {},
95
94
  token = process.env.WENDKEEP_OBSERVER_TOKEN || '',
96
95
  } = {}) {
97
96
  try {
98
- const event = buildProjectSnapshot({ vaultBase, projectRoot, now });
99
- const sql = await publishObserverSql({
97
+ const project = readProjectForValidation(vaultBase);
98
+ if (!project.ok || !project.projectId) throw new Error(project.errors?.join(' ') || 'PROJECT.json inválido.');
99
+ const sql = await publishObserverSqlIncremental({
100
100
  vaultBase,
101
- projectId: event.project_id,
101
+ projectId: project.projectId,
102
102
  url,
103
103
  input,
104
104
  now,
105
105
  token,
106
106
  });
107
- if (!url) return { ok: sql.ok, skipped: true, queued: sql.queued, hookExitCode: 0, event_id: event.event_id, sql };
108
-
109
- await retryObserverOutbox({ vaultBase, url, token });
110
- // SQL is the live authority. Keep the legacy-shaped `memory` field for
111
- // older integrations while reporting the real SQL publication separately.
107
+ // SQLite is the only live authority. The legacy snapshot store remains
108
+ // readable solely as a migration source for pre-SQL installations.
112
109
  const memory = { ok: sql.ok, queued: sql.queued, changed: sql.changed, pending: sql.pending, authority: 'sqlite' };
113
- try {
114
- const response = await postSnapshot(url, event, resolveObserverToken(token));
115
- return {
116
- ok: true,
117
- queued: false,
118
- hookExitCode: 0,
119
- event_id: event.event_id,
120
- duplicate: response.duplicate === true,
121
- memory,
122
- sql,
123
- };
124
- } catch (error) {
125
- queueOutbox(vaultBase, event);
126
- return {
127
- ok: false,
128
- queued: true,
129
- hookExitCode: 0,
130
- event_id: event.event_id,
131
- error: error.message,
132
- memory,
133
- sql,
134
- };
135
- }
110
+ return { ok: sql.ok, queued: sql.queued, hookExitCode: 0, memory, sql };
136
111
  } catch (error) {
137
112
  return { ok: false, queued: false, hookExitCode: 0, error: error.message };
138
113
  }
@@ -4,17 +4,11 @@ import { readFileSync } from 'node:fs';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { gunzipSync } from 'node:zlib';
6
6
  import {
7
- appendObserverEvent,
8
- getObserverProject,
9
7
  listRegisteredObserverProjects,
10
- readObserverIndex,
11
- registerObserverProject,
8
+ readObserverIndexSource,
12
9
  } from './observer-store.mjs';
13
10
  import { MAX_SNAPSHOT_BYTES, validateObserverSnapshot } from './observer-snapshot.mjs';
14
- import {
15
- setMemoryMode,
16
- validateMemoryEvent,
17
- } from './observer-memory.mjs';
11
+ import { validateMemoryEvent } from './observer-memory.mjs';
18
12
  import {
19
13
  OBSERVER_SQL_FILE,
20
14
  OBSERVER_SQL_SCHEMA_VERSION,
@@ -22,6 +16,8 @@ import {
22
16
  ingestObserverEvents,
23
17
  migrateObserverDatabase,
24
18
  readSqlProject,
19
+ readSqlProjectOverview,
20
+ readSqlProjectSnapshot,
25
21
  listSqlProjects,
26
22
  readSqlDocument,
27
23
  readSqlSync,
@@ -33,6 +29,7 @@ import {
33
29
  readUsageCalls,
34
30
  readUsageSummary,
35
31
  registerSqlProject,
32
+ upsertSqlProjectSnapshot,
36
33
  } from './observer-sql-store.mjs';
37
34
  import { migrateObserverContainerData } from './observer-sql-migrate.mjs';
38
35
 
@@ -182,8 +179,8 @@ function ensureSqlProjectRegistration(dataDir, sqlDb, projectId) {
182
179
  } catch (error) {
183
180
  if (error?.code !== 'project_not_registered') throw error;
184
181
  }
185
- const legacy = getObserverProject(dataDir, projectId)
186
- || listRegisteredObserverProjects(dataDir).find((item) => item.projectId === projectId);
182
+ const legacy = listRegisteredObserverProjects(dataDir).find((item) => item.projectId === projectId)
183
+ || readObserverIndexSource(dataDir).projects.find((item) => item.projectId === projectId);
187
184
  if (!legacy) return false;
188
185
  registerSqlProject(sqlDb, {
189
186
  projectId: legacy.projectId,
@@ -234,13 +231,16 @@ export async function startObserverServer({
234
231
  const legacyMigration = migrateObserverContainerData(dataDir, { database: sqlDb });
235
232
  const registered = [
236
233
  ...listRegisteredObserverProjects(dataDir),
237
- ...readObserverIndex(dataDir).projects.map((item) => ({
234
+ ...readObserverIndexSource(dataDir).projects.map((item) => ({
238
235
  projectId: item.projectId,
239
236
  projectName: item.projectName,
240
237
  wendkeepVersion: item.snapshot?.wendkeep_version || '',
241
238
  })),
242
239
  ];
243
240
  for (const project of registered) registerSqlProject(sqlDb, project);
241
+ for (const project of readObserverIndexSource(dataDir).projects) {
242
+ if (project.snapshot) upsertSqlProjectSnapshot(sqlDb, project.snapshot);
243
+ }
244
244
  const server = createServer(async (req, res) => {
245
245
  try {
246
246
  const pathname = new URL(req.url || '/', 'http://127.0.0.1').pathname;
@@ -286,21 +286,11 @@ export async function startObserverServer({
286
286
  }
287
287
 
288
288
  if (parts.length === 2 && req.method === 'GET') {
289
- const index = readObserverIndex(dataDir);
290
- const legacy = new Map(index.projects.map(({ snapshot, ...summary }) => [summary.projectId, summary]));
291
- for (const project of listSqlProjects(sqlDb)) {
292
- const current = legacy.get(project.project_id) || {};
293
- legacy.set(project.project_id, {
294
- ...current,
295
- projectId: project.project_id,
296
- projectName: current.projectName || project.project_name,
297
- wendkeepVersion: current.wendkeepVersion || project.wendkeep_version,
298
- registeredAt: current.registeredAt || project.registered_at,
299
- });
300
- }
301
289
  json(res, 200, {
302
- schema_version: index.schema_version,
303
- projects: [...legacy.values()].sort((a, b) => a.projectId.localeCompare(b.projectId)),
290
+ schema_version: 1,
291
+ projects: listSqlProjects(sqlDb)
292
+ .map((project) => readSqlProjectOverview(sqlDb, project.project_id))
293
+ .sort((a, b) => a.projectId.localeCompare(b.projectId)),
304
294
  });
305
295
  return;
306
296
  }
@@ -386,11 +376,11 @@ export async function startObserverServer({
386
376
  return;
387
377
  }
388
378
  const body = parseJson(await readBody(req));
389
- try {
390
- json(res, 200, setMemoryMode(dataDir, projectId, body.mode));
391
- } catch (error) {
392
- errorResponse(res, 400, error?.code || 'invalid_memory_mode', error?.message || 'modo inválido.');
379
+ if (body.mode && body.mode !== 'container-authority') {
380
+ errorResponse(res, 409, 'sql_authority_fixed', 'O Observer SQL é a autoridade única; modos legados são somente fonte de migração.');
381
+ return;
393
382
  }
383
+ json(res, 200, { ...readSqlSync(sqlDb, projectId), mode: 'container-authority', compatibility_noop: true });
394
384
  return;
395
385
  }
396
386
 
@@ -449,19 +439,8 @@ export async function startObserverServer({
449
439
  }
450
440
 
451
441
  if (parts.length === 3 && req.method === 'GET') {
452
- let project = getObserverProject(dataDir, projectId);
453
- if (!project) {
454
- try {
455
- const sqlProject = readSqlProject(sqlDb, projectId);
456
- project = {
457
- projectId: sqlProject.project_id,
458
- projectName: sqlProject.project_name,
459
- wendkeepVersion: sqlProject.wendkeep_version,
460
- registeredAt: sqlProject.registered_at,
461
- eventCount: 0,
462
- };
463
- } catch { /* handled as not found below */ }
464
- }
442
+ let project;
443
+ try { project = readSqlProjectOverview(sqlDb, projectId); } catch { /* handled below */ }
465
444
  if (!project) {
466
445
  errorResponse(res, 404, 'project_not_found', `projeto não encontrado: ${projectId}`);
467
446
  return;
@@ -476,7 +455,7 @@ export async function startObserverServer({
476
455
  errorResponse(res, 400, 'project_mismatch', 'project_id do corpo não corresponde à rota.');
477
456
  return;
478
457
  }
479
- const result = registerObserverProject(dataDir, {
458
+ const result = registerSqlProject(sqlDb, {
480
459
  projectId,
481
460
  projectName: body.project_name,
482
461
  wendkeepVersion: body.wendkeep_version,
@@ -485,30 +464,23 @@ export async function startObserverServer({
485
464
  errorResponse(res, 400, 'invalid_project', result.errors.join(' '));
486
465
  return;
487
466
  }
488
- registerSqlProject(sqlDb, {
489
- projectId,
490
- projectName: body.project_name,
491
- wendkeepVersion: body.wendkeep_version,
467
+ json(res, 201, {
468
+ projectId: result.project.project_id,
469
+ projectName: result.project.project_name,
470
+ wendkeepVersion: result.project.wendkeep_version,
471
+ registeredAt: result.project.registered_at,
492
472
  });
493
- json(res, 201, result.project);
494
473
  return;
495
474
  }
496
475
 
497
476
  if (parts.length === 4 && parts[3] === 'changes' && req.method === 'GET') {
498
- const project = getObserverProject(dataDir, projectId);
499
- if (!project) {
500
- let sqlProject;
501
- try { sqlProject = readSqlProject(sqlDb, projectId); } catch (error) {
502
- if (error?.code !== 'project_not_registered') throw error;
503
- }
504
- if (!sqlProject) {
505
- errorResponse(res, 404, 'project_not_found', `projeto não encontrado: ${projectId}`);
506
- return;
507
- }
508
- json(res, 200, { project_id: projectId, changes: [] });
477
+ let snapshot;
478
+ try { snapshot = readSqlProjectSnapshot(sqlDb, projectId); } catch (error) {
479
+ if (error?.code !== 'project_not_registered') throw error;
480
+ errorResponse(res, 404, 'project_not_found', `projeto não encontrado: ${projectId}`);
509
481
  return;
510
482
  }
511
- json(res, 200, { project_id: projectId, changes: project.snapshot?.changes || [] });
483
+ json(res, 200, { project_id: projectId, changes: snapshot?.changes || [] });
512
484
  return;
513
485
  }
514
486
 
@@ -519,14 +491,17 @@ export async function startObserverServer({
519
491
  errorResponse(res, 400, 'invalid_snapshot', validation.errors.join(' '));
520
492
  return;
521
493
  }
522
- const result = appendObserverEvent(dataDir, body);
494
+ const result = upsertSqlProjectSnapshot(sqlDb, body);
523
495
  if (!result.accepted && result.duplicate) {
524
496
  json(res, 200, { accepted: false, duplicate: true, event_id: body.event_id });
525
497
  return;
526
498
  }
527
499
  if (!result.accepted) {
528
- const unregistered = result.errors.some((item) => /não registrado/.test(item));
529
- errorResponse(res, unregistered ? 409 : 400, unregistered ? 'project_not_registered' : 'invalid_event', result.errors.join(' '));
500
+ if (result.stale) {
501
+ json(res, 200, { accepted: false, stale: true, event_id: body.event_id });
502
+ return;
503
+ }
504
+ errorResponse(res, 400, 'invalid_event', 'snapshot não aceito.');
530
505
  return;
531
506
  }
532
507
  json(res, 201, { accepted: true, duplicate: false, event_id: body.event_id });
@@ -41,7 +41,7 @@ function walk(root, relativeRoot, out) {
41
41
  for (const entry of readdirSync(root, { withFileTypes: true })) {
42
42
  const logicalPath = relativeRoot ? `${relativeRoot}/${entry.name}` : entry.name;
43
43
  if (entry.name.endsWith('.tmp') || entry.name.endsWith('.lock')
44
- || ['observer-memory-outbox', 'observer-outbox', 'observer-sql-outbox', 'observer-sql-state.json', 'observer-memory-state.json'].includes(entry.name)) continue;
44
+ || ['observer-memory-outbox', 'observer-outbox', 'observer-sql-outbox', 'observer-sql-state.json', 'observer-sql-publisher.lock', 'observer-memory-state.json'].includes(entry.name)) continue;
45
45
  const absolute = join(root, entry.name);
46
46
  if (entry.isDirectory()) walk(absolute, logicalPath, out);
47
47
  else if (entry.isFile()) out.push({ absolute, logicalPath });