IBEX Harness
DocsBenchmarksBlogChangelogRoadmap
GitHub
IBEX Harness

Documentation

Architecture Decision RecordsADR-0002: Repository foundation bootstrapADR-0003: Branch protection and merge policyADR-0004: Protobuf and code generation policyADR-0005: Postgres migration strategyADR-0006: Auth protobuf contract (`ibex.auth.v1`)ADR-0007: Auth token validation implementationADR-0008: Security scanning and CI quality gatesADR-0009: Permission bitmap layoutADR-0010: Cryptography policyADR-0011: Proxy auth gRPC client and middlewareADR-0012: Proxy request normalization (OpenAI chat)ADR-0013: Proxy input validation and stable error envelopeADR-0014: Core domain migration sequencingADR-0015: Proxy rate limit skeleton (Phase 1)ADR-0016: Proxy agent identity verification (Phase 1)ADR-0017: Request ID and trace context strategy (Phase 1)ADR-0018: Graceful shutdown contract (Phase 1)ADR-0019: OpenTelemetry provider configuration (Phase 1)ADR-0020: Shared package boundaries — `packages/config` and `packages/apierror`ADR-0021: Prometheus Metric Catalog (Phase 1)ADR-0022: Health check contract (Phase 1)ADR-0023: Docs site architecture (Phase 1.5)ADR-0024: Benchmark data publishing modelADR-0025: LLM provider abstractionADR-0026: OpenAI client designADR-0027: Streaming dual-write strategyADR-0028: Auth cache designADR-0029: Token revocation propagation via Redis pub/subADR-0030: Directive versioning strategyADR-0031: System prompt injection strategyADR-0032: Session data model and retentionADR-0033: ClickHouse llm_traces schema and retentionADR-0034: Proxy overhead performance measurement methodologyADR-0035: Chat Idempotency-Key Redis dedupeADR-0038: Context assembly service design and gRPC contractADR-0039: Proxy Postgres ownership for session and directive storesADR-0040: Anthropic provider adapterADR-0041: Model capability registryADR-0042: Self-hosted OpenAI-compatible LLM adapterADR-0043: Tokenizer registry architectureADR-0044: Non-streaming response pipelineADR-0045: Streaming response transformationADR-0046: Embedder interface and profile registryADR-0047: Memory temporal validity foundationADR-0048: Memory multi-label categoriesADR-0049: Memory relationship graph readinessADR-0050: MCP server skeleton (transport, auth, audit)ADR-0051: Local LGTM observability stack (Phase 2.5 exit pull-forward)ADR-0052: Memory schema v2 expand (HNSW, quality columns)ADR-0053: Vector store abstraction and composite scoring v2ADR-0054: In-process Presidio PII detection in memoryADR-0055: Memory write-path exact and near-duplicate dedupADR-0056: Temporal-interval-aware conflict detectionADR-0057: Memory write-path persistence and orchestrationADR-0058: Semantic search FTS fallback triggerADR-0059: Hot cache read pathADR-0060: Memory service tenant isolation architectureADR-0061: Retrieval-quality benchmark methodologyADR-0062: Worker task observability and dead-letter handlingADR-0063: Extraction output schema v2ADR-0064: Extraction provider registry and batch executionADR-0065: Extraction batch idempotency and turn pointer orderingADR-0066: Extraction quality evaluation harnessADR-0067: Context capability catalog generate-and-diff for token budgetsADR-0068: Composite scoring-time relevance gateADR-0069: Context packer bounded DP knapsackADR-0070: Context formatting locked ordering and session nonceADR-0071: Context assembly gRPC degradation ladder and retrieval deadlineADR-0072: Extraction enqueue via worker HTTP (not Celery Redis wire format)ADR-0073: Organization Lifecycle and Cascading SuspensionADR-0074 Provider Credential StorageADR-0075 Per-Org Model Routing PolicyADR-0076: Circuit Breaker per ProviderADR-0077: Provider Fallback RoutingADR-0078: Operator runtime topology & environment contract (4.P.0)ADR-0079: Operator session RS256 dual-verify and TOTP step-upIBEX Console service rename and boundaryADR-0081: Fail-closed Proxy runtime controlsD2 metadata-only trace read contractD2 publication, identity, query, and lifecycle contract
ADRs›ADR-0060: Memory service tenant isolation architecture
ADRs

ADR-0060: Memory service tenant isolation architecture

Architecture decision record 0060 — composed Postgres RLS + app-layer org_id filters, Redis namespacing, HNSW isolation, read-path Tier-1 PII guard, and ISO-* security integration gate.

ADR-0060: Memory service tenant isolation architecture

  • Status: Accepted
  • Date: 2026-08-31
  • Authors: IBEX Harness team
  • Milestone: 3.E.1 Memory security integration test suite

Context

Phase 3 memory milestones each enforced tenant scoping locally (schema RLS in ADR-0047–ADR-0049, vector filters in ADR-0053, Redis keys in ADR-0057 / ADR-0059). No single ADR described the composed isolation model that milestone 3.E.1 proves with real Postgres + pgvector + Redis in CI.

Milestone 3.E.1 ships twelve ISO-* integration cases (services/memory/tests/integration/security/) gated by the memory-security-integration job in .github/workflows/ci.yml. This ADR is the authoritative record for that architecture — not a replacement for table-level RLS decisions in ADR-0047–0049.

Write-path PII is ADR-0054. Milestone 3.E.1 adds a read-path Tier-1 structured PII re-check on hydrated search and hot-cache results (ISO-3.1) — defense-in-depth when content bypasses the write pipeline (direct SQL insert).

Options considered

1) RLS-only vs RLS + explicit org_id in queries

  1. Rely on Postgres RLS alone — fewer predicates in application SQL.
  2. FORCE RLS + explicit org_id / composite FK filters in every repository query (chosen).

Decision: (2). AGENTS.md requires defense-in-depth even when RLS exists. Integration tests assert both service-layer behavior (ISO-1.1, 1.2) and RLS floor behavior (ISO-1.3).

2) Redis tenant namespacing

  1. Global keys keyed by agent_id only — same UUID in two orgs would collide.
  2. {org_id} as the leading key segment for all tenant Redis data (chosen).

Decision: (2). Hot sorted sets: {org_id}:hot_memories:{agent_id} (app/cache/hot_keys.py). Object cache: {org_id}:memory:{memory_id} (ADR-0057).

3) Read-path PII bypass (ISO-3.1)

  1. Trust write-path Presidio only — no read re-check.
  2. Tier-1 regex re-check on every hydrated result before returning from search/hot cache (chosen).
  3. Async Tier-2 Presidio re-scan on read — accurate but adds latency and duplicates write-path NLP.

Decision: (2) for Phase 3. Implementation: app/read/pii_guard.py (email, phone, SSN, credit-card, IPv4 patterns). Tier-2 contextual bypass remains a documented residual risk (#642).

4) Org-scope GDPR delete (ISO-2.2)

  1. Implement full org cascade + MinIO purge in Phase 3.
  2. Defer to Phase 4.A.2 (#641) (chosen).

Decision: (2). Phase 3 proves per-memory FK cascade (ISO-2.1) only.

Decision

  1. Postgres session: Every memory-service transaction sets SET LOCAL app.current_org_id to the authenticated org UUID before queries. Missing org context fails closed.
  2. Application queries: All read/write repositories filter org_id explicitly (and agent_id where scoped to an agent), even on tables with FORCE RLS.
  3. HNSW / vector search: PgVectorStore predicates include org_id and agent_id on every ANN query; ISO-1.5 asserts zero cross-org leakage in top-K across ef_search values.
  4. Redis: Keys are org-prefixed; ISO-1.6 asserts identical agent_id UUIDs in two orgs cannot read each other's hot cache; ISO-1.8 asserts same-org cross-agent isolation.
  5. Graph edges: memory_relationships composite (memory_id, org_id) FKs reject cross-org attachment (ISO-1.4). memory_conflict_escalations uses the same pattern (ISO-1.7).
  6. AuthZ: Org A PAT cannot use Org B's agent_id — HTTP 403 semantics mirroring proxy AGENT_NOT_AUTHORIZED (ISO-1.2).
  7. Read-path Tier-1 PII guard: filter_pii_blocked_results() drops hydrated rows whose content still matches structured PII regexes; metric ibex_memory_pii_reconfirm_total. Quarantined memories never return from search (ISO-3.2).
  8. CI gate: memory-security-integration runs infra/scripts/memory-security-integration-test-ci.sh with digest-pinned pgvector/pgvector:0.8.6-pg16 and redis:7.4.2 service containers — same pattern as Phase 1 security-integration.
  9. Explicit Phase 3 non-goals:
    • Org-scope GDPR delete + MinIO archive removal → #641
    • Explicit Redis ZREM / object-cache invalidation on SQL delete → #647; stale ZSET members are dropped at hydrate time (ADR-0059).

Consequences

Positive

  • Composed isolation is regression-tested on every PR touching Go or Python (ci-gate-python / ci-gate-go).
  • Read-path Tier-1 guard closes the direct-DB-write bypass class without adding spaCy latency on the hot read path.
  • Redis and Postgres isolation models are consistent (org_id first).

Negative

  • Hydrate-on-read means deleted or superseded memories may linger in Redis until TTL or trim; callers never see them, but keys are not eagerly purged (#647).
  • Tier-1 regex guard does not catch contextual Tier-2 PII (names in prose without structured patterns) — tracked in #642.
  • No org-wide delete API in Phase 3; operators cannot satisfy GDPR erasure at org scope until Phase 4.A.2.
  • ISO-* tests use service-account SQL for RLS floor cases — production apps must still set org GUC per request.

References

  • 3.E.1 milestone — ISO-* matrix
  • 3.E.2 milestone — lifecycle e2e (ISO-2.1 teardown)
  • PR #643 — 3.E.1 implementation
  • #641 — org-scope GDPR deferral
  • #642 — Tier-2 read-path PII residual risk
  • #647 — Redis explicit invalidation gap

Was this page helpful?

Edit on GitHub

Last updated on

PreviousADR-0059: Hot cache read pathNextADR-0061: Retrieval-quality benchmark methodology

On this page

  • Context
  • Options considered
  • 1) RLS-only vs RLS + explicit org_id in queries
  • 2) Redis tenant namespacing
  • 3) Read-path PII bypass (ISO-3.1)
  • 4) Org-scope GDPR delete (ISO-2.2)
  • Decision
  • Consequences
  • References
0%