wendkeep 0.86.0 → 0.88.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.
- package/.githooks/commit-msg +16 -0
- package/.githooks/prepare-commit-msg +16 -0
- package/CHANGELOG.md +32 -0
- package/README.en.md +3 -1
- package/README.md +3 -1
- package/docs/en/commands/commit.md +159 -0
- package/docs/en/commands/observer-security.md +154 -0
- package/docs/en/commands/observer.md +30 -12
- package/docs/pt-BR/commands/commit.md +159 -0
- package/docs/pt-BR/commands/observer-security.md +154 -0
- package/docs/pt-BR/commands/observer.md +30 -12
- package/hooks/observer-publish.mjs +3 -1
- package/package.json +6 -2
- package/packages/cli/src/index.mjs +11 -1
- package/packages/commit/package.json +6 -0
- package/packages/commit/src/cli.mjs +89 -0
- package/packages/commit/src/commit-input.mjs +181 -0
- package/packages/commit/src/commit-message.mjs +51 -0
- package/packages/commit/src/commit-policy.mjs +144 -0
- package/packages/commit/src/git-runtime.mjs +428 -0
- package/packages/commit/src/index.mjs +28 -0
- package/packages/commit/src/proof-validation.mjs +443 -0
- package/packages/mcp/src/executor.mjs +35 -2
- package/packages/observer/package.json +16 -0
- package/packages/observer/src/audit.mjs +1 -0
- package/packages/observer/src/authz.mjs +38 -0
- package/packages/observer/src/encryption.mjs +75 -0
- package/packages/observer/src/index.mjs +7 -0
- package/packages/observer/src/policy.mjs +305 -0
- package/packages/observer/src/purge.mjs +100 -0
- package/packages/observer/src/redaction.mjs +54 -0
- package/packages/observer/src/retention.mjs +39 -0
- package/packages/observer/src/token-registry.mjs +122 -0
- package/schema/commit-message-v1.schema.json +75 -0
- package/schema/observer/006-observer-security.sql +64 -0
- package/schema/observer-policy-v1.schema.json +63 -0
- package/schema/sync-event-v1.schema.json +10 -0
- package/scripts/validate-commit-range.mjs +244 -0
- package/src/doctor.mjs +7 -0
- package/src/git-commit-hooks.mjs +112 -0
- package/src/init.mjs +13 -0
- package/src/observer-auth.mjs +8 -0
- package/src/observer-privacy.mjs +7 -3
- package/src/observer-publish.mjs +31 -0
- package/src/observer-server.mjs +179 -20
- package/src/observer-sql-migrate.mjs +5 -2
- package/src/observer-sql-publish.mjs +114 -39
- package/src/observer-sql-store.mjs +299 -45
- package/src/observer-transcript-store.mjs +23 -8
- package/src/observer.mjs +145 -12
- package/src/skills-seed.mjs +79 -0
- package/src/sync-protocol.mjs +20 -0
- package/web/observer/app.mjs +107 -31
- package/web/observer/index.html +7 -0
- package/web/observer/styles.css +5 -0
|
@@ -17,6 +17,8 @@ import { parseSessionCost, } from './cost.mjs';
|
|
|
17
17
|
import { buildSessionIdentityMap, listMigrationDocuments, parseFrontmatter, sessionEvents } from './observer-sql-migrate.mjs';
|
|
18
18
|
import { observerAuthHeaders } from './observer-auth.mjs';
|
|
19
19
|
import { sanitizeObserverContent, sanitizeObserverMetadata } from './observer-privacy.mjs';
|
|
20
|
+
import { decryptObserverValue, encryptObserverValue } from '../packages/observer/src/encryption.mjs';
|
|
21
|
+
import { createObserverPolicy, protectObserverEvent } from '../packages/observer/src/policy.mjs';
|
|
20
22
|
|
|
21
23
|
export const SQL_OUTBOX_REL = '.brain/observer-sql-outbox';
|
|
22
24
|
export const SQL_STATE_REL = '.brain/observer-sql-state.json';
|
|
@@ -57,6 +59,18 @@ function isoNow(value) {
|
|
|
57
59
|
if (Number.isNaN(date.getTime())) throw new Error('occurred_at inválido.');
|
|
58
60
|
return date.toISOString();
|
|
59
61
|
}
|
|
62
|
+
function transcriptOccurredAt(value, fallback) {
|
|
63
|
+
const candidate = value === null || value === undefined
|
|
64
|
+
|| (typeof value === 'string' && !value.trim())
|
|
65
|
+
? fallback
|
|
66
|
+
: value;
|
|
67
|
+
if (!(candidate instanceof Date) && typeof candidate !== 'string' && typeof candidate !== 'number') {
|
|
68
|
+
throw new Error('occurred_at inválido.');
|
|
69
|
+
}
|
|
70
|
+
const date = candidate instanceof Date ? candidate : new Date(candidate);
|
|
71
|
+
if (Number.isNaN(date.getTime())) throw new Error('occurred_at inválido.');
|
|
72
|
+
return date.toISOString();
|
|
73
|
+
}
|
|
60
74
|
function readJson(path, fallback) {
|
|
61
75
|
if (!existsSync(path)) return fallback;
|
|
62
76
|
try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return fallback; }
|
|
@@ -167,12 +181,13 @@ function transcriptCalls({ projectId, sessionId, agentId, role, provider, modelF
|
|
|
167
181
|
? 'complete'
|
|
168
182
|
: hash({ prompt, response, tokens, status: turn.status }).slice(0, 16);
|
|
169
183
|
const callId = eventId('call', projectId, `${transcriptId}:${agentId}:${stableTurn}:${turnRevision}`);
|
|
184
|
+
const callOccurredAt = transcriptOccurredAt(turn.timestamp, occurredAt);
|
|
170
185
|
return [{
|
|
171
186
|
schema_version: 1,
|
|
172
187
|
event_id: eventId('call-event', projectId, callId),
|
|
173
188
|
kind: 'llm_call',
|
|
174
189
|
project_id: projectId,
|
|
175
|
-
occurred_at:
|
|
190
|
+
occurred_at: callOccurredAt,
|
|
176
191
|
payload: {
|
|
177
192
|
call_id: callId,
|
|
178
193
|
session_id: sessionId,
|
|
@@ -183,7 +198,7 @@ function transcriptCalls({ projectId, sessionId, agentId, role, provider, modelF
|
|
|
183
198
|
model,
|
|
184
199
|
effort: '',
|
|
185
200
|
sequence: index + 1,
|
|
186
|
-
occurred_at:
|
|
201
|
+
occurred_at: callOccurredAt,
|
|
187
202
|
tokens,
|
|
188
203
|
cost_usd: 0,
|
|
189
204
|
cost_status: 'unknown',
|
|
@@ -223,7 +238,7 @@ function completeTranscriptEvents({ projectId, sessionId, mainAgentId, provider,
|
|
|
223
238
|
? agentEvent({ projectId, sessionId, agentId, parentAgentId: mainAgentId, role: 'subagent', provider, model, input: source.agentInput, occurredAt: now })
|
|
224
239
|
: null;
|
|
225
240
|
const fingerprint = hash(content);
|
|
226
|
-
const transcript =
|
|
241
|
+
const transcript = {
|
|
227
242
|
schema_version: 1,
|
|
228
243
|
event_id: eventId('transcript', projectId, `${source.transcriptId}:${fingerprint}`),
|
|
229
244
|
kind: 'transcript.upsert',
|
|
@@ -238,7 +253,7 @@ function completeTranscriptEvents({ projectId, sessionId, mainAgentId, provider,
|
|
|
238
253
|
source: 'hook-transcript',
|
|
239
254
|
metadata: { source_label: basename(source.path), source_hash: hash(normalizePath(source.path)) },
|
|
240
255
|
},
|
|
241
|
-
}
|
|
256
|
+
};
|
|
242
257
|
const calls = transcriptCalls({
|
|
243
258
|
projectId,
|
|
244
259
|
sessionId,
|
|
@@ -249,7 +264,7 @@ function completeTranscriptEvents({ projectId, sessionId, mainAgentId, provider,
|
|
|
249
264
|
transcriptId: source.transcriptId,
|
|
250
265
|
content,
|
|
251
266
|
occurredAt: now,
|
|
252
|
-
includeMessages:
|
|
267
|
+
includeMessages: true,
|
|
253
268
|
});
|
|
254
269
|
return { events: [agent, transcript, ...calls].filter(Boolean), fingerprint, transcriptId: source.transcriptId };
|
|
255
270
|
}
|
|
@@ -263,10 +278,31 @@ function dedupeEvents(events) {
|
|
|
263
278
|
});
|
|
264
279
|
}
|
|
265
280
|
|
|
266
|
-
|
|
281
|
+
function applyObserverPolicy(events, policy) {
|
|
282
|
+
return events.map((event) => protectObserverEvent(event, { policy })).filter(Boolean);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function publisherPolicy(policy, captureLevel) {
|
|
286
|
+
if (policy) return createObserverPolicy(policy);
|
|
287
|
+
const legacy = normalizeObserverCaptureLevel(captureLevel);
|
|
288
|
+
return createObserverPolicy({
|
|
289
|
+
document_capture: 'full',
|
|
290
|
+
transcript_capture: legacy === 'full-transcript' ? 'full' : legacy,
|
|
291
|
+
prompt_capture: legacy === 'metadata' ? 'none' : 'full',
|
|
292
|
+
response_capture: legacy === 'metadata' ? 'none' : 'full',
|
|
293
|
+
usage_capture: 'calls',
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function publisherCoverage(policy) {
|
|
298
|
+
return `policy:${policy.transcript_capture}`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function buildObserverSqlEventBatch({ vaultBase, projectId, input = {}, now = new Date(), state = readState(vaultBase), remoteDocuments = {}, captureLevel = process.env.WENDKEEP_OBSERVER_CAPTURE_LEVEL || 'metadata', forceFull = false, policy = null } = {}) {
|
|
267
302
|
if (!vaultBase || !projectId) throw new Error('vaultBase e projectId são obrigatórios.');
|
|
268
303
|
const occurredAt = isoNow(now);
|
|
269
|
-
const
|
|
304
|
+
const effectivePolicy = publisherPolicy(policy, captureLevel);
|
|
305
|
+
const resolvedCaptureLevel = publisherCoverage(effectivePolicy);
|
|
270
306
|
const nextState = { schema_version: SQL_SCHEMA_VERSION, files: { ...(state.files || {}) }, transcripts: { ...(state.transcripts || {}) } };
|
|
271
307
|
const events = [];
|
|
272
308
|
const sessionContexts = [];
|
|
@@ -326,7 +362,7 @@ export function buildObserverSqlEventBatch({ vaultBase, projectId, input = {}, n
|
|
|
326
362
|
nextState.transcripts[context.fm.observability_transcript_id] ||= { content_hash: '', coverage: 'summary_only' };
|
|
327
363
|
}
|
|
328
364
|
}
|
|
329
|
-
return { events: dedupeEvents(events), nextState, scanned: Object.keys(nextState.files).length, changed };
|
|
365
|
+
return { events: applyObserverPolicy(dedupeEvents(events), effectivePolicy), nextState, scanned: Object.keys(nextState.files).length, changed };
|
|
330
366
|
}
|
|
331
367
|
|
|
332
368
|
function hookEventName(input = {}) {
|
|
@@ -366,6 +402,7 @@ export function buildObserverSqlIncrementalBatch({
|
|
|
366
402
|
now = new Date(),
|
|
367
403
|
state = readState(vaultBase),
|
|
368
404
|
captureLevel = process.env.WENDKEEP_OBSERVER_CAPTURE_LEVEL || 'metadata',
|
|
405
|
+
policy = null,
|
|
369
406
|
} = {}) {
|
|
370
407
|
if (!vaultBase || !projectId) throw new Error('vaultBase e projectId são obrigatórios.');
|
|
371
408
|
const eventName = hookEventName(input);
|
|
@@ -381,7 +418,8 @@ export function buildObserverSqlIncrementalBatch({
|
|
|
381
418
|
if (!context) return { events: [], nextState, scanned: 0, changed: 0, scope: 'drain-only' };
|
|
382
419
|
|
|
383
420
|
const occurredAt = isoNow(now);
|
|
384
|
-
const
|
|
421
|
+
const effectivePolicy = publisherPolicy(policy, captureLevel);
|
|
422
|
+
const resolvedCaptureLevel = publisherCoverage(effectivePolicy);
|
|
385
423
|
const events = [];
|
|
386
424
|
let changed = 0;
|
|
387
425
|
const safeContent = sanitizeObserverContent(context.content);
|
|
@@ -451,7 +489,7 @@ export function buildObserverSqlIncrementalBatch({
|
|
|
451
489
|
changed += 1;
|
|
452
490
|
}
|
|
453
491
|
return {
|
|
454
|
-
events: dedupeEvents(events),
|
|
492
|
+
events: applyObserverPolicy(dedupeEvents(events), effectivePolicy),
|
|
455
493
|
nextState,
|
|
456
494
|
scanned: 1,
|
|
457
495
|
changed,
|
|
@@ -459,10 +497,30 @@ export function buildObserverSqlIncrementalBatch({
|
|
|
459
497
|
};
|
|
460
498
|
}
|
|
461
499
|
|
|
462
|
-
function
|
|
500
|
+
function outboxAad(path) { return `observer-outbox:${basename(path)}`; }
|
|
501
|
+
|
|
502
|
+
function writeOutbox(path, batch, encryption = null) {
|
|
503
|
+
if (!encryption) return atomicJson(path, batch);
|
|
504
|
+
return atomicJson(path, {
|
|
505
|
+
schema_version: 1,
|
|
506
|
+
protected: true,
|
|
507
|
+
envelope: encryptObserverValue(encryption, JSON.stringify(batch), { aad: outboxAad(path) }),
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function readOutbox(path, fallback = null, encryption = null) {
|
|
512
|
+
const stored = readJson(path, fallback);
|
|
513
|
+
if (!stored?.protected) return stored;
|
|
514
|
+
if (!encryption) {
|
|
515
|
+
throw Object.assign(new Error('Chave da outbox protegida indisponível.'), { code: 'observer_encryption_key_unavailable' });
|
|
516
|
+
}
|
|
517
|
+
return JSON.parse(decryptObserverValue(encryption, stored.envelope, { aad: outboxAad(path) }));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function queueOutbox(vaultBase, batch, encryption = null) {
|
|
463
521
|
mkdirSync(outboxDir(vaultBase), { recursive: true });
|
|
464
522
|
const path = outboxPath(vaultBase, batch);
|
|
465
|
-
if (!existsSync(path))
|
|
523
|
+
if (!existsSync(path)) writeOutbox(path, batch, encryption);
|
|
466
524
|
return path;
|
|
467
525
|
}
|
|
468
526
|
|
|
@@ -521,29 +579,32 @@ function releaseBatchFileLease(lease) {
|
|
|
521
579
|
}
|
|
522
580
|
|
|
523
581
|
/** Queue a precise writer batch and replace older pending state for the same logical scope. */
|
|
524
|
-
export function enqueueObserverSqlBatch(vaultBase, batch, { scope = 'incremental', now = new Date() } = {}) {
|
|
582
|
+
export function enqueueObserverSqlBatch(vaultBase, batch, { scope = 'incremental', now = new Date(), encryption = null, policy = null } = {}) {
|
|
525
583
|
if (!batch?.project_id || !Array.isArray(batch.events)) throw new Error('Batch incremental do Observer inválido.');
|
|
584
|
+
if (policy?.encryption_required && !encryption) {
|
|
585
|
+
throw Object.assign(new Error('A policy exige criptografia antes de enfileirar eventos do Observer.'), { code: 'observer_encryption_required' });
|
|
586
|
+
}
|
|
526
587
|
if (!batch.events.length) return { queued: false, events: 0, path: '' };
|
|
527
588
|
mkdirSync(outboxDir(vaultBase), { recursive: true });
|
|
528
589
|
const path = join(outboxDir(vaultBase), `sql-live-${hash(`${batch.project_id}:${scope}`).slice(0, 24)}.json`);
|
|
529
590
|
const lease = acquireBatchFileLease(path, 200);
|
|
530
591
|
if (!lease) {
|
|
531
|
-
const fallback = queueOutbox(vaultBase, { ...batch, enqueued_at: isoNow(now), scope });
|
|
592
|
+
const fallback = queueOutbox(vaultBase, { ...batch, enqueued_at: isoNow(now), scope }, encryption);
|
|
532
593
|
return { queued: true, events: batch.events.length, path: fallback, coalesced: false };
|
|
533
594
|
}
|
|
534
595
|
try {
|
|
535
|
-
const existing =
|
|
596
|
+
const existing = existsSync(path) ? readOutbox(path, { events: [] }, encryption) : { events: [] };
|
|
536
597
|
const merged = new Map();
|
|
537
598
|
for (const event of [...(existing.events || []), ...batch.events]) merged.set(eventCoalesceKey(event), event);
|
|
538
599
|
const timestamp = isoNow(now);
|
|
539
|
-
|
|
600
|
+
writeOutbox(path, {
|
|
540
601
|
schema_version: SQL_SCHEMA_VERSION,
|
|
541
602
|
project_id: batch.project_id,
|
|
542
603
|
scope,
|
|
543
604
|
enqueued_at: existing.enqueued_at || timestamp,
|
|
544
605
|
updated_at: timestamp,
|
|
545
606
|
events: [...merged.values()],
|
|
546
|
-
});
|
|
607
|
+
}, encryption);
|
|
547
608
|
return { queued: true, events: merged.size, path, coalesced: true };
|
|
548
609
|
} finally {
|
|
549
610
|
releaseBatchFileLease(lease);
|
|
@@ -557,6 +618,7 @@ export function enqueueObserverDocumentChange({
|
|
|
557
618
|
logicalPath,
|
|
558
619
|
deleted = false,
|
|
559
620
|
now = new Date(),
|
|
621
|
+
outboxEncryption = null,
|
|
560
622
|
} = {}) {
|
|
561
623
|
const normalized = normalizePath(logicalPath);
|
|
562
624
|
if (!vaultBase || !projectId || !normalized || normalized.startsWith('/') || normalized.includes('..')) {
|
|
@@ -599,21 +661,26 @@ export function enqueueObserverDocumentChange({
|
|
|
599
661
|
schema_version: SQL_SCHEMA_VERSION,
|
|
600
662
|
project_id: projectId,
|
|
601
663
|
events: [event],
|
|
602
|
-
}, { scope: `document:${normalized}`, now });
|
|
664
|
+
}, { scope: `document:${normalized}`, now, encryption: outboxEncryption });
|
|
603
665
|
atomicJson(statePath(vaultBase), nextState);
|
|
604
666
|
return { ...queued, event_id: event.event_id, deleted };
|
|
605
667
|
}
|
|
606
668
|
|
|
607
|
-
export function listSqlOutbox(vaultBase) {
|
|
669
|
+
export function listSqlOutbox(vaultBase, { encryption = null } = {}) {
|
|
608
670
|
const dir = outboxDir(vaultBase);
|
|
609
671
|
if (!existsSync(dir)) return [];
|
|
610
672
|
return readdirSync(dir).filter((name) => /^sql-(?:live-)?[a-f0-9]{24}\.json$/.test(name)).sort().flatMap((name) => {
|
|
611
|
-
|
|
673
|
+
const path = join(dir, name);
|
|
674
|
+
try { return [{ path, ...readOutbox(path, null, encryption) }]; }
|
|
675
|
+
catch (error) {
|
|
676
|
+
if (error?.code === 'observer_encryption_key_unavailable' || error?.code === 'observer_decryption_failed') throw error;
|
|
677
|
+
return [];
|
|
678
|
+
}
|
|
612
679
|
});
|
|
613
680
|
}
|
|
614
681
|
|
|
615
|
-
export function inspectObserverSqlOutbox(vaultBase, currentTime = Date.now()) {
|
|
616
|
-
const batches = listSqlOutbox(vaultBase);
|
|
682
|
+
export function inspectObserverSqlOutbox(vaultBase, currentTime = Date.now(), { encryption = null } = {}) {
|
|
683
|
+
const batches = listSqlOutbox(vaultBase, { encryption });
|
|
617
684
|
let bytes = 0;
|
|
618
685
|
let oldestAt = '';
|
|
619
686
|
let events = 0;
|
|
@@ -720,8 +787,8 @@ async function postSqlBatch({ url, projectId, events, fetchImpl = globalThis.fet
|
|
|
720
787
|
return aggregate;
|
|
721
788
|
}
|
|
722
789
|
|
|
723
|
-
export async function retryObserverSqlOutbox({ vaultBase, projectId, url, fetchImpl = globalThis.fetch, token = process.env.WENDKEEP_OBSERVER_TOKEN || '' } = {}) {
|
|
724
|
-
const pending = listSqlOutbox(vaultBase);
|
|
790
|
+
export async function retryObserverSqlOutbox({ vaultBase, projectId, url, fetchImpl = globalThis.fetch, token = process.env.WENDKEEP_OBSERVER_TOKEN || '', outboxEncryption = null } = {}) {
|
|
791
|
+
const pending = listSqlOutbox(vaultBase, { encryption: outboxEncryption });
|
|
725
792
|
if (!url) return { attempted: 0, confirmed: 0, pending: pending.length };
|
|
726
793
|
const lease = acquirePublisherLease(vaultBase);
|
|
727
794
|
if (!lease) return { attempted: 0, confirmed: 0, pending: pending.length, busy: true };
|
|
@@ -734,7 +801,7 @@ export async function retryObserverSqlOutbox({ vaultBase, projectId, url, fetchI
|
|
|
734
801
|
if (!batchLease) continue;
|
|
735
802
|
attempted += 1;
|
|
736
803
|
try {
|
|
737
|
-
const current =
|
|
804
|
+
const current = readOutbox(batch.path, null, outboxEncryption);
|
|
738
805
|
if (!current?.events?.length) continue;
|
|
739
806
|
await postSqlBatch({ url, projectId, events: current.events, fetchImpl, token });
|
|
740
807
|
if (existsSync(batch.path)) unlinkSync(batch.path);
|
|
@@ -742,7 +809,7 @@ export async function retryObserverSqlOutbox({ vaultBase, projectId, url, fetchI
|
|
|
742
809
|
} catch { break; }
|
|
743
810
|
finally { releaseBatchFileLease(batchLease); }
|
|
744
811
|
}
|
|
745
|
-
return { attempted, confirmed, pending: listSqlOutbox(vaultBase).length, busy: false };
|
|
812
|
+
return { attempted, confirmed, pending: listSqlOutbox(vaultBase, { encryption: outboxEncryption }).length, busy: false };
|
|
746
813
|
} finally {
|
|
747
814
|
releasePublisherLease(lease);
|
|
748
815
|
}
|
|
@@ -758,20 +825,25 @@ export async function publishObserverSqlIncremental({
|
|
|
758
825
|
fetchImpl = globalThis.fetch,
|
|
759
826
|
token = process.env.WENDKEEP_OBSERVER_TOKEN || '',
|
|
760
827
|
captureLevel = process.env.WENDKEEP_OBSERVER_CAPTURE_LEVEL || 'metadata',
|
|
828
|
+
outboxEncryption = null,
|
|
829
|
+
policy = null,
|
|
761
830
|
} = {}) {
|
|
831
|
+
if (policy?.encryption_required && !outboxEncryption) {
|
|
832
|
+
throw Object.assign(new Error('A policy exige criptografia da outbox do Observer.'), { code: 'observer_encryption_required' });
|
|
833
|
+
}
|
|
762
834
|
if (!vaultBase || !projectId) throw new Error('vaultBase e projectId são obrigatórios.');
|
|
763
835
|
const state = readState(vaultBase);
|
|
764
836
|
const batch = buildObserverSqlIncrementalBatch({
|
|
765
|
-
vaultBase, projectId, input, now, state, captureLevel,
|
|
837
|
+
vaultBase, projectId, input, now, state, captureLevel, policy,
|
|
766
838
|
});
|
|
767
839
|
const queued = enqueueObserverSqlBatch(vaultBase, {
|
|
768
840
|
schema_version: SQL_SCHEMA_VERSION,
|
|
769
841
|
project_id: projectId,
|
|
770
842
|
events: batch.events,
|
|
771
|
-
}, { scope: batch.scope, now });
|
|
843
|
+
}, { scope: batch.scope, now, encryption: outboxEncryption, policy });
|
|
772
844
|
if (batch.events.length) atomicJson(statePath(vaultBase), batch.nextState);
|
|
773
|
-
const replay = await retryObserverSqlOutbox({ vaultBase, projectId, url, fetchImpl, token });
|
|
774
|
-
const pending = listSqlOutbox(vaultBase).length;
|
|
845
|
+
const replay = await retryObserverSqlOutbox({ vaultBase, projectId, url, fetchImpl, token, outboxEncryption });
|
|
846
|
+
const pending = listSqlOutbox(vaultBase, { encryption: outboxEncryption }).length;
|
|
775
847
|
return {
|
|
776
848
|
ok: pending === 0,
|
|
777
849
|
queued: pending > 0,
|
|
@@ -784,30 +856,33 @@ export async function publishObserverSqlIncremental({
|
|
|
784
856
|
};
|
|
785
857
|
}
|
|
786
858
|
|
|
787
|
-
export async function publishObserverSql({ vaultBase, projectId, url = process.env.WENDKEEP_OBSERVER_URL || '', input = {}, now = new Date(), fetchImpl = globalThis.fetch, token = process.env.WENDKEEP_OBSERVER_TOKEN || '', captureLevel = process.env.WENDKEEP_OBSERVER_CAPTURE_LEVEL || 'metadata', forceFull = false } = {}) {
|
|
859
|
+
export async function publishObserverSql({ vaultBase, projectId, url = process.env.WENDKEEP_OBSERVER_URL || '', input = {}, now = new Date(), fetchImpl = globalThis.fetch, token = process.env.WENDKEEP_OBSERVER_TOKEN || '', captureLevel = process.env.WENDKEEP_OBSERVER_CAPTURE_LEVEL || 'metadata', forceFull = false, outboxEncryption = null, policy = null } = {}) {
|
|
860
|
+
if (policy?.encryption_required && !outboxEncryption) {
|
|
861
|
+
throw Object.assign(new Error('A policy exige criptografia da outbox do Observer.'), { code: 'observer_encryption_required' });
|
|
862
|
+
}
|
|
788
863
|
if (!vaultBase || !projectId) throw new Error('vaultBase e projectId são obrigatórios.');
|
|
789
|
-
const replay = await retryObserverSqlOutbox({ vaultBase, projectId, url, fetchImpl, token });
|
|
864
|
+
const replay = await retryObserverSqlOutbox({ vaultBase, projectId, url, fetchImpl, token, outboxEncryption });
|
|
790
865
|
const state = readState(vaultBase);
|
|
791
866
|
const remoteDocuments = forceFull || Object.keys(state.files || {}).length === 0
|
|
792
867
|
? await readRemoteDocuments({ url, projectId, fetchImpl, token })
|
|
793
868
|
: {};
|
|
794
|
-
const batch = buildObserverSqlEventBatch({ vaultBase, projectId, input, now, state, remoteDocuments, captureLevel, forceFull });
|
|
869
|
+
const batch = buildObserverSqlEventBatch({ vaultBase, projectId, input, now, state, remoteDocuments, captureLevel, forceFull, policy });
|
|
795
870
|
if (!batch.events.length) {
|
|
796
871
|
atomicJson(statePath(vaultBase), batch.nextState);
|
|
797
|
-
return { ok: true, queued: false, scanned: batch.scanned, changed: batch.changed, pending: listSqlOutbox(vaultBase).length, replay };
|
|
872
|
+
return { ok: true, queued: false, scanned: batch.scanned, changed: batch.changed, pending: listSqlOutbox(vaultBase, { encryption: outboxEncryption }).length, replay };
|
|
798
873
|
}
|
|
799
874
|
if (!url) {
|
|
800
|
-
queueOutbox(vaultBase, { schema_version: SQL_SCHEMA_VERSION, project_id: projectId, events: batch.events });
|
|
875
|
+
queueOutbox(vaultBase, { schema_version: SQL_SCHEMA_VERSION, project_id: projectId, events: batch.events }, outboxEncryption);
|
|
801
876
|
atomicJson(statePath(vaultBase), batch.nextState);
|
|
802
|
-
return { ok: false, queued: true, scanned: batch.scanned, changed: batch.changed, pending: listSqlOutbox(vaultBase).length, replay, hookExitCode: 0 };
|
|
877
|
+
return { ok: false, queued: true, scanned: batch.scanned, changed: batch.changed, pending: listSqlOutbox(vaultBase, { encryption: outboxEncryption }).length, replay, hookExitCode: 0 };
|
|
803
878
|
}
|
|
804
879
|
try {
|
|
805
880
|
const response = await postSqlBatch({ url, projectId, events: batch.events, fetchImpl, token });
|
|
806
881
|
atomicJson(statePath(vaultBase), batch.nextState);
|
|
807
|
-
return { ok: true, queued: false, scanned: batch.scanned, changed: batch.changed, pending: listSqlOutbox(vaultBase).length, replay, response };
|
|
882
|
+
return { ok: true, queued: false, scanned: batch.scanned, changed: batch.changed, pending: listSqlOutbox(vaultBase, { encryption: outboxEncryption }).length, replay, response };
|
|
808
883
|
} catch (error) {
|
|
809
|
-
queueOutbox(vaultBase, { schema_version: SQL_SCHEMA_VERSION, project_id: projectId, events: batch.events });
|
|
884
|
+
queueOutbox(vaultBase, { schema_version: SQL_SCHEMA_VERSION, project_id: projectId, events: batch.events }, outboxEncryption);
|
|
810
885
|
atomicJson(statePath(vaultBase), batch.nextState);
|
|
811
|
-
return { ok: false, queued: true, scanned: batch.scanned, changed: batch.changed, pending: listSqlOutbox(vaultBase).length, replay, hookExitCode: 0, error: error.message };
|
|
886
|
+
return { ok: false, queued: true, scanned: batch.scanned, changed: batch.changed, pending: listSqlOutbox(vaultBase, { encryption: outboxEncryption }).length, replay, hookExitCode: 0, error: error.message };
|
|
812
887
|
}
|
|
813
888
|
}
|