Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copy to .env to override. Every value below already has a working
# default baked into docker-compose.yml (see ${VAR:-default} references) --
# `docker compose up` succeeds from a clean checkout with no .env file at
# all. These defaults are throwaway local-dev-only credentials, not
# production secrets; see docs/adr/0001-demo-identity-and-data-boundary.md.

POSTGRES_USER=lineageweave
POSTGRES_PASSWORD=lineageweave_dev_only
POSTGRES_DB=lineageweave
POSTGRES_PORT=5432

VALKEY_PORT=6379

KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=admin_dev_only
KEYCLOAK_PORT=8080
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ __pycache__/
*.egg-info/
.DS_Store
.codegraph/
.env

17 changes: 17 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,20 @@ identities and content) and `migrations/0001_initial_schema.sql` for the
`post_lineage_edge`). Real-database tests: `tests/test_schema.py`
(skipped without a reachable PostgreSQL server, same pattern as the
real-provider LLM tests).

### Local infrastructure (Docker Compose)

`docker-compose.yml` runs PostgreSQL, Valkey, and a real Keycloak OIDC
provider (`docker/keycloak/realm-export.json` seeds a `lineageweave-demo`
realm with synthetic demo accounts carrying `corp_code` / `pu_code` as
custom token claims -- see [README](README.md#local-product-stack-docker-compose)).
`scripts/smoke_test_oidc.py` proves the round-trip is real: it logs in as
the synthetic demo user, fetches Keycloak's live JWKS, and cryptographically
verifies the returned JWT's RS256 signature rather than just checking for an
HTTP 200. Both Postgres (`docker/postgres-init/`) and Keycloak
(`docker/keycloak/`) are `build:` targets that `COPY` their seed files in,
not bind mounts -- self-contained images that don't depend on any particular
host filesystem layout being reachable from the Docker daemon, which also
makes them reproducible in CI. Valkey is the Phase 2+ event queue (not a
traditional MQ) for asynchronous work like Keyman/Knowledge-Graph
recomputation once posts change.
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@ All notable changes to this project are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.5.0] - 2026-08-13

### Added

- `docker-compose.yml`: PostgreSQL, Valkey, and a real Keycloak OIDC
provider, genuinely functional (not a stub/mocked adapter). `make up`
brings up all three from a clean checkout; `make smoke` runs
`scripts/smoke_test_oidc.py`, which logs in as a synthetic demo user
seeded by `docker/keycloak/realm-export.json`, fetches Keycloak's live
JWKS, and cryptographically verifies the returned JWT's RS256 signature,
issuer, and `corp_code`/`pu_code` custom claims -- a real round-trip
proof, not a "the container started" check.
- `docker/postgres-init/` and `docker/keycloak/`: both services are `build:`
targets (Dockerfiles that `COPY` in the keycloak-db init script and the
realm seed) rather than bind mounts, so the images are self-contained and
reproducible on any Docker host or CI runner.
- Keycloak stores its own state in a second database (`keycloak`) on the
same PostgreSQL instance -- one running database service for the whole
stack, no second file-backed store.
- `.env.example` documents the (already-defaulted) compose variables,
including how to remap host ports if 5432/6379/8080 are already taken
locally.

## [0.4.0] - 2026-08-13

### Added
Expand Down
20 changes: 20 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.PHONY: up down logs smoke ps

up:
docker compose up -d

down:
docker compose down

logs:
docker compose logs -f

ps:
docker compose ps

# Real OIDC round-trip against the running Keycloak container: logs in as
# the synthetic demo user, verifies the returned JWT's signature against
# Keycloak's live JWKS, and asserts the corp_code/pu_code claims. See
# scripts/smoke_test_oidc.py.
smoke:
python3 scripts/smoke_test_oidc.py
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,38 @@ pip install -e ".[dev]"
pytest
```

## Local product stack (Docker Compose)

The reconstruction library above is being wrapped in a real product (see
[ARCHITECTURE.md](ARCHITECTURE.md#product-schema-phase-1-of-a-larger-roadmap)
and [ADR 0001](docs/adr/0001-demo-identity-and-data-boundary.md)). Phase 1's
infrastructure -- PostgreSQL, Valkey, and a real Keycloak OIDC realm seeded
with synthetic demo accounts -- runs via Docker Compose:

```bash
make up # docker compose up -d: postgres, valkey, keycloak
make smoke # real login as the synthetic demo user + JWT signature
# verification against Keycloak's live JWKS -- proves the
# OIDC round-trip actually works, not just that containers
# started
make down
```

Postgres and Keycloak are built (`docker/postgres-init/`, `docker/keycloak/`)
rather than bind-mounted, so the keycloak database's init script and the
realm seed ship inside the images themselves -- portable to any Docker host
or CI runner, no assumption about a shared local filesystem layout.

Demo accounts (`docker/keycloak/realm-export.json`) are synthetic:
`demo.analyst` / `demo.admin`, password `lineageweave-demo-only`, each
carrying `corp_code` / `pu_code` as token claims -- these are throwaway
local-dev credentials in a locally-run realm, never the org's real Keyverse
tenant (see ADR 0001 for why).

If a port in `docker-compose.yml` (5432, 6379, 8080) is already taken
locally, override it via `.env` (copy `.env.example`) or inline, e.g.
`KEYCLOAK_PORT=18080 make up`.

## Modular / standalone

This repo runs standalone (own server, own tests, own CI) and is equally
Expand Down
58 changes: 58 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
services:
postgres:
# Built (not bind-mounted) so the keycloak-db init script ships inside
# the image itself -- portable across hosts/CI runners that don't share
# a filesystem with the Docker daemon.
build: ./docker/postgres-init
environment:
POSTGRES_USER: ${POSTGRES_USER:-lineageweave}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-lineageweave_dev_only}
POSTGRES_DB: ${POSTGRES_DB:-lineageweave}
ports:
- "${POSTGRES_PORT:-5432}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-lineageweave} -d ${POSTGRES_DB:-lineageweave}"]
interval: 5s
timeout: 5s
retries: 10

valkey:
image: valkey/valkey:8-alpine
ports:
- "${VALKEY_PORT:-6379}:6379"
volumes:
- valkey_data:/data
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 5s
timeout: 5s
retries: 10

keycloak:
# Built (not bind-mounted) so the realm-export.json seed ships inside
# the image itself -- portable across hosts/CI runners that don't share
# a filesystem with the Docker daemon.
build: ./docker/keycloak
command: ["start-dev", "--import-realm"]
environment:
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN:-admin}
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin_dev_only}
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
KC_DB_USERNAME: ${POSTGRES_USER:-lineageweave}
KC_DB_PASSWORD: ${POSTGRES_PASSWORD:-lineageweave_dev_only}
KC_HOSTNAME: localhost
KC_HOSTNAME_STRICT: "false"
KC_HTTP_ENABLED: "true"
KC_HEALTH_ENABLED: "true"
ports:
- "${KEYCLOAK_PORT:-8080}:8080"
depends_on:
postgres:
condition: service_healthy

volumes:
postgres_data:
valkey_data:
2 changes: 2 additions & 0 deletions docker/keycloak/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM quay.io/keycloak/keycloak:26.0
COPY realm-export.json /opt/keycloak/data/import/realm-export.json
89 changes: 89 additions & 0 deletions docker/keycloak/realm-export.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{
"realm": "lineageweave-demo",
"enabled": true,
"sslRequired": "none",
"registrationAllowed": false,
"accessTokenLifespan": 900,
"roles": {
"realm": [
{ "name": "post_viewer", "description": "Can read posts within their own corp/PU scope (ABAC-narrowed)." },
{ "name": "post_admin", "description": "Can read and manage posts across corp/PU scopes." }
]
},
"clients": [
{
"clientId": "lineageweave-frontend",
"name": "LineageWeave frontend (React)",
"enabled": true,
"publicClient": true,
"protocol": "openid-connect",
"standardFlowEnabled": true,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": false,
"redirectUris": ["http://localhost:5173/*"],
"webOrigins": ["http://localhost:5173"],
"protocolMappers": [
{
"name": "corp-code",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"config": {
"user.attribute": "corp_code",
"claim.name": "corp_code",
"jsonType.label": "String",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
},
{
"name": "pu-code",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"config": {
"user.attribute": "pu_code",
"claim.name": "pu_code",
"jsonType.label": "String",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
}
]
}
],
"users": [
{
"username": "demo.analyst",
"email": "demo.analyst@example.test",
"enabled": true,
"emailVerified": true,
"firstName": "Demo",
"lastName": "Analyst",
"attributes": {
"corp_code": ["DEMO-CORP-01"],
"pu_code": ["DEMO-PU-A"]
},
"credentials": [
{ "type": "password", "value": "lineageweave-demo-only", "temporary": false }
],
"realmRoles": ["post_viewer"]
},
{
"username": "demo.admin",
"email": "demo.admin@example.test",
"enabled": true,
"emailVerified": true,
"firstName": "Demo",
"lastName": "Admin",
"attributes": {
"corp_code": ["DEMO-CORP-01"],
"pu_code": ["DEMO-PU-HQ"]
},
"credentials": [
{ "type": "password", "value": "lineageweave-demo-only", "temporary": false }
],
"realmRoles": ["post_admin", "post_viewer"]
}
]
}
4 changes: 4 additions & 0 deletions docker/postgres-init/01-create-keycloak-db.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Keycloak stores its own realm/session state in a separate database on the
-- same PostgreSQL instance (one running database service, not a second file
-- DB) so the stack stays "PostgreSQL only" per ARCHITECTURE.md.
CREATE DATABASE keycloak;
2 changes: 2 additions & 0 deletions docker/postgres-init/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM postgres:16-alpine
COPY 01-create-keycloak-db.sql /docker-entrypoint-initdb.d/01-create-keycloak-db.sql
2 changes: 1 addition & 1 deletion lineageweave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@

__all__ = ["Edge", "Record", "Tree", "reconstruct"]

__version__ = "0.4.0"
__version__ = "0.5.0"
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
version = "0.4.0"
version = "0.5.0"
description = "Reconstructs git-branch-style lineage DAGs from scattered short records using multi-channel score fusion and LLM adjudication."
readme = "README.md"
license = { text = "MIT" }
Expand All @@ -19,6 +19,7 @@ dependencies = [
dev = [
"pillow>=12.3.0",
"psycopg2-binary>=2.9.12",
"pyjwt[crypto]>=2.8.0",
"pytest>=8.0",
]

Expand Down
Loading
Loading