Changelog
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.3.1 — 2026-09-24 · 3 added · 3 changed · 1 fixed
No runtime behaviour changed on the server. This release refreshes the docs site and the MCP Registry listing, and corrects several documentation and CHANGELOG claims found while writing them.
Added
- A MkDocs Material docs site published from the existing user docs, with an identity (mark, favicon, palette, landing page), six concept diagrams embedded in context, and dedicated FAQ, comparison and three intent use-case pages.
- A developer guide overview with tutorials for writing an adapter and
writing a custom
IdentityResolver, both checked against the code they document, plus an exampleIdentityResolverand its unit tests indata-prism-quickstart-extension. websiteUrland the current canonical tagline in the MCP Registryserver.jsonentry, so the next registry publish carries them instead of the stale v0.3.0 description and missingwebsiteUrl.
Changed
- The changelog page on the docs site now collapses every release but the
latest, without editing
CHANGELOG.mditself. - CHANGELOG: corrected the 0.3.0
AuditChainVerifierentry, which attached "caught even on the last record" to deletions as well as edits; only edits are caught at a chain's tail, a deletion is caught only when a later record follows it (seedocs/audit.md). - Adopted a new canonical project description in
README.mdand the rootpom.xml, replacing v0.3.0's "A privacy layer between MCP clients and enterprise APIs.": it now says Data Prism pseudonymises personal data per privacy scope and refuses anything unclassified. The same text is the abstract of the newCITATION.cff.
Fixed
docs/tools.md's claim that an unclassified field is "dropped"; the shippedDEFAULT/STRICTprofiles both refuse the whole request instead.
Not published to Maven Central; the Maven artifacts remain at 0.3.0.
0.3.0 — 2026-09-23 · 6 added · 3 changed · 3 fixed · 4 behavioural changes · 1 not changed
Nested JSON catalogues, one level deep, and a durable, hash-chained audit trail — protect a real API without writing Java, and prove what happened.
Added
- The configuration-driven JSON REST connector gains one level of named
nested catalogues: a field can declare
nested: <name>, pointing at an entry in a top-level nested-catalogues map whose own leaves may benonSensitiveor classified only — a nested leaf carries no identifier of its own (it inherits its subject from the enclosing record), soidentifier: trueand a furthernested:are both refused there. No dotted paths, no JSONPath, no wildcard descent, no inferring structure from the wire —subject-json-pathis untouched and a nested object never carries its own subject. A response nesting deeper than declared — a leaf the catalogue says is a scalar turning up as a structure — refuses with the new, distinctNESTED_LEAF_NOT_SCALARcode rather than falling through to core's genericUNCLASSIFIED_STRUCTURE; the mirror case, a declared structure that turns up as a scalar, refuses withNESTED_FIELD_NOT_STRUCTURED. - A durable, append-only, hash-chained audit sink. Selecting
dataprism.audit.sink: hash-chained, alongside the now-requireddataprism.audit.file-path, produces aFileAuditSinkbean: one file, fsync per record, no rotation. Omitting the file path refuses at startup withMISSING_AUDIT_FILE_PATH; a path this process cannot open refuses withAUDIT_SINK_FILE_UNUSABLE, instead of degrading silently to no auditing. The canonical audit-record hash covers nineteen fields, includingtimestampandsourceSystems. - An offline
AuditChainVerifierCLI replays a hash-chained audit file's writers from a copy and reports one of five outcomes: exit 0 intact; 1 unreadable input; 2 a detected break — an edit inside one writer's chain, caught anywhere including that chain's own last record, or a deletion caught only when a later record follows it in the same chain; 3 the final record has no terminating newline, reported as possibly in flight, which is proof of neither health nor tampering; and 4 a structural anomaly (an interrupted-write fragment, a duplicate sequence, or a chain not starting atGENESISimmediately after another anomaly), never returned together with a break. Exit codes 3 and 4 name shapes ordinary operation can also produce; neither rules out tampering. The verifier cannot detect truncation of a writer's most recent records at all — a file with its tail removed verifies intact at exit 0, because nothing remains in the file to disagree with — and the same blind spot extends to a whole process boot: deleting every record of one boot leaves the surviving writers reporting intact and never mentions the deleted one, indistinguishable from that boot never having run. - An opt-in
dataprism.identity.resolver: pass-throughproperty selectsPassThroughIdentityResolver, refusing an unrecognised value withUNSUPPORTED_IDENTITY_RESOLVER; an application-suppliedIdentityResolverbean still wins. Combined with the configuration-driven JSON REST connector, this makes protecting a flat JSON API genuinely possible with no Java class. A SpringFailureAnalyzernow renders everyDataPrismConfigurationExceptionas an operator-facing block naming the refusal code, what to supply, and the two docs pages that explain it,docs/configuration.mdanddocs/quickstart.md, with no stack frame. A new walkthrough,docs/protect-your-own-api.md, takes a reader with a flat JSON REST API from nothing to a pseudonymised MCP response using only YAML. - A one-command demo,
examples/quickstart-demo/run.sh, drives the running Compose quickstart end to end over its real MCP transport (obtaining a token, handshaking over SSE-framedtools/callbodies, and printing a pseudonymised response), extracted out of the CI smoke test so a reader can run the same thing locally. - The four Compose quickstart images (server-with-extension, fixtures,
issuer, certs-init) are now published as multi-architecture GHCR
manifests;
docker compose uppulls them by default, with the from-source build path moved todocker compose -f compose.yaml -f compose.build.yaml up --build.
Changed
data-prism-exampleis renamed todata-prism-integration-tests: it hosts 11 integration test classes with no duplicate elsewhere, includingPiiLogScanTest, the sole enforcement of privacy rule 7, and was never a demo. Packageio.github.aindriub.dataprism.exampleandExampleApplicationare unchanged. Sixdata-prism-examplestrings survive deliberately insidedata-prism-integration-tests— its own JWTissuerand auditwriter-idconfig values, and the tests asserting on them — because they are observable audit output, not a module identifier.- A configured JSON source's
DataSourceAdapterbean no longer needs a matchingdataprism.sourcesentry:DataPrismContractValidator's cross-check betweendataprism.sourcesand the supplied adapters now requires only thatdataprism.sourcesbe a subset of the supplied adapters, not an exact match, so a source whose transport lives entirely in its own JSON catalogue can supply an adapter with no correspondingdataprism.sourcesentry. EveryDataSourceAdapterbean still has to earn its way onto the review allow-list, though: it must be named bydataprism.sourcesor supplied by the JSON-catalogue mechanism (the catalogue's own source names), or startup refuses with the stable codeUNREVIEWED_SOURCE_ADAPTER— an adapter bean present on the classpath for neither reason is not implicitly approved. - Documentation reconciled against the shipped code rather than the plan that
preceded it: two consumer guides,
docs/extending.mdanddocs/tools.md, are now linked fromREADME.md,docs/quickstart.mdanddocs/agents/README.md; every stale "one tool" claim across those files anddocs/architecture.mdis corrected to name both shipped tools,get_entity_contextandcompare_entity_sources;docs/architecture.mdnow attributesArchitectureTesttodata-prism-architecture, the module that hosts it, instead of the renamed module; andREADME.md's "Until Task 20 delivers…" claim is replaced — the configuration-driven JSON REST mode shipped as the publisheddata-prism-connectors-restartefact, self- registering via Spring'sAutoConfiguration.importsand requiring no Java.
Fixed
AuditRecorderno longer advances its in-memorypreviousHashuntil the sink'srecordcall actually succeeds. Previously, a throwing sink still left the chain head pointing past an event that was never durably written, so the next successful write chained against a hash for a record that does not exist; a throwing sink now rolls the recorder back to byte-identical prior state instead.AutoConfiguredBeanClassificationTest's sweep now walks@Imported and nested configuration classes recursively, closing a gap that let a bean be placed specifically to dodge classification.- A sink failure raised while recording an audit event no longer reaches the
MCP client carrying its own exception text.
GetEntityContextToolandCompareEntitySourcesToolnow catch only the audit-record failure and rethrow it asAuditUnavailableException, exposed to the client as the stable codeAUDIT_UNAVAILABLEwith no text derived from the caught exception — closing a path by which a hash-chained sink's failure could disclose the server-side audit file's path to the MCP client. The response is still refused, exactly as before; only what the client is told changed.
Behavioural change for API consumers
HmacSyntheticGenerator's discriminator widens from 20 bits (masked out of a single 4-byte digest word, four Crockford base32 characters) to 40 bits (eight distinct digest bytes, eight characters), andADDRESS— which previously rendered no discriminator at all — now carries one like every other namespace. Every pseudonym this generator produces changes as a result; a pseudonym stored or compared under 0.2.0 will not match the one produced under 0.3.0 for the same input.PseudonymisationVersionnow rejects a MAC algorithm whose digest is too short for the generator's own reads at construction time, with the stable codepseudonymisation.algorithm-digest-too-short, rather than surfacing anArrayIndexOutOfBoundsExceptionlater;HmacMD5andHmacSHA1are both now rejected, so an operator configured with either must move to an algorithm whose MAC output is at least 24 bytes. This reduces collision probability substantially; it does not make collisions impossible, and no such claim is made.AuditRecordernow derivesinstanceIdas<writer-id>/<per-boot random UUID>instead of the writer-id alone, so a restart under the same writer-id is reported as a new writer starting atGENESISrather than a false chain break.instanceIdvalues recorded before this change are not comparable to ones recorded after it. A writer-id containing/is now refused at startup with the new codeINVALID_AUDIT_WRITER; rename any writer-id that contains a/before upgrading.- Configuring
dataprism.audit.sink: approved-sinkwith no matchingAuditSinkbean now refuses with the new codeAUDIT_SINK_BEAN_REQUIREDinstead of the genericMISSING_AUDIT_SINK, which is retained unchanged for the separate case of an absent or blankdataprism.audit.sinkproperty. Anything keyed on the old code for the bean-absent case must switch to the new one. - In 0.2.0,
DataPrismContractValidatorcompareddataprism.sourcesand the suppliedDataSourceAdapterbeans for exact equality, so an adapter bean present on the classpath with no matchingdataprism.sourcesentry refused withUNRESOLVED_SOURCE_ADAPTER. In 0.3.0 that same case — an adapter named by neitherdataprism.sourcesnor the JSON-catalogue mechanism — refuses instead with the new codeUNREVIEWED_SOURCE_ADAPTER(see the Changed entry above);UNRESOLVED_SOURCE_ADAPTERis retained, unchanged, for the other direction: adataprism.sourcesentry with no adapter bean supplied for it. Anything keyed on the old code for the adapter-present case must switch to the new one.
Not changed
- What the hash-chained audit trail's tamper-evidence covers, stated
precisely: an edit of a record inside one writer's chain, caught anywhere
including that writer's own last record, and a deletion of a record inside
one writer's chain, caught only when a later record follows it. It does
not cover truncation of a
writer's most recent records, or deletion of an entire process boot's
records — both are undetectable from inside the file alone (see the
verifier entry above).
AuditEventHashis also unkeyed SHA-256, so anyone with write access to the audit file can recompute the whole chain after tampering with it; the trail does not resist an operator, or anyone else who already has that access. Nothing here should be read as, or later restated as, a claim that the audit log is tamper-proof, immutable, or independently complete.
0.2.0 — 2026-09-17 · 1 added · 1 changed · 1 behavioural change · 1 not changed
A second MCP tool, compare_entity_sources, and no other new feature.
Added
compare_entity_sources, the second MCP tool: per-fieldidentityplus findings over the same correlated, scrubbedContextResponseget_entity_contextalready builds. Findings report agreement, disagreement (INCONSISTENT/FORMATTING_ONLY/ABBREVIATION) andMISSING_IN_SOME_SOURCES, each distinguishable by an explicit discriminator rather than by absence, so a caller can tell "compared and consistent" from "never compared".
Changed
- The MCP registry namespace is corrected to
io.github.AindriuB/data-prism, matching the casing the registry actually grants for the GitHub login. Maven Central'sio.github.aindriuband the GHCR pathghcr.io/aindriub/data-prism-serverare different identifiers, each correct in its own system and untouched by this correction — do not "fix" the casing inconsistency between them; doing so would break two already-published artifacts. No MCP registry listing for this server exists yet; this corrected namespace, and the label it is checked against, is what the first successful publish will use.server.json's OCI package reference is also corrected to the canonical form the registry requires: noregistryBaseUrl, andidentifiernow carries the image tag directly (ghcr.io/aindriub/data-prism-server:0.2.0) rather than a bare path paired with a separateversionfield.
Behavioural change for API consumers
ContextResponsegained a new record component (fieldsByNamespace), so itsequals,hashCodeandtoStringnow include it. This is not a linkage break — binary compatibility was verified withjavapagainst the published 0.1.1 jar, and the old constructor signature still works — but twoContextResponsevalues that compared equal under 0.1.1 may no longer compare equal under 0.2.0.
Not changed
- The privacy engine, pseudonymisation and security modules: no behaviour change in this release.
0.1.1 — 2026-09-17 · 3 changed
A release-plumbing patch, no features. 0.1.1 supersedes 0.1.0 for the
published image: the v0.1.0 tag predates the multi-architecture publish
pipeline, so a dispatch against that tag would silently re-run the old
single-architecture workflow rather than publish a multi-arch image. main
has also diverged from the tag since (CI action bumps, documentation, a
quickstart dependency pin), so an image built from main and labelled
0.1.0 would not correspond to the tagged tree. Cutting a patch version is
cheaper and more honest than force-moving a tag a published GitHub Release
already points at. No production source differs from 0.1.0, so the library
artifacts on Maven Central are functionally identical and consumers of
those jars have no functional reason to upgrade; 0.1.1 exists for the image
and the publish pipeline, where multi-architecture support is a real
improvement for anyone running the image.
Changed
- The server image is now published as a multi-architecture manifest list
(
linux/amd64+linux/arm64) instead oflinux/amd64only. - Three GitHub Actions dependency bumps in CI workflows.
- nimbus-jose-jwt bumped to 10.9.1 in
data-prism-quickstart-issuer. This affects the quickstart issuer only, not the published server or library artifacts, which never depend on it directly.
0.1.0 — 2026-09-16 · 9 added · 4 not included
First release: the walking skeleton and every slice through S9a.
Added
- Privacy engine that assigns one deterministic synthetic identity per
(scope, subject, namespace, algorithm version, key), never random and never stored in plaintext. - Correlation and consistency findings that surface source-data inconsistencies instead of hiding them.
- Parallel mTLS connectors to enterprise source APIs.
- Embedded Hazelcast identity cache and per-scope read budget.
- OAuth2 resource server with session-derived
PrivacyContext. - Audit logging and metrics.
- One MCP tool,
get_entity_context. - Standalone server as the primary deployment surface, and a Spring Boot starter for embedding the privacy layer directly.
- A one-command local Compose quickstart.
Not included in this release
- The re-identification operator surface, deferred past V1 by decision (see
docs/architecture.md#decisions-worth-knowing). - The Elasticsearch connector and its search tools.
- The three additional MCP tools named in the design review — only
get_entity_contextexists today. - An append-only audit sink with hash-chain verifier. The only audit sink in this release writes to a file and to SLF4J.