Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
4a6b584
feat(spp_cel_dci_bridge): scaffold registry-agnostic CEL <-> DCI bridge
gonzalesedwin1123 May 13, 2026
b959025
feat(spp_cel_dci_bridge): extend data.provider and cel.variable for DCI
gonzalesedwin1123 May 13, 2026
85d36b8
feat(spp_cel_dci_bridge): add DCI dispatcher skeleton
gonzalesedwin1123 May 13, 2026
066ee8b
feat(spp_cel_dci_bridge): implement DR registry-type handler
gonzalesedwin1123 May 13, 2026
db6c746
feat(spp_cel_dci_bridge): override cache manager to route DCI externals
gonzalesedwin1123 May 13, 2026
3f67ffb
feat(spp_cel_dci_bridge): implement external_failure_policy
gonzalesedwin1123 May 13, 2026
9162a53
feat(spp_cel_dci_bridge): add per-subject DCI fetch audit log
gonzalesedwin1123 May 13, 2026
2466eba
fix(spp_cel_domain): handle boolean RHS in metric SQL fast path
gonzalesedwin1123 May 13, 2026
73e5092
feat(spp_cel_dci_bridge): end-to-end test of the demo flow
gonzalesedwin1123 May 13, 2026
cb6aa2c
feat(spp_cel_dci_bridge): add CRVS and IBR handlers + registry_type n…
gonzalesedwin1123 May 13, 2026
24f1ed6
feat(spp_dci_openg2p): permanent OpenG2P DCI preset (config-only)
gonzalesedwin1123 May 13, 2026
ac35f1c
docs(spp_cel_dci_bridge,spp_dci_openg2p): readmes + lint fixes
gonzalesedwin1123 May 13, 2026
a28831b
fix(spp_cel_dci_bridge): preserve operator attribution in DCI audit
gonzalesedwin1123 May 13, 2026
291816a
fix(spp_cel_dci_bridge): surface broken DCI integration via DCIConfig…
gonzalesedwin1123 May 13, 2026
9a7e6a7
fix(spp_dci_openg2p): post_init_hook re-asserts DCI binding on upgrade
gonzalesedwin1123 May 13, 2026
0f90f5c
fix(spp_cel_dci_bridge): register menuitems for DCI fetch audit
gonzalesedwin1123 May 13, 2026
a6ba0b7
fix(spp_cel_dci_bridge): dedicate notebook page for DCI integration o…
gonzalesedwin1123 May 13, 2026
ed18796
fix(spp_cel_dci_bridge): gate CEL variable DCI fields on is_dci_backed
gonzalesedwin1123 May 13, 2026
7ee4903
fix(spp_cel_dci_bridge): subject Reference field on DCI fetch audit
gonzalesedwin1123 May 13, 2026
1e5ecbd
chore(spp_cel_dci_bridge): ruff-format + drop redundant field string
gonzalesedwin1123 May 13, 2026
58ca1cb
perf(spp_cel_dci_bridge): use DISTINCT ON for last_known SQL lookup
gonzalesedwin1123 May 13, 2026
54d7e7d
chore(spp_cel_dci_bridge,spp_dci_openg2p): fix pre-commit failures fr…
gonzalesedwin1123 May 13, 2026
1281efa
test(spp_cel_dci_bridge,spp_dci_openg2p): close codecov gaps on error…
gonzalesedwin1123 May 13, 2026
306916a
fix(spp_cel_dci_bridge): drop redundant string= on external_failure_p…
gonzalesedwin1123 May 13, 2026
1d4cb97
fix(spp_cel_dci_bridge,spp_dci_openg2p): drop reimported patch (W0404)
gonzalesedwin1123 May 14, 2026
ce1936a
feat(spp_dci_openg2p): OpenG2P adapter for FR-as-DR demo
gonzalesedwin1123 May 14, 2026
013308f
feat(spp_dci_openg2p): seed UIN vocabulary code for the registrant form
gonzalesedwin1123 May 14, 2026
cc1853f
fix(spp_cel_dci_bridge): pre-warm cache before program-level CEL elig…
gonzalesedwin1123 May 14, 2026
e822163
fix(spp_cel_domain): guard against legacy spp.indicator.evaluate missing
gonzalesedwin1123 May 14, 2026
beba099
fix(spp_dci_openg2p): preset must activate the var_has_disability var…
gonzalesedwin1123 May 14, 2026
8e1c80a
refactor(spp_dci_openg2p): retarget preset to OpenG2P Social Registry…
gonzalesedwin1123 May 14, 2026
d0f1e95
feat(spp_dci_openg2p): replace has_disability override with SR variables
gonzalesedwin1123 May 14, 2026
9b0b6be
feat(spp_dci_server_disability): real /disability/registry/sync/searc…
gonzalesedwin1123 May 14, 2026
52fb056
feat(spp_dci_openspp_dr): SP-side preset for an OpenSPP-DR instance
gonzalesedwin1123 May 14, 2026
56ee2f3
chore(docker): add DR-instance overlay compose for federated demo
gonzalesedwin1123 May 14, 2026
53d0c8e
chore(docker): make DR compose standalone, not an overlay
gonzalesedwin1123 May 14, 2026
aa8eb7d
fix(spp_dci_openspp_dr): drop colliding UIN seed; install no longer f…
gonzalesedwin1123 May 14, 2026
78782e7
fix(spp_dci_server_disability): read real fields, not stale field names
gonzalesedwin1123 May 14, 2026
816659a
fix(spp_dci_openspp_dr): correct DR endpoint path — root_path is /dci…
gonzalesedwin1123 May 14, 2026
cd4ae47
fix(spp_cel_dci_bridge): surface vendor field on data source views
gonzalesedwin1123 May 14, 2026
7195b82
fix(spp_dci_server_disability): sudo() registry lookups for public-us…
gonzalesedwin1123 May 14, 2026
96868aa
feat(spp_dci_openg2p): rebind is_poor to income_level; park dependent…
gonzalesedwin1123 May 14, 2026
2ce3b83
docs: bring all federated-demo docs in line with the shipped implemen…
gonzalesedwin1123 May 14, 2026
af64ddb
chore: add pyproject.toml stubs for the two new federated-demo modules
gonzalesedwin1123 May 14, 2026
9d688a5
demo(scripts): one-shot seed for SPDCI federated dry-run
gonzalesedwin1123 May 15, 2026
b28b419
chore(demo): rename seed script to setup_spdci_demo.py
gonzalesedwin1123 May 15, 2026
c363af8
demo(scripts): extend SPDCI seed to all 15 OpenG2P SR personas
gonzalesedwin1123 May 15, 2026
024698d
demo(scripts): rename in-place + auto-enroll into demo program
gonzalesedwin1123 May 15, 2026
fc37f77
demo(scripts): add reset script for repeated SPDCI demo runs
gonzalesedwin1123 May 15, 2026
503e7fd
fix(spp_cel_domain): AND all metric SQL overrides on compound express…
gonzalesedwin1123 May 15, 2026
4fc8544
docs(demo): add SPDCI demo briefing sheet for the presentation
gonzalesedwin1123 May 15, 2026
ef2049d
chore: pre-commit autofixes + linter compliance pass on branch
gonzalesedwin1123 May 15, 2026
166155e
fix(spp_dci_server_disability): keep nosemgrep marker on the sudo() line
gonzalesedwin1123 May 15, 2026
84328e8
fix(lint): clear remaining pre-commit + Semgrep OSS findings
gonzalesedwin1123 May 15, 2026
f49a443
docs(demo): add OpenG2P demo data CSV with 15 personas
gonzalesedwin1123 May 18, 2026
3bca0cf
docs(demo): trim OpenG2P demo CSV to 8 identity/demographic columns
gonzalesedwin1123 May 18, 2026
27aecd0
docs(demo): walkthrough of CEL-to-DCI execution for presentation
gonzalesedwin1123 May 18, 2026
cdbce38
feat(spp_dci_openg2p): add SR-import wizard for operator-driven regis…
gonzalesedwin1123 May 18, 2026
2983985
refactor(spp_dci_openg2p): relabel user-facing strings from "OpenG2P"…
gonzalesedwin1123 May 18, 2026
50c9b59
fix(spp_dci_openg2p): use canonical registry_type URI in SR-import wi…
gonzalesedwin1123 May 18, 2026
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
113 changes: 113 additions & 0 deletions docker-compose.dr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# OpenSPP-DR standalone docker-compose
#
# Self-contained file for the second OpenSPP container that plays the
# Disability Registry role in the federated demo topology (ADR-024).
# The SP side keeps its own lifecycle through `./spp` against
# docker-compose.yml — this file touches none of those services.
#
# Networking: the DR container joins the SP project's existing Docker
# network (`openspp2_openspp` by default) as an EXTERNAL network so the
# two containers can resolve each other by service name. Make sure the
# SP is up (`./spp start`) before launching the DR — the external
# network must exist.
#
# Database: shares the SP's `db` container by joining the same network
# and using DB_HOST=db, but uses a distinct database name (`openspp_dr`)
# so neither side sees the other's records.
#
# Usage:
#
# # Prereq: SP is up (network + db exist)
# ./spp start
#
# # Launch DR
# docker compose -f docker-compose.dr.yml up -d
#
# # Access:
# # SP UI (existing): determined by `./spp url` (ui=8069, dev=dynamic)
# # DR UI (new): http://localhost:8070 (admin/admin)
# # SP -> DR in-network: http://openspp-dr:8069
#
# # Logs / shell / stop
# docker compose -f docker-compose.dr.yml logs -f openspp-dr
# docker compose -f docker-compose.dr.yml exec openspp-dr odoo shell -d openspp_dr
# docker compose -f docker-compose.dr.yml down
#
# # Wipe DR data (drops openspp_dr filestore volume; the SP's openspp
# # database in the shared db container is untouched, but you'll want
# # to manually DROP DATABASE openspp_dr in psql for a clean re-init).
# docker compose -f docker-compose.dr.yml down -v
#
# Module wiring:
# - SP container: install `spp_dci_openspp_dr` + `spp_dci_openg2p`
# via the SP's ODOO_INIT_MODULES (set before `./spp start`).
# - DR container: defaults to `spp_dci_server_disability` (overridable
# via ODOO_DR_INIT_MODULES).
#
# Network-name override:
# If your SP project name isn't `openspp2` (e.g. you cloned into a
# differently-named directory), set OPENSPP_NETWORK before launching:
# OPENSPP_NETWORK=<project>_openspp docker compose -f docker-compose.dr.yml up -d

name: openspp-dr

services:
openspp-dr:
image: openspp-dev
# No `build:` here — relies on `./spp start` (or `./spp build`)
# having produced the openspp-dev image first. This keeps the DR
# standalone but avoids duplicating build state.
container_name: openspp-dr
hostname: openspp-dr
environment:
# Connect to the SP project's db container by service-name DNS
# resolution on the shared network.
DB_HOST: db
DB_PORT: "5432"
DB_USER: odoo
DB_PASSWORD: odoo
DB_NAME: ${ODOO_DR_DB_NAME:-openspp_dr}
DB_FILTER: "^${ODOO_DR_DB_NAME:-openspp_dr}$$"
LIST_DB: "False"

ODOO_ADMIN_PASSWD: admin

ODOO_WORKERS: "0"
ODOO_CRON_THREADS: "0"

# The DR-side server module. Override to install richer demo
# registrant data alongside the server endpoint.
ODOO_INIT_MODULES: "${ODOO_DR_INIT_MODULES:-spp_dci_server_disability}"
ODOO_UPDATE_MODULES: "${ODOO_DR_UPDATE_MODULES:-}"

LOG_LEVEL: info
PROXY_MODE: "False"
ports:
# Host 8070 → container 8069. SP-side OpenSPPDRService talks to
# http://openspp-dr:8069 over the shared Docker network; the
# host port is only for the operator's browser.
- "8070:8069"
volumes:
- .:/mnt/extra-addons/openspp:ro,z
- openspp_dr_data:/var/lib/odoo
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8069/web/health"]
interval: 10s
timeout: 10s
start_period: 90s
retries: 10
networks:
- sp-shared

volumes:
openspp_dr_data:

networks:
# Join the SP project's existing network so we can reach the SP's
# `db` container and the SP's openspp container can reach us by name.
# The default name `openspp2_openspp` reflects the SP project's
# default name (derived from the OpenSPP2 directory) — override via
# OPENSPP_NETWORK if your SP project is named differently.
sp-shared:
name: ${OPENSPP_NETWORK:-openspp2_openspp}
external: true
361 changes: 361 additions & 0 deletions scripts/demo/SPDCI_CEL_TO_DCI_FLOW.md

Large diffs are not rendered by default.

196 changes: 196 additions & 0 deletions scripts/demo/SPDCI_DEMO_BRIEFING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# SPDCI Federated Demo — Briefing Sheet

A one-page reference for the live demo of OpenSPP V2's SPDCI federated-eligibility flow.

## The narrative

> A government runs a social-protection program — **Disability Assistance** — that
> targets registrants who are **both** living with a disability **and** classified as
> poor. The two facts live in two independent registries owned by two independent
> agencies. OpenSPP composes the eligibility decision by querying both over the DCI
> standard, in real time, with a single click.

## Demo eligibility rule

```
has_disability == true && is_poor == "low"
```

| Clause | Source registry | Resolved via |
| ---------------- | ------------------------------------------------------------------------- | ----------------------------------------------------- |
| `has_disability` | **OpenSPP-DR** (Disability Registry, a sibling OpenSPP instance) | DCI `/dci_api/v1/disability/registry/sync/search` |
| `is_poor` | **OpenG2P SR** (National Social Registry, `partner-nsr.play.openg2p.org`) | DCI `/dci/registry/sync/search`, reads `income_level` |

Both fetches happen inside Enroll Eligible's pre-warm phase. The CEL executor ANDs the
two SQL subqueries against the SP's cohort.

## The 15 demo registrants

Each persona on the SP carries a UIN reg_id matching an OpenG2P SR seed identifier
(`IND-NSR-0001`..`IND-NSR-0015`). Names mirror OpenG2P's actual seed records so the
federation story stays honest — operators can curl OpenG2P and verify the same name
comes back.

| # | UIN | Registrant | OpenG2P `income_level` | OpenSPP-DR `has_disability` | Verdict |
| --- | -------------- | ------------- | ---------------------- | --------------------------- | ----------------------------- |
| 1 | `IND-NSR-0001` | Alex Rivera | **low** | **true** | ✅ **ENROLLED** |
| 2 | `IND-NSR-0002` | Priya Rivera | low | false | not eligible (no DR record) |
| 3 | `IND-NSR-0003` | Noah Rivera | (empty) | false | not eligible (both fail) |
| 4 | `IND-NSR-0004` | Morgan Cole | **low** | **true** | ✅ **ENROLLED** |
| 5 | `IND-NSR-0005` | Leah Cole | low | false | not eligible (no DR record) |
| 6 | `IND-NSR-0006` | Nia Cole | (empty) | true | not eligible (income not low) |
| 7 | `IND-NSR-0007` | Kim Lee | medium | true | not eligible (income not low) |
| 8 | `IND-NSR-0008` | Jun Lee | medium | false | not eligible (both fail) |
| 9 | `IND-NSR-0009` | Rin Lee | (empty) | true | not eligible (income not low) |
| 10 | `IND-NSR-0010` | Taylor Brooks | **low** | **true** | ✅ **ENROLLED** |
| 11 | `IND-NSR-0011` | Iris Brooks | (empty) | false | not eligible (both fail) |
| 12 | `IND-NSR-0012` | Reyn Brooks | (empty) | false | not eligible (both fail) |
| 13 | `IND-NSR-0013` | Sam Hayes | **low** | **true** | ✅ **ENROLLED** |
| 14 | `IND-NSR-0014` | Dev Hayes | low | false | not eligible (no DR record) |
| 15 | `IND-NSR-0015` | Asha Hayes | (empty) | true | not eligible (income not low) |

**Expected outcome**: 4 / 15 enrolled. The other 11 illustrate each failure mode of the
AND'd rule.

## Topology

```
┌────────────────────────┐
│ OpenG2P SR (cloud) │
│ partner-nsr │
│ Returns income_level │
└───────────▲────────────┘
│ DCI search-sync
│ (HTTPS, expression query)
┌──────────────────────┐ ┌────────────┴────────────┐ ┌──────────────────────┐
│ Operator clicks │──▶│ OpenSPP SP instance │──▶│ OpenSPP-DR instance │
│ "Enroll Eligible" │ │ (./spp container) │ │ (sibling container)│
│ │ │ │ │ │
│ Program rule: │ │ • spp_cel_dci_bridge │ │ • spp_disability_ │
│ has_disability == │ │ • spp_dci_openg2p (SR) │ │ registry │
│ true && │ │ • spp_dci_openspp_dr │ │ • spp_dci_server │
│ is_poor == "low" │ │ (DR client) │ │ • spp_dci_server_ │
│ │ │ │ │ disability │
└──────────────────────┘ └─────────────────────────┘ └──────────────────────┘
│ DCI search-sync (HTTP, in-container network)
http://openspp-dr:8069
```

The bridge fans the eligibility check out to two independent registries, caches the
results in `spp.data.value`, audits every fetch in `spp.dci.fetch.audit`, and lets the
CEL executor compose the final eligibility decision in one SQL query.

## Glossary

### Standards & protocols

**SPDCI** — Social Protection Digital Convergence Initiative. A community-driven effort
under the broader DCI banner to standardise how social-protection MIS systems
interoperate with identification, civil-registration, and other government registries.

**DCI** — Digital Convergence Initiative. The umbrella body publishing open standards
for cross-registry data exchange, hosted at [spdci.org](https://spdci.org). The DCI
specs define wire-level message envelopes, header conventions, signature/consent blocks,
and per-registry search semantics.

**Search-Sync** — DCI's synchronous search protocol. A POST request carrying a DCI
envelope (`signature`, `header`, `message.search_request`) returns matching registry
records in the same HTTP response. Used for "tell me what you know about this person"
lookups. Contrasted with search-async (a callback-based variant for long-running
queries).

**OIDC / OAuth2** — The OpenID Connect / OAuth 2.0 family. Used for authentication and
authorisation, especially with MOSIP eSignet. Different protocol from DCI search-sync:
OIDC mediates **user authentication via browser redirect**; DCI does **server-to-server
data lookup**.

### Registries

**SR — Social Registry**. Holds household-composition and socio-economic data used for
eligibility targeting (e.g., `income_level`, `marital_status`, `employment_status`). In
this demo, the SR is OpenG2P's playground at `partner-nsr.play.openg2p.org`. SPDCI
registry-type code: `SR`.

**DR — Disability Registry**. Holds disability assessments and related data (e.g.,
`has_disability`, severity, review cadence). In this demo, the DR is a second OpenSPP
instance running `spp_disability_registry` + `spp_dci_server_disability`. SPDCI
registry-type code: `DR`.

**CRVS — Civil Registration and Vital Statistics**. Holds birth/death/marriage records.
Not used in this demo. Code: `CRVS`.

**IBR — Integrated Beneficiary Registry**. Cross-program beneficiary index, often used
to detect duplicate enrollment. Not used in this demo. Code: `IBR`.

**FR — Functional/Farmer Registry**. Domain-specific registries (e.g., farmer
registries). Code: `FR`.

### CEL

**CEL — Common Expression Language**. Google's open-source domain-specific language for
evaluating boolean and numeric expressions. OpenSPP uses CEL for program eligibility
rules. Example:

```
has_disability == true && age_years(r.birthdate) >= 18
```

**CEL accessor** — The identifier inside a CEL rule that references a registrant
attribute (e.g., `has_disability`, `is_poor`, `age_years`). Accessors are
**vendor-neutral** by design — rewriting a rule to read from a different vendor's
registry doesn't change the CEL surface, only the data-source configuration backing the
accessor.

**`spp.cel.variable`** — The Odoo model that backs a CEL accessor. Carries the value
type, source (`field` / `external` / `computed` / `aggregate`), provider link, cache
strategy, and other metadata.

**`metric()` call** — How the CEL planner translates a registry-backed variable when
evaluating. A rule like `has_disability == true` compiles to
`metric('has_disability', me) == true` and the executor's SQL fast path turns that into
an `('id', 'in', <SQL subquery>)` clause on `spp.data.value`.

### OpenSPP-side terminology

**`spp.dci.data.source`** — A configured DCI endpoint (host, path, auth, sender/receiver
ids, vendor adapter). One per external registry.

**`spp.data.provider`** — The CEL framework's reference to a backing source. A
DCI-backed provider has `dci_data_source_id` set.

**`spp.cel.dci.dispatcher`** — Bridge code that, for a CEL variable backed by a DCI
source, routes the fetch to the right per-registry-type handler (`_handler_sr`,
`_handler_dr`, etc.) which then delegates to a vendor service adapter.

**Vendor adapter** — Optional Python service class that absorbs vendor-specific
request/response quirks. Examples in this demo: `OpenG2PSocialService` (handles
OpenG2P's expression-query / consent-block shape), `OpenSPPDRService` (unwraps
`data.reg_records[0]` correctly).

**`spp.data.value`** — Persistent cache of resolved variable values. Each row:
`(subject_model, subject_id, variable_name, period_key, value_json, expires_at, ...)`.

**`spp.dci.fetch.audit`** — Compliance log of every DCI fetch. One row per subject per
fetch, regardless of outcome (ok / not_found / error). Surfaces who queried what, when,
with what response.

### Other systems (referenced but out of scope)

**MOSIP** — Modular Open Source Identity Platform. National identity system used by
several governments. Future-work integration point for SPDCI; not in this demo.

**eSignet** — MOSIP's OIDC-compliant authentication service. Mediates user identity
verification via browser redirect + KYC token. Different protocol family from DCI
search-sync. Phase 4 roadmap item in ADR-024.

**OpenG2P** — Open-source social-protection platform. Provides the SR
(`partner-nsr.play.openg2p.org`) used in this demo's federated eligibility flow.

## See Also

- ADR-023 — CEL ↔ DCI External Fetch Bridge
- ADR-024 — Federated DCI Demo Topology for SPDCI
- `docs/plans/SPP_DCI_FEDERATED_DEMO_PLAN.md` — implementation plan
- `scripts/demo/setup_spdci_demo.py` — seed script for the 15 demo personas
- `scripts/demo/reset_spdci_demo.py` — per-iteration reset (membership + cache)
16 changes: 16 additions & 0 deletions scripts/demo/openg2p_demo_data.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
uin,given_name,surname,sex,birth_date,marital_status,employment_status,occupation
IND-NSR-0001,Alex,Rivera,male,1984-04-10,married,self employed,wage labourer
IND-NSR-0002,Priya,Rivera,female,1986-06-22,married,self employed,petty trader
IND-NSR-0003,Noah,Rivera,male,2017-01-15,single,student,student
IND-NSR-0004,Morgan,Cole,female,1968-09-02,widowed,unemployed,small-plot farmer
IND-NSR-0005,Leah,Cole,female,1998-02-10,single,employed,shop assistant
IND-NSR-0006,Nia,Cole,female,2019-11-01,single,student,student
IND-NSR-0007,Kim,Lee,male,1981-07-18,married,employed,clerk
IND-NSR-0008,Jun,Lee,female,1982-12-04,married,self employed,tailor
IND-NSR-0009,Rin,Lee,female,1953-05-30,widowed,retired,retired
IND-NSR-0010,Taylor,Brooks,others,1990-03-25,single,unemployed,none
IND-NSR-0011,Iris,Brooks,female,1957-11-12,widowed,retired,retired
IND-NSR-0012,Reyn,Brooks,male,2015-08-19,single,student,student
IND-NSR-0013,Sam,Hayes,female,1987-01-05,married,self employed,livestock keeper
IND-NSR-0014,Dev,Hayes,male,1984-07-20,married,self employed,livestock keeper
IND-NSR-0015,Asha,Hayes,female,2011-06-14,single,student,student
Loading
Loading