hippo-memory 0.39.0 → 1.0.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/README.md +12 -0
- package/dist/cli.js +80 -17
- package/dist/cli.js.map +1 -1
- package/dist/connectors/slack/transform.d.ts +2 -0
- package/dist/connectors/slack/transform.d.ts.map +1 -1
- package/dist/connectors/slack/transform.js +3 -0
- package/dist/connectors/slack/transform.js.map +1 -1
- package/dist/consolidate.d.ts.map +1 -1
- package/dist/consolidate.js +9 -3
- package/dist/consolidate.js.map +1 -1
- package/dist/correction-latency.d.ts +36 -0
- package/dist/correction-latency.d.ts.map +1 -0
- package/dist/correction-latency.js +74 -0
- package/dist/correction-latency.js.map +1 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +87 -1
- package/dist/db.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/provenance-coverage.d.ts +18 -0
- package/dist/provenance-coverage.d.ts.map +1 -0
- package/dist/provenance-coverage.js +23 -0
- package/dist/provenance-coverage.js.map +1 -0
- package/dist/src/cli.js +80 -17
- package/dist/src/cli.js.map +1 -1
- package/dist/src/connectors/slack/transform.js +3 -0
- package/dist/src/connectors/slack/transform.js.map +1 -1
- package/dist/src/consolidate.js +9 -3
- package/dist/src/consolidate.js.map +1 -1
- package/dist/src/correction-latency.js +74 -0
- package/dist/src/correction-latency.js.map +1 -0
- package/dist/src/db.js +87 -1
- package/dist/src/db.js.map +1 -1
- package/dist/src/mcp/server.js +1 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/provenance-coverage.js +23 -0
- package/dist/src/provenance-coverage.js.map +1 -0
- package/dist/src/store.js +105 -52
- package/dist/src/store.js.map +1 -1
- package/dist/store.d.ts +10 -10
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +105 -52
- package/dist/store.js.map +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,6 +85,18 @@ hippo recall "data pipeline issues" --budget 2000
|
|
|
85
85
|
|
|
86
86
|
---
|
|
87
87
|
|
|
88
|
+
### What's new in v1.0.0
|
|
89
|
+
|
|
90
|
+
- **Tenant-isolation security release.** v0.40.0's measurement gates surfaced a real cross-tenant data leak on the continuity tables (`task_snapshots`, `session_events`, `session_handoffs`). Schema migration v22 closes the gap: every continuity helper now scopes reads and writes by `tenantId`. Markdown mirror files (`buffer/active-task.md`, `buffer/recent-session.md`) are tenant-scoped too; the default tenant keeps the unsuffixed filename for on-disk back-compat.
|
|
91
|
+
- **Slack envelope fix.** `messageToRememberOpts` now sets `owner: 'user:<slack_user_id>'` so ingested Slack rows pass the v0.40.0 `hippo provenance --strict` gate.
|
|
92
|
+
- **Breaking change for JS callers.** 10 store helpers (`saveActiveTaskSnapshot`, `loadLatestHandoff`, `appendSessionEvent`, etc.) gained a required `tenantId` second argument. TypeScript callers get a compile error. JS callers get a runtime guard error (`assertTenantId`) that detects the most common misbinding (passing a `sess-*` session id) and points at the migration. See CHANGELOG for the full helper list.
|
|
93
|
+
- **Schema v22 migration.** Idempotent, transactional, with smart tenant backfill via unambiguous `task_snapshots.session_id` joins.
|
|
94
|
+
|
|
95
|
+
### What's new in v0.40.0
|
|
96
|
+
|
|
97
|
+
- **Company Brain measurement gates.** Two new diagnostic commands close the last blocked rows of the Company Brain scorecard. `hippo provenance [--json] [--strict]` audits every `kind='raw'` row for `owner` + `artifact_ref`; `--strict` exits non-zero so CI can block on coverage regressions. `hippo correction-latency [--json]` reports p50 / p95 / max wall-clock lag from receipt to supersession across `superseded_by` chains. Both helpers (`buildProvenanceCoverage`, `buildCorrectionLatency`) are importable from `src/`.
|
|
98
|
+
- **No behavioral change to remember / recall.** Additive only: schema unchanged, retrieval untouched.
|
|
99
|
+
|
|
88
100
|
### What's new in v0.39.0
|
|
89
101
|
|
|
90
102
|
- Security hardening release: 5 CRITICAL cross-tenant fixes (CVE candidates), GDPR Path A on archive (true RTBF), MCP per-client isolation, Slack ingestion race + idempotency hardening, auth timing leak reduction.
|
package/dist/cli.js
CHANGED
|
@@ -53,6 +53,8 @@ import { importChatGPT, importClaude, importCursor, importGenericFile, importMar
|
|
|
53
53
|
import { cmdCapture } from './capture.js';
|
|
54
54
|
import { auditMemories, appendAuditEvent, } from './audit.js';
|
|
55
55
|
import { listApiKeys, revokeApiKey } from './auth.js';
|
|
56
|
+
import { buildProvenanceCoverage } from './provenance-coverage.js';
|
|
57
|
+
import { buildCorrectionLatency } from './correction-latency.js';
|
|
56
58
|
import * as api from './api.js';
|
|
57
59
|
import * as client from './client.js';
|
|
58
60
|
import { detectServer, removePidfile } from './server-detect.js';
|
|
@@ -2498,7 +2500,7 @@ function cmdSnapshot(hippoRoot, args, flags) {
|
|
|
2498
2500
|
console.error('Usage: hippo snapshot save --task <task> --summary <summary> --next-step <step> [--source <source>] [--session <session-id>]');
|
|
2499
2501
|
process.exit(1);
|
|
2500
2502
|
}
|
|
2501
|
-
const snapshot = saveActiveTaskSnapshot(hippoRoot, {
|
|
2503
|
+
const snapshot = saveActiveTaskSnapshot(hippoRoot, resolveTenantId({}), {
|
|
2502
2504
|
task,
|
|
2503
2505
|
summary,
|
|
2504
2506
|
next_step: nextStep,
|
|
@@ -2514,7 +2516,7 @@ function cmdSnapshot(hippoRoot, args, flags) {
|
|
|
2514
2516
|
return;
|
|
2515
2517
|
}
|
|
2516
2518
|
if (subcommand === 'clear') {
|
|
2517
|
-
const cleared = clearActiveTaskSnapshot(hippoRoot, String(flags['status'] ?? 'cleared'));
|
|
2519
|
+
const cleared = clearActiveTaskSnapshot(hippoRoot, resolveTenantId({}), String(flags['status'] ?? 'cleared'));
|
|
2518
2520
|
if (!cleared) {
|
|
2519
2521
|
console.log('No active task snapshot to clear.');
|
|
2520
2522
|
return;
|
|
@@ -2523,7 +2525,7 @@ function cmdSnapshot(hippoRoot, args, flags) {
|
|
|
2523
2525
|
return;
|
|
2524
2526
|
}
|
|
2525
2527
|
if (subcommand === 'show') {
|
|
2526
|
-
const snapshot = loadActiveTaskSnapshot(hippoRoot);
|
|
2528
|
+
const snapshot = loadActiveTaskSnapshot(hippoRoot, resolveTenantId({}));
|
|
2527
2529
|
if (!snapshot) {
|
|
2528
2530
|
if (flags['json']) {
|
|
2529
2531
|
console.log(JSON.stringify({ snapshot: null }));
|
|
@@ -2556,7 +2558,7 @@ function cmdSession(hippoRoot, args, flags) {
|
|
|
2556
2558
|
console.error('Usage: hippo session log --id <session-id> --content <text> [--type <type>] [--task <task>] [--source <source>]');
|
|
2557
2559
|
process.exit(1);
|
|
2558
2560
|
}
|
|
2559
|
-
const event = appendSessionEvent(hippoRoot, {
|
|
2561
|
+
const event = appendSessionEvent(hippoRoot, resolveTenantId({}), {
|
|
2560
2562
|
session_id: sessionId,
|
|
2561
2563
|
task: task || null,
|
|
2562
2564
|
event_type: eventType || 'note',
|
|
@@ -2569,7 +2571,7 @@ function cmdSession(hippoRoot, args, flags) {
|
|
|
2569
2571
|
return;
|
|
2570
2572
|
}
|
|
2571
2573
|
if (subcommand === 'show') {
|
|
2572
|
-
const events = listSessionEvents(hippoRoot, {
|
|
2574
|
+
const events = listSessionEvents(hippoRoot, resolveTenantId({}), {
|
|
2573
2575
|
session_id: sessionId || undefined,
|
|
2574
2576
|
task: task || undefined,
|
|
2575
2577
|
limit,
|
|
@@ -2582,8 +2584,8 @@ function cmdSession(hippoRoot, args, flags) {
|
|
|
2582
2584
|
return;
|
|
2583
2585
|
}
|
|
2584
2586
|
if (subcommand === 'latest') {
|
|
2585
|
-
const snapshot = loadActiveTaskSnapshot(hippoRoot);
|
|
2586
|
-
const events = listSessionEvents(hippoRoot, {
|
|
2587
|
+
const snapshot = loadActiveTaskSnapshot(hippoRoot, resolveTenantId({}));
|
|
2588
|
+
const events = listSessionEvents(hippoRoot, resolveTenantId({}), {
|
|
2587
2589
|
session_id: sessionId || snapshot?.session_id || undefined,
|
|
2588
2590
|
limit,
|
|
2589
2591
|
});
|
|
@@ -2616,7 +2618,7 @@ function cmdSession(hippoRoot, args, flags) {
|
|
|
2616
2618
|
const metadata = { ended_at: new Date().toISOString() };
|
|
2617
2619
|
if (summary)
|
|
2618
2620
|
metadata.summary = summary;
|
|
2619
|
-
const event = appendSessionEvent(hippoRoot, {
|
|
2621
|
+
const event = appendSessionEvent(hippoRoot, resolveTenantId({}), {
|
|
2620
2622
|
session_id: sessionId,
|
|
2621
2623
|
task: task || null,
|
|
2622
2624
|
event_type: 'session_complete',
|
|
@@ -2628,7 +2630,7 @@ function cmdSession(hippoRoot, args, flags) {
|
|
|
2628
2630
|
return;
|
|
2629
2631
|
}
|
|
2630
2632
|
if (subcommand === 'resume') {
|
|
2631
|
-
const handoff = loadLatestHandoff(hippoRoot, sessionId || undefined);
|
|
2633
|
+
const handoff = loadLatestHandoff(hippoRoot, resolveTenantId({}), sessionId || undefined);
|
|
2632
2634
|
if (!handoff) {
|
|
2633
2635
|
console.log('No handoff to resume from.');
|
|
2634
2636
|
return;
|
|
@@ -2701,7 +2703,7 @@ function cmdHandoff(hippoRoot, args, flags) {
|
|
|
2701
2703
|
const artifacts = Array.isArray(artifactFlag)
|
|
2702
2704
|
? artifactFlag
|
|
2703
2705
|
: (typeof artifactFlag === 'string' ? [artifactFlag] : []);
|
|
2704
|
-
const handoff = saveSessionHandoff(hippoRoot, {
|
|
2706
|
+
const handoff = saveSessionHandoff(hippoRoot, resolveTenantId({}), {
|
|
2705
2707
|
version: 1,
|
|
2706
2708
|
sessionId,
|
|
2707
2709
|
repoRoot: process.cwd(),
|
|
@@ -2721,7 +2723,7 @@ function cmdHandoff(hippoRoot, args, flags) {
|
|
|
2721
2723
|
}
|
|
2722
2724
|
if (subcommand === 'latest') {
|
|
2723
2725
|
const sessionId = String(flags['session'] ?? flags['id'] ?? '').trim() || undefined;
|
|
2724
|
-
const handoff = loadLatestHandoff(hippoRoot, sessionId);
|
|
2726
|
+
const handoff = loadLatestHandoff(hippoRoot, resolveTenantId({}), sessionId);
|
|
2725
2727
|
if (!handoff) {
|
|
2726
2728
|
if (flags['json']) {
|
|
2727
2729
|
console.log(JSON.stringify({ handoff: null }));
|
|
@@ -2749,7 +2751,7 @@ function cmdHandoff(hippoRoot, args, flags) {
|
|
|
2749
2751
|
console.error(`Invalid handoff ID: ${idArg}`);
|
|
2750
2752
|
process.exit(1);
|
|
2751
2753
|
}
|
|
2752
|
-
const handoff = loadHandoffById(hippoRoot, handoffId);
|
|
2754
|
+
const handoff = loadHandoffById(hippoRoot, resolveTenantId({}), handoffId);
|
|
2753
2755
|
if (!handoff) {
|
|
2754
2756
|
if (flags['json']) {
|
|
2755
2757
|
console.log(JSON.stringify({ handoff: null }));
|
|
@@ -2774,9 +2776,9 @@ function cmdCurrent(hippoRoot, args, flags) {
|
|
|
2774
2776
|
const subcommand = args[0] ?? 'show';
|
|
2775
2777
|
if (subcommand === 'show') {
|
|
2776
2778
|
const asJson = Boolean(flags['json']);
|
|
2777
|
-
const snapshot = loadActiveTaskSnapshot(hippoRoot);
|
|
2779
|
+
const snapshot = loadActiveTaskSnapshot(hippoRoot, resolveTenantId({}));
|
|
2778
2780
|
const sessionId = snapshot?.session_id ?? undefined;
|
|
2779
|
-
const events = listSessionEvents(hippoRoot, {
|
|
2781
|
+
const events = listSessionEvents(hippoRoot, resolveTenantId({}), {
|
|
2780
2782
|
session_id: sessionId,
|
|
2781
2783
|
limit: 5,
|
|
2782
2784
|
});
|
|
@@ -2865,12 +2867,12 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
2865
2867
|
let totalTokens = 0;
|
|
2866
2868
|
// Task snapshots / session events live in the local store. Skip when
|
|
2867
2869
|
// local isn't initialized — loading would auto-create .hippo in the cwd.
|
|
2868
|
-
const activeSnapshot = hasLocal ? loadActiveTaskSnapshot(hippoRoot) : null;
|
|
2870
|
+
const activeSnapshot = hasLocal ? loadActiveTaskSnapshot(hippoRoot, resolveTenantId({})) : null;
|
|
2869
2871
|
const sessionHandoff = hasLocal && activeSnapshot?.session_id
|
|
2870
|
-
? loadLatestHandoff(hippoRoot, activeSnapshot.session_id)
|
|
2872
|
+
? loadLatestHandoff(hippoRoot, resolveTenantId({}), activeSnapshot.session_id)
|
|
2871
2873
|
: null;
|
|
2872
2874
|
const recentSessionEvents = hasLocal && activeSnapshot?.session_id
|
|
2873
|
-
? listSessionEvents(hippoRoot, { session_id: activeSnapshot.session_id, limit: 5 })
|
|
2875
|
+
? listSessionEvents(hippoRoot, resolveTenantId({}), { session_id: activeSnapshot.session_id, limit: 5 })
|
|
2874
2876
|
: [];
|
|
2875
2877
|
if (localEntries.length === 0 && globalEntries.length === 0 && !activeSnapshot && !sessionHandoff && recentSessionEvents.length === 0) {
|
|
2876
2878
|
return;
|
|
@@ -4621,6 +4623,11 @@ Commands:
|
|
|
4621
4623
|
--threshold <n> Overlap threshold 0-1 (default: 0.7)
|
|
4622
4624
|
status Show memory health stats
|
|
4623
4625
|
audit [--fix] Check memory quality (--fix removes junk)
|
|
4626
|
+
provenance Provenance coverage gate for kind='raw' rows
|
|
4627
|
+
--json Output as JSON
|
|
4628
|
+
--strict Exit non-zero when coverage < 100%
|
|
4629
|
+
correction-latency Wall-clock lag from receipt to supersession (p50/p95/max)
|
|
4630
|
+
--json Output as JSON
|
|
4624
4631
|
outcome Apply feedback to last recall
|
|
4625
4632
|
--good Memories were helpful
|
|
4626
4633
|
--bad Memories were irrelevant
|
|
@@ -5008,6 +5015,62 @@ async function main() {
|
|
|
5008
5015
|
}
|
|
5009
5016
|
break;
|
|
5010
5017
|
}
|
|
5018
|
+
case 'correction-latency': {
|
|
5019
|
+
requireInit(hippoRoot);
|
|
5020
|
+
const entries = loadAllEntries(hippoRoot);
|
|
5021
|
+
const report = buildCorrectionLatency(entries);
|
|
5022
|
+
if (flags['json']) {
|
|
5023
|
+
console.log(JSON.stringify(report, null, 2));
|
|
5024
|
+
}
|
|
5025
|
+
else if (report.count === 0) {
|
|
5026
|
+
console.log('No supersessions found. Correction latency is undefined.');
|
|
5027
|
+
}
|
|
5028
|
+
else {
|
|
5029
|
+
const fmt = (ms) => {
|
|
5030
|
+
if (ms === null)
|
|
5031
|
+
return 'n/a';
|
|
5032
|
+
if (ms < 1000)
|
|
5033
|
+
return `${ms}ms`;
|
|
5034
|
+
if (ms < 60_000)
|
|
5035
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
5036
|
+
if (ms < 3_600_000)
|
|
5037
|
+
return `${(ms / 60_000).toFixed(1)}m`;
|
|
5038
|
+
return `${(ms / 3_600_000).toFixed(1)}h`;
|
|
5039
|
+
};
|
|
5040
|
+
console.log(`Corrections: ${report.count} total (${report.extractionCount} extraction-driven, ${report.manualCount} manual)`);
|
|
5041
|
+
console.log(`Latency p50: ${fmt(report.p50Ms)}, p95: ${fmt(report.p95Ms)}, max: ${fmt(report.maxMs)}`);
|
|
5042
|
+
if (report.extractionCount === 0 && report.manualCount > 0) {
|
|
5043
|
+
console.log(`\nAll ${report.manualCount} corrections were manual supersedes: no measurable observation lag.`);
|
|
5044
|
+
console.log(`To measure latency, route corrections through extraction (set new.extracted_from to the raw receipt).`);
|
|
5045
|
+
}
|
|
5046
|
+
}
|
|
5047
|
+
break;
|
|
5048
|
+
}
|
|
5049
|
+
case 'provenance': {
|
|
5050
|
+
requireInit(hippoRoot);
|
|
5051
|
+
const entries = loadAllEntries(hippoRoot);
|
|
5052
|
+
const coverage = buildProvenanceCoverage(entries);
|
|
5053
|
+
if (flags['json']) {
|
|
5054
|
+
console.log(JSON.stringify(coverage, null, 2));
|
|
5055
|
+
}
|
|
5056
|
+
else if (coverage.rawTotal === 0) {
|
|
5057
|
+
console.log('No kind=raw memories present. Coverage gate trivially satisfied.');
|
|
5058
|
+
}
|
|
5059
|
+
else {
|
|
5060
|
+
const pct = (coverage.coverage * 100).toFixed(1);
|
|
5061
|
+
console.log(`Provenance coverage: ${coverage.rawWithEnvelope}/${coverage.rawTotal} raw rows envelope-complete (${pct}%)`);
|
|
5062
|
+
if (coverage.gaps.length > 0) {
|
|
5063
|
+
console.log(`\nGaps:`);
|
|
5064
|
+
for (const g of coverage.gaps) {
|
|
5065
|
+
console.log(` ${g.id}: missing ${g.missing.join(', ')}`);
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
5069
|
+
if (flags['strict'] && coverage.coverage < 1) {
|
|
5070
|
+
process.exit(1);
|
|
5071
|
+
}
|
|
5072
|
+
break;
|
|
5073
|
+
}
|
|
5011
5074
|
case 'status':
|
|
5012
5075
|
cmdStatus(hippoRoot);
|
|
5013
5076
|
break;
|