hippo-memory 1.11.3 → 1.11.5

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/dist/api.d.ts CHANGED
@@ -245,6 +245,16 @@ export interface RecallResult {
245
245
  * `ctx.tenantId`. The `mode` flag is accepted for forward compatibility (the
246
246
  * CLI exposes hybrid/physics paths) but Task 2 wires only the BM25 candidate
247
247
  * loader; later tasks can extend this to call the physics/hybrid scorer.
248
+ *
249
+ * **api.recall does NOT mutate `index.last_retrieval_ids`** (v1.11.5 contract
250
+ * lock). The CLI `cmdRecall` (cli.ts) writes `last_retrieval_ids` because the
251
+ * CLI is interactive (user is about to run `hippo outcome --good`). SDK callers
252
+ * are programmatic: they either pass explicit ids to `api.outcome` or call
253
+ * `api.getContext` first for the context-then-outcome workflow (getContext
254
+ * DOES write `last_retrieval_ids`). Adding the side-effect here would change
255
+ * `api.recall` from a pure read into a read+write, breaking SDK callers who
256
+ * batch recall calls in a row. Locked by
257
+ * `tests/api-recall-no-side-effects.test.ts`.
248
258
  */
249
259
  export declare function recall(ctx: Context, opts: RecallOpts): RecallResult;
250
260
  export interface AssembleOpts {
@@ -400,13 +410,23 @@ export type DrillDownOutcome = DrillDownResult | DrillDownFailure;
400
410
  export declare function drillDown(ctx: Context, summaryId: string, opts?: DrillDownOpts): DrillDownOutcome;
401
411
  /**
402
412
  * Apply a positive/negative outcome to a list of recently-recalled memory ids.
403
- * Used by the MCP `hippo_outcome` tool. Tenant-scoped: ids that don't belong
404
- * to ctx.tenantId are silently skipped (matches the prior MCP semantics —
405
- * a stale id from another tenant doesn't crash the call). Each successful
406
- * outcome emits one audit_log row with op='outcome' tagged with ctx.actor.
413
+ * Used by the MCP `hippo_outcome` tool and the HTTP `POST /v1/outcome` route.
414
+ * Tenant-scoped: ids that don't belong to ctx.tenantId are silently skipped
415
+ * (matches the prior MCP semantics — a stale id from another tenant doesn't
416
+ * crash the call). Each successful outcome emits one audit_log row with
417
+ * op='outcome' tagged with ctx.actor.
418
+ *
419
+ * Returns `{applied, appliedIds}`. `appliedIds` is the tenant-filtered subset
420
+ * of input ids that actually had `applyOutcome` run on them (i.e. ids whose
421
+ * `readEntry(..., ctx.tenantId)` resolved). Callers that surface the id list
422
+ * over a multi-tenant boundary (HTTP /v1/outcome last-recall path, Python SDK)
423
+ * MUST return `appliedIds` instead of the raw input list — otherwise the
424
+ * non-applied (cross-tenant) ids leak to the caller. Added in v1.11.4 to
425
+ * close that disclosure path on POST /v1/outcome.
407
426
  */
408
427
  export declare function outcome(ctx: Context, ids: ReadonlyArray<string>, good: boolean): {
409
428
  applied: number;
429
+ appliedIds: string[];
410
430
  };
411
431
  /**
412
432
  * Delete a memory by id. `deleteEntry` threads ctx.actor into its internal
@@ -628,17 +648,17 @@ export interface SleepResult {
628
648
  *
629
649
  * Tenant scope note: sleep operates on the WHOLE hippoRoot (all tenants in
630
650
  * it), matching the pre-refactor cmdSleepCore behavior. Correct for a CLI
631
- * maintenance op invoked by the operator. But once Episode B exposes this
632
- * over HTTP `/v1/sleep`, the route MUST gate to a global-admin actor or
633
- * scope dedup/audit/delete by ctx.tenantId otherwise a tenant-A Bearer
634
- * could dedupe and delete tenant-B's rows. See TODOS.md "Episode A
635
- * follow-ups" for the Episode B preflight checklist.
651
+ * maintenance op invoked by the operator. Episode B (v1.11.4) exposed this
652
+ * over HTTP `/v1/sleep` with loopback-only enforcement (per-request guard
653
+ * in the handler plus serve()'s boot-time host check). The TODOS.md
654
+ * per-tenant scoping follow-up remains open for the day non-loopback
655
+ * serving lands — at that point the route will need an admin-role gate OR
656
+ * api.sleep itself will need to scope dedup / audit / delete by ctx.tenantId.
636
657
  *
637
658
  * Audit emission gap: the consolidation phases (dedup, audit-delete) do
638
659
  * NOT emit audit_log rows today, matching pre-refactor cmdSleepCore. Same
639
660
  * CLI/MCP parity gap that T6 fixed for cmdOutcome, now visible at the api
640
- * surface. Episode B should decide whether `/v1/sleep` writes a single
641
- * 'consolidate' audit row per invocation or per-phase rows per deletion.
661
+ * surface. Tracked in TODOS.md "Episode A follow-ups" for a future minor.
642
662
  */
643
663
  export declare function sleep(ctx: Context, opts?: SleepOpts): Promise<SleepResult>;
644
664
  /**
@@ -646,10 +666,19 @@ export declare function sleep(ctx: Context, opts?: SleepOpts): Promise<SleepResu
646
666
  *
647
667
  * Reads `loadIndex(ctx.hippoRoot).last_retrieval_ids` (per-hippoRoot local
648
668
  * state; not tenant-scoped at the index layer) and forwards to `outcome()`,
649
- * which DOES tenant-filter via `readEntry(..., ctx.tenantId)` — cross-tenant
669
+ * which DOES tenant-filter via `readEntry(..., ctx.tenantId)`. Cross-tenant
650
670
  * ids in `last_retrieval_ids` are silently skipped, matching the MCP
651
671
  * `hippo_outcome` semantics.
652
672
  *
673
+ * **Tenant-safe response shape (v1.11.4 security fix):** the returned `ids`
674
+ * field contains ONLY the tenant-filtered subset that actually had outcomes
675
+ * applied (i.e. `appliedIds` from the inner `outcome()` call). Earlier
676
+ * versions returned the raw `last_retrieval_ids` regardless of tenant, which
677
+ * leaked cross-tenant memory IDs to the caller via POST /v1/outcome's
678
+ * no-body last-recall response. The fix is at this helper so all callers
679
+ * (CLI cmdOutcome, HTTP /v1/outcome, MCP `hippo_outcome` if added later)
680
+ * inherit the tenant-safe contract.
681
+ *
653
682
  * Do NOT tighten `loadIndex` with `tenantId` inside this helper — doing so
654
683
  * would break the (correct) cross-tenant-silent-skip behavior covered by
655
684
  * the test in `tests/api-outcome-for-last-recall.test.ts`.
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAwBL,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,WAAW,EAEjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,OAAO,EACb,MAAM,YAAY,CAAC;AAGpB,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,WAAW,CAAC;AAOnB,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEtE,MAAM,WAAW,OAAO;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAgB,IAAI,EAChB,gCAAgC,GAChC,uBAAuB,CAAC;gBAE1B,IAAI,EACA,gCAAgC,GAChC,uBAAuB,EAC3B,OAAO,EAAE,MAAM;CAMlB;AAiBD;;;;;;;;;GASG;AACH;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAaT;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAExE;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/D;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,cAAc,CAczE;AAMD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IACrC;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;;;OAaG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,mBAAmB,EAAE,YAAY,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,YAAY,CA8TnE;AAMD,MAAM,WAAW,YAAY;IAC3B,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;kCAC8B;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB;sCACkC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;kCAC8B;IAC9B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;qDACiD;IACjD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,YAAiB,GACtB,cAAc,CA+HhB;AAMD,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACtH,QAAQ,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnG,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,WAAW,GAAG,eAAe,CAAC;CACxC;AAED,MAAM,MAAM,gBAAgB,GAAG,eAAe,GAAG,gBAAgB,CAAC;AAElE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,aAAkB,GACvB,gBAAgB,CA0DlB;AAMD;;;;;;GAMG;AACH,wBAAgB,OAAO,CACrB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,EAC1B,IAAI,EAAE,OAAO,GACZ;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAsBrB;AAMD;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAiBzE;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CACrB,GAAG,EAAE,OAAO,EACZ,EAAE,EAAE,MAAM,GACT;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAuClD;AAMD;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAsF5C;AAMD;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,KAAK,IAAI,CAAC;CACzE;AAED,wBAAgB,UAAU,CACxB,GAAG,EAAE,OAAO,EACZ,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,cAAmB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAsDlC;AAMD,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,gBAAgB,CAQ/E;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GACxB,cAAc,EAAE,CAQlB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,MAAM,GACZ;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CA8CjC;AAMD,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,GAAG,UAAU,EAAE,CAYzE;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,WAAW;IAC1B,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IACrC,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,OAAO,EACZ,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,aAAa,CAAC,CA4RxB;AAMD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE;QACR,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,KAAK,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,KAAK,CACzB,GAAG,EAAE,OAAO,EACZ,IAAI,GAAE,SAAc,GACnB,OAAO,CAAC,WAAW,CAAC,CA+EtB;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,OAAO,GACZ;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAMpC"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAwBL,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,WAAW,EAEjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,OAAO,EACb,MAAM,YAAY,CAAC;AAGpB,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,WAAW,CAAC;AAOnB,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEtE,MAAM,WAAW,OAAO;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAgB,IAAI,EAChB,gCAAgC,GAChC,uBAAuB,CAAC;gBAE1B,IAAI,EACA,gCAAgC,GAChC,uBAAuB,EAC3B,OAAO,EAAE,MAAM;CAMlB;AAiBD;;;;;;;;;GASG;AACH;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAaT;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAExE;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/D;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,cAAc,CAczE;AAMD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IACrC;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;;;OAaG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,mBAAmB,EAAE,YAAY,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,YAAY,CA8TnE;AAMD,MAAM,WAAW,YAAY;IAC3B,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;kCAC8B;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB;sCACkC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;kCAC8B;IAC9B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;qDACiD;IACjD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,YAAiB,GACtB,cAAc,CA+HhB;AAMD,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACtH,QAAQ,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnG,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,WAAW,GAAG,eAAe,CAAC;CACxC;AAED,MAAM,MAAM,gBAAgB,GAAG,eAAe,GAAG,gBAAgB,CAAC;AAElE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,aAAkB,GACvB,gBAAgB,CA0DlB;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CACrB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,EAC1B,IAAI,EAAE,OAAO,GACZ;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAsB3C;AAMD;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAiBzE;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CACrB,GAAG,EAAE,OAAO,EACZ,EAAE,EAAE,MAAM,GACT;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAuClD;AAMD;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAsF5C;AAMD;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,KAAK,IAAI,CAAC;CACzE;AAED,wBAAgB,UAAU,CACxB,GAAG,EAAE,OAAO,EACZ,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,cAAmB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAsDlC;AAMD,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,gBAAgB,CAQ/E;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GACxB,cAAc,EAAE,CAQlB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,MAAM,GACZ;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CA8CjC;AAMD,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,GAAG,UAAU,EAAE,CAYzE;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,WAAW;IAC1B,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IACrC,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,OAAO,EACZ,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,aAAa,CAAC,CA4RxB;AAMD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE;QACR,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,KAAK,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,KAAK,CACzB,GAAG,EAAE,OAAO,EACZ,IAAI,GAAE,SAAc,GACnB,OAAO,CAAC,WAAW,CAAC,CAqJtB;AAMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,OAAO,GACZ;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAMpC"}
package/dist/api.js CHANGED
@@ -115,6 +115,16 @@ export function remember(ctx, opts) {
115
115
  * `ctx.tenantId`. The `mode` flag is accepted for forward compatibility (the
116
116
  * CLI exposes hybrid/physics paths) but Task 2 wires only the BM25 candidate
117
117
  * loader; later tasks can extend this to call the physics/hybrid scorer.
118
+ *
119
+ * **api.recall does NOT mutate `index.last_retrieval_ids`** (v1.11.5 contract
120
+ * lock). The CLI `cmdRecall` (cli.ts) writes `last_retrieval_ids` because the
121
+ * CLI is interactive (user is about to run `hippo outcome --good`). SDK callers
122
+ * are programmatic: they either pass explicit ids to `api.outcome` or call
123
+ * `api.getContext` first for the context-then-outcome workflow (getContext
124
+ * DOES write `last_retrieval_ids`). Adding the side-effect here would change
125
+ * `api.recall` from a pure read into a read+write, breaking SDK callers who
126
+ * batch recall calls in a row. Locked by
127
+ * `tests/api-recall-no-side-effects.test.ts`.
118
128
  */
119
129
  export function recall(ctx, opts) {
120
130
  const limit = opts.limit ?? 10;
@@ -630,13 +640,22 @@ export function drillDown(ctx, summaryId, opts = {}) {
630
640
  // ---------------------------------------------------------------------------
631
641
  /**
632
642
  * Apply a positive/negative outcome to a list of recently-recalled memory ids.
633
- * Used by the MCP `hippo_outcome` tool. Tenant-scoped: ids that don't belong
634
- * to ctx.tenantId are silently skipped (matches the prior MCP semantics —
635
- * a stale id from another tenant doesn't crash the call). Each successful
636
- * outcome emits one audit_log row with op='outcome' tagged with ctx.actor.
643
+ * Used by the MCP `hippo_outcome` tool and the HTTP `POST /v1/outcome` route.
644
+ * Tenant-scoped: ids that don't belong to ctx.tenantId are silently skipped
645
+ * (matches the prior MCP semantics — a stale id from another tenant doesn't
646
+ * crash the call). Each successful outcome emits one audit_log row with
647
+ * op='outcome' tagged with ctx.actor.
648
+ *
649
+ * Returns `{applied, appliedIds}`. `appliedIds` is the tenant-filtered subset
650
+ * of input ids that actually had `applyOutcome` run on them (i.e. ids whose
651
+ * `readEntry(..., ctx.tenantId)` resolved). Callers that surface the id list
652
+ * over a multi-tenant boundary (HTTP /v1/outcome last-recall path, Python SDK)
653
+ * MUST return `appliedIds` instead of the raw input list — otherwise the
654
+ * non-applied (cross-tenant) ids leak to the caller. Added in v1.11.4 to
655
+ * close that disclosure path on POST /v1/outcome.
637
656
  */
638
657
  export function outcome(ctx, ids, good) {
639
- let applied = 0;
658
+ const appliedIds = [];
640
659
  const db = openHippoDb(ctx.hippoRoot);
641
660
  try {
642
661
  for (const id of ids) {
@@ -652,13 +671,13 @@ export function outcome(ctx, ids, good) {
652
671
  targetId: id,
653
672
  metadata: { good },
654
673
  });
655
- applied++;
674
+ appliedIds.push(id);
656
675
  }
657
676
  }
658
677
  finally {
659
678
  closeHippoDb(db);
660
679
  }
661
- return { applied };
680
+ return { applied: appliedIds.length, appliedIds };
662
681
  }
663
682
  // ---------------------------------------------------------------------------
664
683
  // forget
@@ -1289,82 +1308,153 @@ export async function getContext(ctx, opts = {}) {
1289
1308
  *
1290
1309
  * Tenant scope note: sleep operates on the WHOLE hippoRoot (all tenants in
1291
1310
  * it), matching the pre-refactor cmdSleepCore behavior. Correct for a CLI
1292
- * maintenance op invoked by the operator. But once Episode B exposes this
1293
- * over HTTP `/v1/sleep`, the route MUST gate to a global-admin actor or
1294
- * scope dedup/audit/delete by ctx.tenantId otherwise a tenant-A Bearer
1295
- * could dedupe and delete tenant-B's rows. See TODOS.md "Episode A
1296
- * follow-ups" for the Episode B preflight checklist.
1311
+ * maintenance op invoked by the operator. Episode B (v1.11.4) exposed this
1312
+ * over HTTP `/v1/sleep` with loopback-only enforcement (per-request guard
1313
+ * in the handler plus serve()'s boot-time host check). The TODOS.md
1314
+ * per-tenant scoping follow-up remains open for the day non-loopback
1315
+ * serving lands — at that point the route will need an admin-role gate OR
1316
+ * api.sleep itself will need to scope dedup / audit / delete by ctx.tenantId.
1297
1317
  *
1298
1318
  * Audit emission gap: the consolidation phases (dedup, audit-delete) do
1299
1319
  * NOT emit audit_log rows today, matching pre-refactor cmdSleepCore. Same
1300
1320
  * CLI/MCP parity gap that T6 fixed for cmdOutcome, now visible at the api
1301
- * surface. Episode B should decide whether `/v1/sleep` writes a single
1302
- * 'consolidate' audit row per invocation or per-phase rows per deletion.
1321
+ * surface. Tracked in TODOS.md "Episode A follow-ups" for a future minor.
1303
1322
  */
1304
1323
  export async function sleep(ctx, opts = {}) {
1305
1324
  const dryRun = Boolean(opts.dryRun);
1306
- // Phase 1: Consolidation.
1307
- const consolidateResult = await consolidate(ctx.hippoRoot, { dryRun });
1308
- const result = {
1309
- active: consolidateResult.decayed,
1310
- removed: consolidateResult.removed,
1311
- mergedEpisodic: consolidateResult.merged,
1312
- newSemantic: consolidateResult.semanticCreated,
1313
- dryRun,
1314
- details: consolidateResult.details,
1315
- };
1316
- if (dryRun)
1317
- return result;
1318
- // Phase 2: Dedup (post-consolidate near-duplicate cleanup).
1319
- const dedupResult = deduplicateStore(ctx.hippoRoot);
1320
- if (dedupResult.removed > 0) {
1321
- const semDups = dedupResult.pairs.filter((p) => p.keptLayer === 'semantic' && p.removedLayer === 'semantic').length;
1322
- const epiDups = dedupResult.pairs.filter((p) => p.keptLayer === 'episodic' && p.removedLayer === 'episodic').length;
1323
- const crossDups = dedupResult.pairs.filter((p) => p.keptLayer !== p.removedLayer).length;
1324
- result.deduped = {
1325
- removed: dedupResult.removed,
1326
- semDups,
1327
- epiDups,
1328
- crossDups,
1325
+ // v1.11.5: phase counters for the consolidate audit emit (in finally).
1326
+ // Accumulated as each phase completes so partial-failure paths still report
1327
+ // accurate "what got done before the failure" data.
1328
+ let consolidationCount = 0;
1329
+ let dedupCount = 0;
1330
+ let auditDeletedCount = 0;
1331
+ let ambientTotal = 0;
1332
+ let phaseError = null;
1333
+ let result = null;
1334
+ try {
1335
+ // Phase 1: Consolidation.
1336
+ const consolidateResult = await consolidate(ctx.hippoRoot, { dryRun });
1337
+ consolidationCount = consolidateResult.semanticCreated + consolidateResult.merged;
1338
+ result = {
1339
+ active: consolidateResult.decayed,
1340
+ removed: consolidateResult.removed,
1341
+ mergedEpisodic: consolidateResult.merged,
1342
+ newSemantic: consolidateResult.semanticCreated,
1343
+ dryRun,
1344
+ details: consolidateResult.details,
1329
1345
  };
1330
- }
1331
- // Phase 3: Quality audit (remove junk, report warnings).
1332
- const allEntries = loadAllEntries(ctx.hippoRoot);
1333
- const auditOut = auditMemories(allEntries);
1334
- if (auditOut.issues.length > 0) {
1335
- const errors = auditOut.issues.filter((i) => i.severity === 'error');
1336
- const warnings = auditOut.issues.filter((i) => i.severity === 'warning');
1337
- if (errors.length > 0) {
1338
- for (const issue of errors) {
1339
- deleteEntry(ctx.hippoRoot, issue.memoryId);
1346
+ if (dryRun)
1347
+ return result;
1348
+ // Phase 2: Dedup (post-consolidate near-duplicate cleanup).
1349
+ const dedupResult = deduplicateStore(ctx.hippoRoot);
1350
+ dedupCount = dedupResult.removed;
1351
+ if (dedupResult.removed > 0) {
1352
+ const semDups = dedupResult.pairs.filter((p) => p.keptLayer === 'semantic' && p.removedLayer === 'semantic').length;
1353
+ const epiDups = dedupResult.pairs.filter((p) => p.keptLayer === 'episodic' && p.removedLayer === 'episodic').length;
1354
+ const crossDups = dedupResult.pairs.filter((p) => p.keptLayer !== p.removedLayer).length;
1355
+ result.deduped = {
1356
+ removed: dedupResult.removed,
1357
+ semDups,
1358
+ epiDups,
1359
+ crossDups,
1360
+ };
1361
+ }
1362
+ // Phase 3: Quality audit (remove junk, report warnings).
1363
+ const allEntries = loadAllEntries(ctx.hippoRoot);
1364
+ const auditOut = auditMemories(allEntries);
1365
+ if (auditOut.issues.length > 0) {
1366
+ const errors = auditOut.issues.filter((i) => i.severity === 'error');
1367
+ const warnings = auditOut.issues.filter((i) => i.severity === 'warning');
1368
+ if (errors.length > 0) {
1369
+ for (const issue of errors) {
1370
+ deleteEntry(ctx.hippoRoot, issue.memoryId);
1371
+ }
1372
+ }
1373
+ auditDeletedCount = errors.length;
1374
+ if (errors.length > 0 || warnings.length > 0) {
1375
+ result.audit = {
1376
+ errorsRemoved: errors.length,
1377
+ warningCount: warnings.length,
1378
+ };
1340
1379
  }
1341
1380
  }
1342
- if (errors.length > 0 || warnings.length > 0) {
1343
- result.audit = {
1344
- errorsRemoved: errors.length,
1345
- warningCount: warnings.length,
1346
- };
1381
+ // Phase 4: Auto-share high-transfer-score memories to global.
1382
+ if (!opts.noShare) {
1383
+ const sleepConfig = loadConfig(ctx.hippoRoot);
1384
+ if (sleepConfig.autoShareOnSleep) {
1385
+ const shared = autoShare(ctx.hippoRoot, { minScore: 0.6 });
1386
+ if (shared.length > 0) {
1387
+ result.shared = shared.length;
1388
+ }
1389
+ }
1347
1390
  }
1348
- }
1349
- // Phase 4: Auto-share high-transfer-score memories to global.
1350
- if (!opts.noShare) {
1351
- const sleepConfig = loadConfig(ctx.hippoRoot);
1352
- if (sleepConfig.autoShareOnSleep) {
1353
- const shared = autoShare(ctx.hippoRoot, { minScore: 0.6 });
1354
- if (shared.length > 0) {
1355
- result.shared = shared.length;
1391
+ // Phase 5: Post-sleep ambient state summary.
1392
+ const postSleepConfig = loadConfig(ctx.hippoRoot);
1393
+ if (postSleepConfig.ambient.enabled) {
1394
+ const postSleepEntries = loadAllEntries(ctx.hippoRoot).filter((e) => !e.superseded_by);
1395
+ if (postSleepEntries.length > 0) {
1396
+ result.ambient = computeAmbientState(postSleepEntries);
1397
+ ambientTotal = result.ambient.totalMemories;
1356
1398
  }
1357
1399
  }
1400
+ return result;
1401
+ }
1402
+ catch (err) {
1403
+ phaseError = err;
1404
+ throw err;
1358
1405
  }
1359
- // Phase 5: Post-sleep ambient state summary.
1360
- const postSleepConfig = loadConfig(ctx.hippoRoot);
1361
- if (postSleepConfig.ambient.enabled) {
1362
- const postSleepEntries = loadAllEntries(ctx.hippoRoot).filter((e) => !e.superseded_by);
1363
- if (postSleepEntries.length > 0) {
1364
- result.ambient = computeAmbientState(postSleepEntries);
1406
+ finally {
1407
+ // v1.11.5: emit one 'consolidate' audit_log row per api.sleep invocation,
1408
+ // with phase counters in metadata. Closes the CLI/MCP parity gap that T6
1409
+ // fixed for cmdOutcome (Episode A follow-up). In finally so partial-failure
1410
+ // paths still emit; `partial: true` + errorMessage flag the failure.
1411
+ // Dedicated handle for this emit only (phase helpers above each open their
1412
+ // own handle via hippoRoot — SQLite single-writer makes parallel handles
1413
+ // safe for the read-heavy phases).
1414
+ //
1415
+ // TODO(v1.12.0 + A5 v2): the audit row is tagged with ctx.tenantId but
1416
+ // api.sleep is host-wide (cross-tenant dedup is intentional). When
1417
+ // /v1/sleep moves off loopback-only, either tag with a synthetic "host"
1418
+ // tenant or scope api.sleep per-tenant. Independent-review-critic flag,
1419
+ // v1.11.5 ship.
1420
+ //
1421
+ // Error preservation: if openHippoDb or appendAuditEvent throws here, we
1422
+ // do NOT let it replace the original phaseError (independent-review HIGH:
1423
+ // would mask the underlying consolidation failure). Audit emit failure
1424
+ // is logged to stderr but the original throw wins.
1425
+ try {
1426
+ const db = openHippoDb(ctx.hippoRoot);
1427
+ try {
1428
+ appendAuditEvent(db, {
1429
+ tenantId: ctx.tenantId,
1430
+ actor: ctx.actor,
1431
+ op: 'consolidate',
1432
+ metadata: {
1433
+ consolidationCount,
1434
+ dedupCount,
1435
+ auditDeletedCount,
1436
+ ambientTotal,
1437
+ dryRun,
1438
+ noShare: opts.noShare ?? false,
1439
+ partial: phaseError !== null,
1440
+ ...(phaseError ? { errorMessage: phaseError.message } : {}),
1441
+ },
1442
+ });
1443
+ }
1444
+ finally {
1445
+ closeHippoDb(db);
1446
+ }
1447
+ }
1448
+ catch (auditErr) {
1449
+ // Audit emit failure must NOT mask the original phaseError. Log to
1450
+ // stderr so the secondary failure is observable but does not throw.
1451
+ // This guards the case where consolidation AND audit-emit fail in the
1452
+ // same invocation against the same DB (correlated: same disk, same
1453
+ // schema state) — losing the original error makes diagnosis much harder.
1454
+ // eslint-disable-next-line no-console
1455
+ console.error(`[hippo] api.sleep audit emit failed: ${auditErr.message}`);
1365
1456
  }
1366
1457
  }
1367
- return result;
1368
1458
  }
1369
1459
  // ---------------------------------------------------------------------------
1370
1460
  // outcomeForLastRecall (last-recall wrapper around outcome — Task 3)
@@ -1374,10 +1464,19 @@ export async function sleep(ctx, opts = {}) {
1374
1464
  *
1375
1465
  * Reads `loadIndex(ctx.hippoRoot).last_retrieval_ids` (per-hippoRoot local
1376
1466
  * state; not tenant-scoped at the index layer) and forwards to `outcome()`,
1377
- * which DOES tenant-filter via `readEntry(..., ctx.tenantId)` — cross-tenant
1467
+ * which DOES tenant-filter via `readEntry(..., ctx.tenantId)`. Cross-tenant
1378
1468
  * ids in `last_retrieval_ids` are silently skipped, matching the MCP
1379
1469
  * `hippo_outcome` semantics.
1380
1470
  *
1471
+ * **Tenant-safe response shape (v1.11.4 security fix):** the returned `ids`
1472
+ * field contains ONLY the tenant-filtered subset that actually had outcomes
1473
+ * applied (i.e. `appliedIds` from the inner `outcome()` call). Earlier
1474
+ * versions returned the raw `last_retrieval_ids` regardless of tenant, which
1475
+ * leaked cross-tenant memory IDs to the caller via POST /v1/outcome's
1476
+ * no-body last-recall response. The fix is at this helper so all callers
1477
+ * (CLI cmdOutcome, HTTP /v1/outcome, MCP `hippo_outcome` if added later)
1478
+ * inherit the tenant-safe contract.
1479
+ *
1381
1480
  * Do NOT tighten `loadIndex` with `tenantId` inside this helper — doing so
1382
1481
  * would break the (correct) cross-tenant-silent-skip behavior covered by
1383
1482
  * the test in `tests/api-outcome-for-last-recall.test.ts`.
@@ -1387,7 +1486,7 @@ export function outcomeForLastRecall(ctx, good) {
1387
1486
  const ids = idx.last_retrieval_ids;
1388
1487
  if (ids.length === 0)
1389
1488
  return { applied: 0, ids: [] };
1390
- const { applied } = outcome(ctx, ids, good);
1391
- return { applied, ids };
1489
+ const { applied, appliedIds } = outcome(ctx, ids, good);
1490
+ return { applied, ids: appliedIds };
1392
1491
  }
1393
1492
  //# sourceMappingURL=api.js.map