All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
First minor bump since v0.9.0. Two PRs shipped together: (1) the
chat-path-bypass documentation refresh that names the structural limit
of the Community tier when Anthropic consumer plans (Pro/Max) are in
the picture, and (2) the privacy-incident MCP-tool layer + 72-hour
deadline watcher on top of the long-standing privacy_incidents
schema. No DB migrations, no breaking config changes — the OPA
pb.config.incidents block is additive and the new MCP tools are
gated by the same pb_ API-key auth as every other tool.
- Privacy Incident MCP Tools (B-47, GDPR Art. 33/34) — Five MCP tools
expose the
privacy_incidentsschema that has been carrying detection signals since the EU AI Act push:report_breach(any authenticated role),list_incidents/assess_incident/notify_authority/notify_data_subject(admin-only). The new OPA packagepb.incidentsowns RBAC plus a configurable risk-score (high/medium/low PII weights × subject-count brackets × data-category multiplier × notifiable threshold). A newincident_deadline_checkworker job (every 15 min) classifies open incidents intowarning/critical/overduebuckets and exposes them via three new Prometheus alert rules (IncidentAssessmentOverdue,IncidentNotificationDeadlineImminent,IncidentNotificationOverdue) so the 72-hour Art. 33 deadline cannot silently slip past operations. 19 MCP unit tests + 21 OPA tests. Powerbrain records the evidence chain only; the outbound notification (email to authority, letter to subject) remains an organisational workflow. Spec:docs/specs/2026-05-12-incident-mcp-tools.md. - Edition Boundary Transparency (PR #153) — Documented that
Anthropic consumer plans (Claude Pro/Max, both Desktop App and Code
via
/login) cannot be redirected topb-proxybecause their OAuth flow ignoresANTHROPIC_BASE_URL. Newdocs/compliance-claude-desktop.mdone-pager with the three-tier mitigation model (real-time proxy / detective chat-history ingest / endpoint DLP), DPA vs EU AI Act distinction, and scenario recommendations.docs/editions.mdgained an "Edition boundary" section with the three-data-paths matrix (ingest / tool calls / chat content). Cross-links from README,getting-started.md,gdpr-external-ai-services.md,CLAUDE.md.
Routine dependency-maintenance release on top of v0.9.2 — no service-code changes, no DB migrations, no breaking changes. Picks up the Dependabot backlog accumulated since v0.9.0 and refreshes the floor of every Python pin we shipped with v0.9.2 to the latest 2026-05 upstream release.
- Stack-wide pin floor refresh. Bumped 14 Python deps across
mcp-server/,ingestion/,pb-proxy/, andreranker/requirements to the current upstream releases. All bumps are loosen-the-floor (>=) changes; CI (unit-tests, opa-tests, docker-build, security-scan) green on master after each merge.pb-proxy—fastapi >= 0.136.1(#151),pydantic >= 2.13.4(#150),prometheus-client >= 0.25.0(#147),mcp >= 1.27.1(#138).mcp-server—starlette >= 1.0.0(#149),cachetools >= 7.1.1(#146),tenacity >= 9.1.4(#142),mcp >= 1.27.1(#140),opentelemetry-instrumentation-fastapi >= 0.62b1(#137).ingestion—pydantic >= 2.13.4(#148),opentelemetry-api >= 1.41.1(#143),presidio-analyzer >= 2.2.359(#141),pyjwt >= 2.12.1(#139),beautifulsoup4 >= 4.14.3,<5.0(#136).reranker—pydantic >= 2.13.4(#144).
- Held back:
litellmfloor bump (#145, open).litellm >= 1.83.7introduces a stricter pydantic constraint that conflicts with the rest of the proxy's pin set. Will land in a follow-up release once upstream relaxes the bound or once we re-pin pydantic accordingly. Current floorlitellm >= 1.80remains in effect.
A single performance fix on top of v0.9.1 — no service-code semantics change, no DB migrations, no breaking changes. Halves the LLM-wait portion of per-document ingestion latency on backends that support real concurrency (vLLM, TEI, cloud LLM providers).
- Parallel L0+L1 layer generation in ingestion
(ingestion_api.py). The two LLM calls
for the L0 abstract and L1 overview of a single document are
independent (same
processed_chunks, same statelesscompletion_provider, same async-safehttpx.AsyncClient) but were awaited sequentially. Switched toasyncio.gatherso both calls run concurrently. On a 2026-05-08 re-ingest of 3782 commit-docs, layer generation dominated wall time at ~10s of ~12s per document; running the two completions in parallel cuts that to ~5s, a ~40% speedup on long ingestion runs without changing the model or any of the graceful-degradation paths. New unit test (test_layer_generation.py) asserts the twocompletion_provider.generatecalls overlap in time. Single-engine backends without batching (e.g. Ollama on a single GPU/CPU) will see less benefit because inference serialises at the model level — networking and prompt-prep still overlap.
A single CI hardening fix on top of v0.9.0 — no service-code changes, no DB migrations, no breaking changes. Cuts an explicit release so downstream infra repos can pin to a version tag.
- Forgejo build pipeline now publishes version tags
(build-images.sh +
.forgejo/workflows/build-images.yml).
The Forgejo Actions build was tagging images only as
:latestand:sha-<short>, never as:<version>— so consumers downstream (e.g. infra repos pinning the image tag) could not reference a release like0.9.0. Two changes:- Workflow now triggers on
tags: ['v*']in addition tomaster, and drops thepaths:filter (release commits are typically CHANGELOG-only and would otherwise be skipped). The script's own change detection still keeps non-release runs cheap. scripts/build-images.shreads the newRELEASE_TAGenv (passed by the workflow asgithub.ref_nameon tag pushes, with agit describe --exact-matchfallback for local runs) and adds a:${VERSION}build/push tag alongside the existing two. Release-tagged runs also forcerebuild_all=trueso the full image set always carries the version tag, even when only CHANGELOG.md changed in the release commit.
- Workflow now triggers on
Closes the audit-review backlog filed against v0.8.0 (#101–#105) plus a follow-up security hardening for ingestion auth (#126).
-
Ingestion auth fail-closed (#126).
INGESTION_AUTH_TOKENno longer falls back to allow-all when empty. With the defaultAUTH_REQUIRED=true, mcp-server, pb-proxy, ingestion, and pb-worker now refuse to start if the token is missing — surfacing the silent-degradation mode introduced in v0.8.0 (B-50, PR #89) as a hard boot failure instead. Mirrors the OPA hardening from v0.7.1 (PR #62) and the sameSKIP_*_STARTUP_CHECKopt-out pattern.Migration: existing deployments that already provisioned
secrets/ingestion_auth_token.txt(or set the env var) need no changes. Deployments mid-rollout with an empty token must either set the token, or explicitly setAUTH_REQUIRED=false(test/dev only — disables ALL auth layers, not only ingestion). Unit-test rigs can setSKIP_INGESTION_AUTH_STARTUP_CHECK=true; the standardconftest.pyfixtures already do. -
pb_audit_force_reset()signature gains an optionalp_purposeargument and behavior changes (#101). The function now writes a self-record intoagent_access_logbefore the truncate so the reset action is captured in the cryptographic chain that's about to be archived. As a result,archived_rowsincludes the self-record (typically+1over the previous behavior), andarchived_hash/audit_archive.last_verified_hashpoint to the self-record'sentry_hash(the correct cryptographic snapshot of the archived chain) instead of the pre-call tail. Existing zero-arg and one-arg callers continue to work via the new defaultp_purpose=NULL. Test/staging tooling that asserted onarchived_rows/archived_hashliterals must update; the bundled live tests show the pattern.
audit_archive.reset_caller+reset_purposecolumns (#101, migration026_audit_force_reset_provenance.sql). Captures the DB role that issuedpb_audit_force_reset()and the operator-supplied reason. Survives in continuity mode; lost in genesis (audit_archive is truncated by design, but Postgres statement logs still record the function call).pb_ingestion_auth_enabled{service=...}Prometheus gauge (#126). Reports the boot-time decision per service (1=token configured,0=disabled or skipped) so dashboards can alert on degraded mode regardless of how the service started.- Live PG coverage for the audit_integrity_status worker writes
(#105).
New
TestAuditIntegrityStatusLiveclass inmcp-server/tests/test_audit_integrity.py(PG_INTEGRATION=1gated). Catches future regressions if the worker role loses the BYPASSRLS that today makes the UPSERT throughFORCE ROW LEVEL SECURITYwork.
- Audit-worker logs verify result before the cache UPSERT
(#102).
When migration 024 hadn't been applied yet (mid-rollback or partial
setup), the verify result was silently lost: the primary
pb_verify_audit_chain_tail()call succeeded, but the UPSERT into the missingaudit_integrity_statustable raised, and the exception handler tried the same UPSERT and also failed. Operators now seeaudit chain verified: {…}at INFO level (oraudit chain invalid: {…}at ERROR) before the persistence attempt. - Transparency snapshot returns
total_checked: nullwhen the cache is stale (#104). Previously returned0, which the Annex IV renderer dutifully formatted asverified at last check: '0'— misleading because no check had run at all. The snapshot'sstaleflag is the primary signal;total_checked: nullis the JSON-idiomatic "value unknown" so consumers can't accidentally read it as zero. The compliance-doc renderer now displaysunknowninstead ofNone/0. - Test state pollution in
TestHashChainLive(PR #131). Thelive_poolfixture cleanedagent_access_logandaudit_archivebetween tests but notaudit_tail, so a later test's inserts chained from a stale tail andpb_verify_audit_chainwalked them as broken. Fixture now resetsaudit_tailto genesis in both setup and teardown, mirroringTestForceReset.live_pool. Latent bug; only visible underPG_INTEGRATION=1.
- BYPASSRLS dependency on audit-chain writes
(#103).
Inline comments in migrations 022 (
audit_tail) and 024 (audit_integrity_status) explaining that writes succeed only via BYPASSRLS —pb_adminisSUPERUSERby default in thepb-postgresimage. Deployments running the worker as a non-superuser role need an explicit INSERT/UPDATE policy or theaudit_integrity_status_refreshjob will silently fail and the transparency snapshot will degrade to stale. pb_audit_force_reset()self-record +p_purposeindocs/audit-chain-migration.md. Documents the new optionalp_purposeargument and the implications of the in-chain self-record on the function's return values.
Audit-chain hardening on top of the 0.7.x summarization-pool + ingestion-auth groundwork. Five issues filed during a recovery walkthrough after the 0.7.1 concurrency fix get resolved here, plus a new operator helper for non-production audit resets.
pb_audit_force_reset()operator helper (#97, #99). Single-call replacement for the multi-statement Continuity / Genesis recovery procedure documented indocs/audit-chain-migration.md. Both modes acquire theaudit_tailrow lock (cannot race with concurrent inserts) and archive the current tail withchain_valid=falsefor forensic continuity.continuitypreservesaudit_archiveand seeds the new chain from the archived hash so the verifier walks straight through;genesisadditionally truncates the archive and resetsaudit_tail.last_entry_hashto 32 zero bytes.SECURITY DEFINERwithREVOKE EXECUTE … FROM PUBLIC— only the DB owner / superuser can call it. Test/staging only — no production-environment guard yet (see #97 follow-up).- Worker-cached
audit_integrity_status(#95, #98). New single-row table holding the most recentpb_verify_audit_chain_tail()result, refreshed by a new pb-worker job (audit_integrity_status_refresh, every 60 s by default, configurable viaAUDIT_INTEGRITY_INTERVAL_SECONDSandAUDIT_INTEGRITY_TAIL_ROWS). The transparency report'saudit_integrityfield now reads from this cache so the snapshot reflects committed state, decoupled from the request-path INSERT (consumers see achecked_attimestamp and can judge staleness themselves). For a live answer, call theverify_audit_integrityMCP tool. - Decoupled summarization LLM pool (plan).
MCP server now accepts
SUMMARIZATION_PROVIDER_URL/SUMMARIZATION_MODEL/SUMMARIZATION_API_KEYso the in-pipeline summary call can run against its own endpoint instead of competing with the pb-proxy agent loop on a shared Ollama slot. Defaults to the existingLLM_*values — single-endpoint deployments need no change. Optional sidecarpb-ollama-summaryships underdocker compose --profile summary-llm, exposing port 11435 on the host and an internalhttp://ollama-summary:11434endpoint suitable for a smaller distilled model (e.g.qwen2.5:1.5b).GET /transparencyreports whether the pool is split viamodels.llm.pool_split. Closes the follow-up tracked in 0.7.0 release notes. - Service-token authentication for the ingestion API
(B-50). The ingestion service exposed
/extract,/pseudonymize,/scan,/ingest,/ingest/chunks,/snapshots/create,/sync,/sync/{repo}, and/previewwith no application-level auth — only Docker-network isolation. A new pure-ASGIIngestionAuthMiddlewarenow validates anAuthorization: Bearer <token>header on every request, with/healthand/metrics*exempt. The token lives in the newsecrets/ingestion_auth_token.txtDocker Secret (mirrored at/run/secrets/ingestion_auth_tokenand read viashared.config.read_secret). All callers — mcp-server, pb-proxy, pb-worker, pb-demo, pb-seed, and the ingestion service's own/syncloopback into_ingest_documents— pass the token. Token comparison useshmac.compare_digestfor constant-time evaluation. Backward-compatible: when the token is empty (e.g. existing deployments mid-upgrade), the middleware logs a loud warning at startup and lets requests through, so rolling out the secret is not a breaking change.scripts/quickstart.shauto-generates a 32-byte hex token alongside the existing secrets. pb_ingestion_auth_failures_total{reason}Prometheus counter on the new middleware. Labelsmissing(no/invalid header) andinvalid(wrong token) so operators can distinguish "service down" from "stale token" without log grepping.- E2E test for chat-path document attachments (B-51).
New suite at
tests/integration/e2e/test_document_attachment.pycovers both block shapes (OpenAIfilevia/v1/chat/completionsand Anthropicdocumentvia/v1/messages) plus the three policy error paths (413 oversize, 415 disallowed MIME, 403 viewer denied). The "PII pseudonymised before LLM" promise is asserted via Prometheus counters (pbproxy_documents_extracted_total{status="ok"}andpbproxy_pii_entities_pseudonymized_total{entity_type="PERSON"}), which keeps the test robust to LLM-provider availability while still proving the pipeline ran in order. Pre-generated fixtures live intestdata/documents/with a regeneration script for when the corpus needs to change. - Grafana dashboard panels for document extraction
(B-53). Four new panels appended to the
Powerbrain Overview dashboard under a Document Extraction row:
proxy doc-extract requests/s by MIME + status
(
pbproxy_documents_extracted_total), ingestion/extractduration p50/p95/p99 (pb_extract_duration_seconds_bucket), input-size heatmap (pb_extract_bytes_in_bucket), and ingestion-side success vs. error rate (pb_extract_requests_total). All metrics were already scraped — this just makes them visible. - ADR T-6 — markitdown vs. Docling (B-52).
Decision recorded in
docs/technology-decisions.md: stay with markitdown by default; ship Docling as an opt-in second backend when triggers fire (>20% scanned-PDF corpora, repeat extraction errors on tabular PDFs/XLSX, or a layout-fidelity-driven adapter). Companion benchmark harnessscripts/benchmark_extractors.pyruns both extractors on a directory and prints chars-out + latency for each — staying outside the production codepath until the benchmark data justifies a backend switch.
pb_verify_audit_chain()detects inconsistent seeds on an empty log (#94, #98). Previously returnedvalid=true, total_checked=0even whenaudit_tail.last_entry_hashdisagreed with the resolved archive seed — a state that's a guaranteed chain break on the next insert (e.g. genesis reset that forgot to truncateaudit_archive). Migration 023 cross-checks the tail in the empty-range path and now returnsvalid=false, first_invalid_id=1proactively. Range-scoped calls (p_start_id > 1) keep their existing behaviour.export_audit_logaccepts ISO-8601 datetime strings (#96, #98). Thesince/untilparameters were passed to asyncpg as raw strings, which fails the TIMESTAMPTZ type check with a confusing 500. New_parse_iso_datetimehelper accepts theZ/+00:00/ naive variants, returns a structured 422-style error for malformed input, and binds realdatetimeinstances. Pattern lifted from the existingvalidate_pii_access_token()helper.- Audit-chain recovery doc accuracy
(#93,
#98).
docs/audit-chain-migration.mdno longer claimspb_audit_checkpoint_and_prunedeletes broken segments (the DELETE is fail-closed behindIF v_verify.valid); Option B's manual TRUNCATE uses a CTE pattern that feeds the archive hash into theUPDATE audit_tail(the previous SQL referenced a non-existent column); the genesis-reset path is documented explicitly with the caveat thataudit_archivemust be truncated alongside or the next insert breaks at id=1.
Three concurrency and misconfiguration bug fixes filed against the 0.7.0 production deployment. Each shipped as an independent PR so it can be reviewed and reverted separately.
-
POST /syncworks inside the container again (#60, #61). The ingestion Dockerfile flattenedingestion/*into/app/, soingestion_api.py's deferredfrom ingestion.sync_service import …(and ~40 more absolute imports acrosssync_service.pyand the adapters) could never resolve. Preserved the package layout (COPY ingestion/ /app/ingestion/), added the missing top-level__init__.py, setPYTHONPATH=/app/ingestion:/appso sibling imports (from pii_scanner import …) keep working without rewriting 40+ statements, and switched uvicorn toingestion.ingestion_api:app. Side effect: the pre-existing compose mount./ingestion/repos.yaml:/app/ingestion/repos.yaml:ronow targets a real path inside the image. -
Missing OPA policies surface loudly instead of silently denying (#59 part 2, #62). Every OPA helper used
resp.json().get("result", {}), which collapsed an OPA response with noresultfield (policy not loaded) intoallowed=False, min_score=0.0. That produced the mathematically-impossible rejection logquality_score 0.629 < required 0.000and hours of debugging on fresh deployments.- New
shared/opa_client.pywithopa_query()that raisesOpaPolicyMissingErrorwhenresultis absent. verify_required_policies()runs on service startup — ingestion, mcp-server, and pb-proxy refuse to boot if a required policy package is missing. Env varSKIP_OPA_STARTUP_CHECK=trueopts out for unit tests.- Quality gate now uses
min_score=-1.0as a sentinel when the policy is missing, so the value itself flags a configuration issue in logs and theingestion_rejectionstable. - 13 new unit tests for the shared helper, 8 regression tests for the ingestion missing-policy path.
- New
-
Audit hash chain stays valid under concurrent writers (#59 part 1, #63).
audit_integrity.validflipped tofalseafter ingesting 4 861 documents with concurrency=8, breaking EU AI Act Art. 12 tamper-evidence. Two separate root causes:pg_advisory_xact_lockserialized trigger execution but did not invalidate the parent INSERT's READ COMMITTED snapshot — the waiter's SELECT still read the stale tail hash after the lock was released.BIGSERIALassignedidvia the column DEFAULT before the trigger ran, so id order and chain order could diverge under concurrency.pb_verify_audit_chain()walks byid ASCand flagged any divergence as a break, even when every individual hash was sound.
- New migration
init-db/022_audit_tail_pointer.sqlintroduces a single-rowaudit_tailtable protected bySELECT … FOR UPDATE, and derivesNEW.idfromlast_entry_id + 1atomically inside the lock. id order now matches chain order by construction. pb_audit_checkpoint_and_prune()rewritten to take the same tail lock instead of the advisory lock.- New integration test: 16 writers × 100 rows = 1 600 concurrent
inserts → chain
valid=true. docs/audit-chain-migration.mddocuments the operator procedure for deployments whose chain is already broken.
One optional precision layer for the PII pipeline, plus the Tab-D demo reliability fixes that came out of a live debugging session on CPU Ollama.
- Semantic PII Verifier (Option B) (#56): optional precision layer
that sits between Presidio's
scan_textoutput and the rest of the ingestion pipeline. Presidio has excellent recall but over-flags German compound nouns (Zahlungsstatus,Geschäftsführer,Sparkasse Köln) as PERSON / LOCATION. The verifier catches those false positives without touching recall.- New abstraction
shared/pii_verify_provider.py(same factory pattern asrerank_provider.py). Two backends ship:noop(community default, pass-through) andllm(OpenAI-compatible chat, e.g. Ollama / qwen2.5:3b). - Pattern types (IBAN, email, phone, DOB) skip the verifier — their Presidio score is already trustworthy. Ambiguous types batch into a single low-temperature chat call per document with ±60-char context windows.
- Fail-open on any error: unreachable LLM, malformed JSON, timeout → keep every candidate Presidio generated.
- OPA-policy-driven backend via
pb.config.ingestion.pii_verifier.{enabled,backend,min_confidence_keep}so admins flip runtime behaviour throughmanage_policieswithout restarting ingestion. - Prometheus metrics:
pb_ingestion_pii_verifier_calls_total{entity_type,backend,result}andpb_ingestion_pii_verifier_duration_seconds{backend}. - Applied in both the production
ingest_text_chunksper-chunk loop and the/previewdry-run, so demo Tab E renders{input, forwarded, reviewed, kept, reverted}stats live plus averifier.beforesnapshot for contrast. - Live verification on the NovaTech SharePoint fixture: 9 raw Presidio candidates → 6 after verifier (3 false positives removed) in ~12 s on qwen2.5:3b CPU.
- Docs:
docs/pii-verifier.md(architecture + configuration) anddocs/pii-custom-model.md(long-horizon roadmap for a fine-tuned German PII model — triggers, phases, why we're not building it today).
- New abstraction
- Tab-D "Advanced proxy settings" expander (#57): model, request
timeout (30–600 s), and
max_tokens(100–1000) editable per run in the sales-demo "MCP vs Proxy" tab. Session-scoped; persistent override viaPROXY_MODEL/PROXY_TIMEOUTenv on thepb-demoservice. Plus a diagnostic panel that surfaces tool-call count, finish_reason, and the typical failure modes ("LLM made no tool calls" / "Empty response after N LLM call(s) and M tool call(s)"). - Demo playbook — Tuning section (#57): new chapter in
docs/playbook-sales-demo.mdcovering local-LLM levers (timeout,max_tokens, Ollama warm-up,OLLAMA_NUM_CTX, GPU profile, host Ollama, hosted fallback model) with effect estimates. - Follow-up plan for separated LLM pools (#57):
docs/plans/2026-04-20-separate-summary-llm-pool.mdspecifies the clean fix for agent-loop vs summary-LLM contention — a secondCompletionProviderinstance inmcp-serverrouted throughSUMMARIZATION_PROVIDER_URL/SUMMARIZATION_MODEL/SUMMARIZATION_API_KEY. Tracked for the next release.
CompletionProvider.generate()accepts per-calltimeout(#57): shared LLM provider abstraction lets callers override the httpx client default when they sit behind a stricter upstream deadline (e.g. the summary LLM call behind the proxy'sTOOL_CALL_TIMEOUT). Backward-compatible; existing callers unchanged.- MCP server
SUMMARIZATION_TIMEOUT(#57): new env var (default 15 s) gates the summary LLM call insearch_knowledgeandget_code_context. Keeps the graceful-fallback branch ("return raw chunks") well below the proxy'sTOOL_CALL_TIMEOUTso the fallback response actually reaches the upstream caller. - pb-proxy
TOOL_CALL_TIMEOUT(#57): default 30 s → 60 s. Required headroom for the mcp-server's summary attempt + raw-chunks fallback + response envelope on CPU Ollama. Lower it again when pointing at a hosted provider with sub-second latency. - Proxy tool allowlist ships by default (#57):
pb-proxy/mcp_servers.yamlnow declares atool_whitelistwith five entries (search_knowledge,get_document,graph_query,query_data,check_policy). The MCP server still exposes all 23 tools — they are just hidden from the LLM by default so small local models (qwen2.5:3b) stop suffering choice-paralysis from the ~8–10 kB of schema overhead. Enterprise deployments with capable models (Haiku, gpt-4o-mini, qwen2.5:14b+) can drop the whitelist.
- Tab D proxy timeout / empty-response deadlock (#57): the
pb-proxy agent loop and the mcp-server's forced summarisation
(
pb.summarization.summarize_requiredforconfidentialhits) both hit 30 s httpx deadlines simultaneously, so the graceful raw-chunks fallback never reached the proxy. Stale symptom in the demo: "Read timed out" or "(empty response)" after ~60 s. The newSUMMARIZATION_TIMEOUT=15+TOOL_CALL_TIMEOUT=60ordering makes the fallback land ~45 s before the proxy gives up. - Demo client default read timeout (#57):
_ProxyClient.timeoutdefault 60 s → 180 s (+PROXY_TIMEOUTenv override) so a slow-but-successful agent-loop iteration on CPU doesn't get killed by the HTTP client in Streamlit before the proxy returns.
SUMMARIZATION_TIMEOUT(new env, default 15 s) andTOOL_CALL_TIMEOUT(default raised from 30 s to 60 s) are plumbed throughdocker-compose.yml. No action needed unless you override these in a custom compose file — in that case, make sureTOOL_CALL_TIMEOUT > SUMMARIZATION_TIMEOUTby a comfortable margin.pb-proxy/mcp_servers.yamltool_whitelist: existing deployments that rely on tools outside the five-entry default (e.g. LLM-drivensubmit_feedbackorgraph_mutate) must explicitly add them to the whitelist or remove the whitelist entirely. The MCP server still exposes every tool — only the proxy-side injection is narrowed.- OPA
pb.config.ingestion.pii_verifiersection (new): defaults toenabled=false,backend=noop. No behaviour change unless you opt in. Flip at runtime viamanage_policiesonce an LLM endpoint is reachable.
- 2 merged PRs since v0.6.0: #56, #57.
- +1964 / −21 lines across 19 files (2 new source files, 5 new docs/plans files).
- Unit tests: full suite 953 passing (plus 30 intentionally
skipped, 8 integration-deselected). New
shared/tests/ test_pii_verify_provider.pyadds comprehensive coverage for the verifier's noop/LLM backends, the skip-by-type logic, and the fail-open guarantees. - OPA tests: unchanged pass count (no new Rego paths, only a new
pb.config.ingestion.pii_verifierdata section).
Four features that together answer the questions enterprise decision-makers ask most often — who can see what, what happens to our PII, and what does your pipeline do with our documents — plus the sales-demo surfaces that make those answers visible in fifteen minutes.
- Sales-Demo UI (#50): opt-in Streamlit app
pb-demoon port 8095, profiledemo. Starts out with three tabs:- Tab A Same question, different answers — analyst vs viewer side-by-side on the same query, shows OPA access matrix in action.
- Tab B We never stored the secret — live PII vault scan → ingest →
HMAC-token reveal with purpose-bound
fields_to_redact. - Tab C The org behind the answer —
streamlit-agraphrendering of the NovaTech knowledge graph (8 employees → 3 departments → 4 projects) viagraph_query. - Plus pre-seeded demo keys (
pb_demo_analyst_localonly,pb_demo_viewer_localonly), 6 German-PII customer records, and an 8-employee graph seed. Quickstart gained--seed/--demoflags and auto-generates Postgres / HMAC / proxy secrets. - 15-min presenter script in
docs/playbook-sales-demo.md.
- Editions (Community vs Enterprise) +
/vault/resolve(#52):- Every service advertises
"edition": "community"(mcp-server) or"enterprise"(pb-proxy) on/health+/transparency. - New mcp-server endpoint
POST /vault/resolvedoes text-level de-pseudonymisation (regex extract[ENTITY_TYPE:hash]→ SQL hash-match → OPA vault policy → purpose-based field redaction → audit log). - pb-proxy's agent loop calls
/vault/resolveon tool results under the OPA-gatedpb.proxy.pii_resolve_tool_resultspolicy (enabled / allowed_roles / allowed_purposes / default_purpose). Client declares purpose viaX-Purposeheader. - Stats surface on
_proxy.vault_resolutions+X-Proxy-Vault-*response headers. - Demo Tab D MCP vs Proxy renders both paths side-by-side on the same query; purpose toggle changes what gets redacted.
- Full capability matrix + topology in
docs/editions.md.
- Every service advertises
- Pipeline Inspector +
/previewendpoint (#54):- New
POST /previewon the ingestion service: runs the full pipeline (optional extract → PII scan → quality score + OPA ingestion gate → OPA privacy decision) against a document without persisting to PostgreSQL or Qdrant. Returns a structured{extract, scan, quality, privacy, summary}payload with per-phase timings and an explicitwould_ingestverdict. - Demo Tab E Pipeline Inspector with three adapter-representative
fixtures (SharePoint contract, Outlook support email, GitHub
README) plus optional file upload. Editable
classification/source_type/legal_basisso the OPA policy effect is visible live.
- New
- OPA policy data path (#50):
opa-policies/data.jsonmoved from thepb/subdirectory to the repo-levelopa-policies/so the OPArunloader mounts it atdata.pb.config.*instead of the doubly-prefixeddata.pb.pb.config.*. Without this, the ingestionpii_actioncheck was silently stuck on the defaultblock. CI invocations and Docker volume mounts updated; no deployer action needed beyond pulling the new image. - Graph PII masking (#51):
_mask_graph_piiin the MCP server is now deterministic and policy-driven. The previous Presidio-per-value scan produced inconsistent results on non-English names (Elena →<PERSON>, Tim → unchanged, Sarah →<LOCATION>). Newpb.config. graph_pii_keyssection indata.jsonmaps graph property keys to Presidio entity-type labels; the walker replaces matched values with<ENTITY_TYPE>deterministically. Admin-editable at runtime viamanage_policies. - Access matrix (#50):
confidentialnow includesanalystanddeveloperin addition toadmin. Matches realistic RBAC for customer records and salary bands;restrictedstays admin-only. - Quickstart flags (#50):
./scripts/quickstart.sh --seedseeds the 21 base documents;--demoadds the PII customer records, the graph seed, the demo UI profile, the pb-proxy profile, and pulls the summarisation model. Auto-generates Postgres / vault / proxy-service tokens — no more manual.envediting.
- PII pseudonymisation overlap (#53): Presidio can emit overlapping
hits on the same character range (classic case: the trailing digit
run of a German IBAN is also a valid phone number). The pseudonymiser
replaced both in descending-position order and produced nested
artefacts like
[IBAN_CODE:73c1acb4]db0d4]. New_resolve_overlapping_spanshelper picks one hit per overlap — higher score wins, then longer span, then earlier start — and is applied consistently inscan_text,mask_text, andpseudonymize_text. - Graph
find_path(#50):_parse_return_columnsingraph_service.pywas splitting"RETURN a, r, b LIMIT 1"into three columns and sanitising the third to"bLIMIT1", which broke the row lookup. The parser now stripsLIMIT/ORDER BY/SKIP/OFFSETbefore splitting. - DE date-of-birth recognizer (#50): dropped the pure-numeric
dd.mm.yyyypattern because it fired on harmless policy dates (e.g. "gültig ab 01.01.2025"), which the ingestion quality gate then blocked wholesale. The keyword-anchored variant (Geburtsdatum:,geb.,geboren am) remains and covers the real use case. - Vault schema width (#50):
pii_vault.pseudonym_mapping.pseudonymwasVARCHAR(20)— too narrow for longer entity tags like[DE_DATE_OF_BIRTH:…]or[EMAIL_ADDRESS:…]. Migration021widens it toVARCHAR(64)idempotently. - pb-proxy bootstrap (#52): the proxy service token in
secrets/mcp_auth_token.txtmust be a registeredapi_keysrow so the proxy can reach mcp-server underAUTH_REQUIRED=true. The quickstart now registers it automatically viascripts/register-proxy-key.sh; previously this was a silent startup failure on fresh installs. - Seed authentication (#50):
testdata/seed.pynow sendsAuthorization: Beareron every MCP call; the graph seed step previously couldn't initialise the MCP session under the defaultAUTH_REQUIRED=true, and the seed container aborted before the graph ran. - Suggestion buttons in demo Tab A (#51): moved outside the
st.formso clicks actually fire and update the query / trigger the search.
init-db/020_viewer_role.sql(new): widens theapi_keys. agent_roleCHECK to includeviewerso the pre-seeded demo viewer key is valid. Existing deployments pick this up on next restart; no manual steps required.init-db/021_widen_vault_pseudonym.sql(new): widenspii_vault.pseudonym_mapping.pseudonymtoVARCHAR(64). Idempotent — re-runs on existing databases.opa-policies/data.jsonmoved fromopa-policies/pb/data.json(same forpolicy_data_schema.json). Deployers who mount these paths directly in customdocker-composeoverrides should update their mounts.- pb-proxy service token:
secrets/mcp_auth_token.txtis now auto-registered byquickstart.sh. Manual deployments should run./scripts/register-proxy-key.shonce after the Postgres init completes.
- 989 unit tests pass (13 new), 68.95% coverage (CI threshold 68%).
- 131/131 OPA policy tests pass (12 new for
graph_pii_keys+pii_resolve_tool_results+ viewer role regressions). - 5 merged PRs since v0.5.0: #50, #51, #52, #53, #54.
- Office 365 Adapter (#42): second source adapter. Syncs SharePoint,
OneDrive, Outlook Mail, Teams Messages, and OneNote into the knowledge
base via Microsoft Graph API.
- Delta Queries for incremental sync (all providers except OneNote, which uses timestamp-based sync).
- OAuth2 Client Credentials (app-only) + Delegated Auth (OneNote, post-March-2025 Microsoft Graph policy).
- Content extraction via Microsoft
markitdown+ format-specific fallbacks (python-docx, openpyxl, python-pptx, BeautifulSoup). - Site-level classification in YAML config.
- Teams ↔ SharePoint deduplication (file attachments stored as refs only).
- Resource Unit budget tracking +
$batchAPI usage. - Config:
ingestion/office365.yaml(example provided).
- Shared document extraction (
ingestion/content_extraction/, #46) —ContentExtractorlifted out of the Office 365 adapter into a reusable module so all adapters + the/extractendpoint share one surface. POST /extractendpoint on the ingestion service (#46) — converts base64-encoded binary documents (PDF, DOCX, XLSX, PPTX, MSG, EML, RTF, ...) to text. Size-capped viaEXTRACT_MAX_BYTES(default 25 MB) and bounded byEXTRACT_TIMEOUT_SECONDS(default 30 s).- Chat-path document attachments in pb-proxy (#46,
/v1/chat/completionsand/v1/messages) — extractsfile/input_file(OpenAI) anddocument(Anthropic) blocks via/extractbefore PII scanning and LLM forwarding. - GitHub adapter opt-in document ingestion (#46) via
allow_documents: trueinrepos.yaml— fetches Office/PDF files as bytes and runs them through the sharedContentExtractor. Ingested assource_type="github-document". - New OPA policy
pb.proxy.documents(#46) — gates chat attachments by role, size, MIME type, and per-request file count. Data-driven viadata.json. - Optional Tesseract OCR fallback (#46) for scanned PDFs — activated at
build time via
--build-arg WITH_OCR=trueplus runtimeOCR_FALLBACK_ENABLED=true. - Prometheus metrics:
pb_extract_requests_total,pb_extract_duration_seconds,pb_extract_bytes_in,pbproxy_documents_extracted_total,pbproxy_documents_extracted_bytes. - 42 new unit tests (content_extraction, /extract endpoint, pb-proxy document extraction, Anthropic document normalization) + 11 new OPA tests.
ingestion/adapters/office365/content.pyis now a thin shim re-exporting fromingestion.content_extraction— fully backward compatible.ingestion/requirements.txtnow hosts markitdown + Office document fallbacks (moved up fromoffice365/requirements.txt) so all consumers (adapters +/extractendpoint) share one dependency surface.- GitHub adapter's
BINARY_EXTENSIONSsplit intoHARD_BINARY_EXTENSIONS(images/archives — always blocked) andDOCUMENT_EXTENSIONS(opt-in viaallow_documents). Legacy alias preserved for backward compatibility. - Consolidated dependency version bumps across all services (#47):
- Security floors:
pyjwt>=2.10,pyyaml>=6.0.2,httpx>=0.28,msal>=1.32(Entra-ID fixes),litellm>=1.80(proxy). - OpenTelemetry unified across ingestion/pb-proxy/reranker/mcp-server at
>=1.27(core) and>=0.48b0(instrumentation). torch>=2.6in reranker for CVE coverage.- Worker requirements relaxed from hard pins to SemVer-safe ranges
(
apscheduler>=3.11,<4.0— 4.x is an incompatible rewrite,python-dotenv>=1.2,<2.0,qdrant-client>=1.15,<2.0, etc.).
- Security floors:
- CI PR validation detects changes correctly across multi-commit pushes
(#43) — use
BEFORE_SHAinstead ofHEAD^for the change-detection baseline. python-pptx<1.0ceiling excluded the already-released 1.0.x series — corrected to<2.0(#47).
- GitHub adapter reference added and cross-linked with the Office 365 adapter docs (#44).
docs/architecture.md§2.9 Document Extraction — new section describing the shared extractor, policy gates, and OCR fallback.- 4 new backlog tickets logged: B-50 (unified ingestion auth layer), B-51 (E2E test for chat document attachments), B-52 (ADR markitdown vs. Docling), B-53 (Grafana panels for extraction metrics).
0.4.0 - 2026-04-10
- GitHub Adapter: first source adapter for knowledge base ingestion from GitHub repositories (#39)
- Incremental sync via commit SHA tracking (
repo_sync_statetable) - Configurable include/exclude path patterns, default binary/noise skip rules
- PAT + GitHub App authentication (JWT → installation token)
- Polling via pb-worker (configurable interval) +
POST /sync/{repo}endpoint - Full pipeline: PII scan, OPA quality gate, embedding, context layers
- Cascade deletion of removed files (Qdrant, PG, vault, graph)
- Config:
ingestion/repos.yaml(example provided) - 59 new unit tests, 2 new OPA tests (111 total)
- Incremental sync via commit SHA tracking (
0.3.1 - 2026-04-10
- README badges (CI, License, Docker, MCP) and corrected tool count (16 → 23)
- GitHub Issue Templates (bug report, feature request) and PR template
- SECURITY.md with vulnerability reporting policy
- SUPPORT.md pointing to Discussions, Issues, and Docs
scripts/quickstart.shfor automated first-time setup with optional demo data seedingdocs/getting-started.md— step-by-step tutorial with authentication guidedocs/mcp-tools.md— all 23 MCP tools with parameters and access rolesdocker-compose.ghcr.yml— compose override for pre-built GHCR images.github/workflows/release.yml— automated GHCR image publishing and GitHub Releases on tag push.github/dependabot.yml— weekly dependency updates for pip, Docker, and GitHub Actions.pre-commit-config.yamlwith ruff linter and formatter- Locust load test for MCP search pipeline (
tests/load/) - CI security scanning:
pip-audit(dependency vulnerabilities) +bandit(static analysis) - CI coverage threshold enforcement (
--cov-fail-under=73)
- CLAUDE.md updated with new files, CI changes, and expanded pre-public checklist
- Quick Start in README references
quickstart.shand includes health verification step - MCP client config examples now include Bearer token authentication
0.3.0 - 2026-04-09
- PII masking for graph_query/graph_mutate results via ingestion
/scanendpoint (B-30) - Metadata PII redaction in search_knowledge/get_code_context based on configurable field mapping and OPA
fields_to_redactpolicy (B-31) manage_policiesMCP tool for runtime OPA policy data management with JSON Schema validation (B-12)boost_correctionsreranking parameter for user-corrected documents (B-13)- OPAL integration for real-time policy sync from git repos (
--profile opal) (B-10) - CHANGELOG v0.1.0 and v0.2.0 entries (#8)
- PipelineStep fallback in pb-proxy now matches shared/telemetry.py signature including
to_dict()(B-20) - BACKLOG.md fully closed out — all items completed or marked won't do
- Missing
pyyamldependency in mcp-server/requirements.txt - EU flag emoji replaced with ⚖️ for cross-platform display in README
- CLAUDE.md: tool count, directory structure, components table, secrets list updated to match reality
0.2.0 - 2026-04-09
- EU AI Act compliance implementation (Art. 9, 11-15): risk management, technical documentation, transparency reporting, human oversight, accuracy/robustness monitoring, and pb-worker background service (#5, #6)
- Translate all German comments, docstrings, and documentation to English (#7)
- Correct license reference in README from MIT to Apache 2.0 (#4)
0.1.0 - 2026-04-08
Initial public release of the Powerbrain Context Engine.
- MCP Server with 12 tools (search, query, ingest, graph, policy, classification)
- 3-stage search pipeline: Qdrant vector search, OPA policy filtering, Cross-Encoder reranking
- Configurable reranker backend (Powerbrain/TEI/Cohere) via strategy pattern
- OPA-controlled context summarization with LLM provider abstraction
- Data-driven OPA policies (access, privacy, rules, summarization, proxy) with JSON Schema validation
- Sealed Vault for GDPR-compliant PII pseudonymization (dual storage, HMAC tokens, purpose binding)
- PII Scanner (Microsoft Presidio) with configurable entity types and custom recognizers
- Knowledge Graph via Apache AGE (queries and mutations)
- Context Layers (L0/L1/L2) for progressive document loading
- Knowledge versioning with snapshots
- AI Provider Proxy with multi-MCP-server aggregation, SSE streaming, and per-provider key management
- Proxy authentication (ASGI middleware, pb_ API keys, identity propagation)
- Docker Secrets support with env var fallback
- Optional TLS via Caddy reverse proxy
- Structured telemetry (OpenTelemetry tracing, Prometheus metrics, Grafana dashboards)
- Performance caches (embedding cache, OPA result cache, batch embedding)
- Evaluation and feedback loop
- Monitoring stack (Prometheus, Grafana, Tempo)
- CI workflows (GitHub Actions + Forgejo for internal use)
- Comprehensive documentation (architecture, deployment, scalability, GDPR, ADRs)