Repository navigation
feat(examples): add Postgres CDC and webhook ingestion recipe with reactive queries - #218
Merged
Merged
Conversation
Adds examples/ingestion: Postgres -> Debezium Server (HTTP sink) and signed SaaS webhooks -> a small adapter -> InputLayer facts, plus a docker-compose stack and a self-checking demo in which a watching client receives every change to a derived answer. Each change becomes one atomic program that replaces the fact by key and records its source revision (Postgres LSN, webhook sequence) in ingest_revision, so replays and late out-of-order deliveries are no-ops and deletes become retractions with a tombstone revision. Every write endpoint verifies Standard Webhooks signatures. Documented as the "Ingestion (CDC & Webhooks)" guide.
…nd watcher failures
…d timeout documentation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Ensure that we have perfectly ready streaming KG reworking on which AI agents can subscribe to queries and get results in the reactive mode of working. Always follow clean code principles and clean code architecture. Ensure that the computational real-time aspects, where results come back in milliseconds, stay the same; if locks get added, ensure this does not hurt performance. The performance policy should be done as a pipeline which is always run before a PR commit. Agents subscribing through the engine WebSocket and SDK is what should already be expected in the tests; make sure those expectations are part of the e2e quality checks pipeline. The engine is installed per tenant. Ignore draft PRs. Do not run CI redundantly; it is okay to move quickly and break things, then run QC at certain checkpoints. Call them release checkpoints, where the entire CI and QC verification runs. To move quickly, run and test things locally, make sure the relevant checks run in parallel before making the commit, and only run the checks that make sense, not everything.
What Changed
examples/ingestion/, a runnable Docker Compose stack (Postgres, Debezium Server, a Python adapter and InputLayer) withdemo.sh. The script generates credentials and ports bound to loopback, then runs a self-checking demo: Postgres row changes and signed billing webhooks drive theneeds_attentionrule, and a client subscribed over WebSocket receives every insert and retraction delta.adapter/ingest/) does the following:/cdcand/webhooks/<source>.mappings.py.appliedandskippedcounters come from committed updates.ENGINE_REQUEST_TIMEOUTand returns503on expiry.guides/ingestion.mdxdocs guide and its nav entry. The guide covers the walkthrough, ordering and replay guarantees, acknowledgment semantics, signatures, timeouts and production notes.Risk Assessment
✅ Low: The change is confined to the authorized ingestion recipe and documentation, with no substantiated material defects after tracing authentication, conditional commits, retries, counters, and watcher failures.
Testing
I built the engine from this worktree and ran the adapter's engine-backed tests: all 95 passed, none skipped. I also ran the real Docker demo on an isolated stack. CDC and webhook changes reached the watching client as exact deltas in tens of milliseconds, and duplicates and late webhooks were skipped. Adversarial checks also passed: bad signatures got 401, a paused engine got 503 at the 10s deadline, and the adapter recovered with a safe skipped retry. The default published engine image speaks WebSocket protocol 1, so the adapter refuses it. I report that as an informational note. Everything here is CLI or service behaviour, so there is no visual evidence. The stack and caches were torn down.
Evidence: Docker e2e demo transcript (CDC and webhooks to standing-query deltas)
Evidence: Live adversarial checks: signatures, engine stall deadline, recovery, retry
Evidence: Adapter engine-backed pytest run
Evidence: demo.sh with default ghcr latest image (protocol 1 refused)
Evidence: Demo excerpt
Evidence: Engine stall excerpt
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 8 issues found → auto-fixed (3) ✅
examples/ingestion/adapter/ingest/demo.py:177- The required SDK subscription path remains absent. The intent requires agents subscribing through “the engine WebSocket and SDK” in the “e2e quality checks pipeline,” but the addedWatcher.open(...)uses a separate example client. Public SDKs still discard subscription pushes (packages/inputlayer-js/src/connection.ts:149; packages/inputlayer-py/src/inputlayer/connection.py:338), and TESTING.md explicitly excludes SDK agents. Neither the new demo nor its tests is included in the release-checkpoint pipeline. Implement and exercise the public SDK path through that pipeline.examples/ingestion/adapter/ingest/applier.py:50- Replay safety fails across an interrupted connection: revision 10's write can remain executing on the engine after the adapter detects disconnection; a reconnected request commits revision 11; the older write then commits unconditionally, restoring revision 10 and stale data. The engine awaits execution before reading socket closure. The invariant must cover applier.py:50 (separate revision read), applier.py:77 (reconnect), and iql.py:60–66 (data deletion/insertion and watermark replacement), for both CDC and webhooks. The supported boundary is the engine's atomic program commit, including revision validation. This defect belongs to the unrequired ingestion subsystem identified in R8; removing that subsystem is the smallest scope-compliant remedy.examples/ingestion/adapter/ingest/debezium.py:55- With the configured default replica identity, updating the tier of a customer whose unchanged name is stored out-of-line produces Debezium's unavailable-value placeholder. row_from accepts that string, and the adapter overwrites the real name, advances the revision and returns 200. Both mapped text fields, name and tier, lack handling for unavailable values. Related sites: postgres/init.sql:5–6 defines unrestricted text; mappings.py:15 maps both fields; docs/content/docs/guides/ingestion.mdx:113 incorrectly says default replica identity suffices. Debezium documents this behavior. This is inside R8's unrequired CDC component; removal is the smallest scope-compliant remedy.examples/ingestion/docker-compose.yml:15- Running the documented demo publishes the engine on all host interfaces with the public bootstrap credential from line 6. Any reachable peer can authenticate and read or modify its knowledge graphs. The same exposure occurs at line 43: the public secrets at lines 7–8 let peers forge CDC and billing writes. This contradicts the file's instruction to replace secrets before exposing ports; exposure happens immediately. These publications belong to R8's unrequired Compose stack, whose removal is the smallest scope-compliant remedy.examples/ingestion/adapter/ingest/demo.py:187- The self-check can report success after its watcher fails. If the connection closes or a subscription_error arrives after the final expected retraction, collect() terminates with an exception, but expect_quiet() treats the empty queue as success and main prints “all deltas matched.” The collector is never awaited or checked. Related sites: demo.py:49 collects, :145 interprets silence, :184 starts the task, and :189 cancels without observing failure. This belongs to R8's unrequired ingestion demo; removing that demo is the smallest scope-compliant remedy.examples/ingestion/demo.sh:12- The intent requires performance policy as a pipeline “always run before a PR commit.” That source-level integration is absent: make perf-gate remains a standalone manual target, and the added demo.sh:10–12 only builds and runs the example. Its demo.py:28 timeout permits 15-second delivery and does not enforce the millisecond requirement. Add the required pre-commit performance integration and coverage for the introduced serialized ingestion path, or explicitly amend the criterion. This concerns missing implementation, not this run's deferred test or CI results..github/workflows/ci.yml:10- The explicit criterion “Ignore draft PRs” remains unimplemented. The branch leaves the pull_request workflows without draft guards: .github/workflows/ci.yml:10 runs the fast gate for drafts, .github/workflows/full-suite.yml:39 permits release-labeled drafts, and .github/workflows/docker.yml:26 permits their image builds. The added examples also match the existing examples/** fast-gate filter. Add draft exclusion to these entry points or obtain an explicit amendment to the criterion.examples/ingestion/docker-compose.yml:22- Simplification: the intent requires reactive engine/SDK subscriptions and quality gates, not a new ingestion product. The added Postgres/Debezium service and mapper, billing webhook endpoint and signing scheme, configurable relation/type/key mappings, batch/schema-wrapper/tombstone acceptance paths, persistent ingest_revision ledger, retrying adapter service, separate WebSocket client/watcher, Docker stack, demo and ingestion guide exceed that scope. Primary sites: docker-compose.yml:22, :38, :52; debezium.py:21; webhooks.py:38; signing.py:28; changes.py:30; iql.py:17; server.py:89; engine.py:59; watch.py:41; demo.py:36; docs/content/docs/guides/ingestion.mdx:1. Remove these components and implement the required subscription path in the existing SDKs and quality pipeline.🔧 Fix applied.
6 issues (4 errors, 2 warnings) still open:
examples/ingestion/adapter/ingest/demo.py:177- The required SDK subscription path remains absent. The intent requires agents subscribing through “the engine WebSocket and SDK” in the “e2e quality checks pipeline,” but the addedWatcher.open(...)uses a separate example client. Public SDKs still discard subscription pushes (packages/inputlayer-js/src/connection.ts:149; packages/inputlayer-py/src/inputlayer/connection.py:338), and TESTING.md explicitly excludes SDK agents. Neither the new demo nor its tests is included in the release-checkpoint pipeline. Implement and exercise the public SDK path through that pipeline.examples/ingestion/adapter/ingest/applier.py:50- Replay safety fails across an interrupted connection: revision 10's write can remain executing on the engine after the adapter detects disconnection; a reconnected request commits revision 11; the older write then commits unconditionally, restoring revision 10 and stale data. The engine awaits execution before reading socket closure. The invariant must cover applier.py:50 (separate revision read), applier.py:77 (reconnect), and iql.py:60–66 (data deletion/insertion and watermark replacement), for both CDC and webhooks. The supported boundary is the engine's atomic program commit, including revision validation. This defect belongs to the unrequired ingestion subsystem identified in R8; removing that subsystem is the smallest scope-compliant remedy.examples/ingestion/demo.sh:12- The intent requires performance policy as a pipeline “always run before a PR commit.” That source-level integration is absent: make perf-gate remains a standalone manual target, and the added demo.sh:10–12 only builds and runs the example. Its demo.py:28 timeout permits 15-second delivery and does not enforce the millisecond requirement. Add the required pre-commit performance integration and coverage for the introduced serialized ingestion path, or explicitly amend the criterion. This concerns missing implementation, not this run's deferred test or CI results..github/workflows/ci.yml:10- The explicit criterion “Ignore draft PRs” remains unimplemented. The branch leaves the pull_request workflows without draft guards: .github/workflows/ci.yml:10 runs the fast gate for drafts, .github/workflows/full-suite.yml:39 permits release-labeled drafts, and .github/workflows/docker.yml:26 permits their image builds. The added examples also match the existing examples/** fast-gate filter. Add draft exclusion to these entry points or obtain an explicit amendment to the criterion.examples/ingestion/docker-compose.yml:22- Simplification: the intent requires reactive engine/SDK subscriptions and quality gates, not a new ingestion product. The added Postgres/Debezium service and mapper, billing webhook endpoint and signing scheme, configurable relation/type/key mappings, batch/schema-wrapper/tombstone acceptance paths, persistent ingest_revision ledger, retrying adapter service, separate WebSocket client/watcher, Docker stack, demo and ingestion guide exceed that scope. Primary sites: docker-compose.yml:22, :38, :52; debezium.py:21; webhooks.py:38; signing.py:28; changes.py:30; iql.py:17; server.py:89; engine.py:59; watch.py:41; demo.py:36; docs/content/docs/guides/ingestion.mdx:1. Remove these components and implement the required subscription path in the existing SDKs and quality pipeline.examples/ingestion/adapter/ingest/applier.py:53- Round 1's R2 fix left the outcome counters based on the pre-read. Concrete sequence: revision 12 remains in flight after disconnection; a reconnected revision-11 request reads watermark 9; revision 12 commits; the engine correctly skips revision 11, but this returns applied=1, skipped=0. Derive both counters from the committed conditional outcome. Related sites: examples/ingestion/adapter/ingest/applier.py:50–53 selects and counts candidates, :55–57 logs incorrect counts; examples/ingestion/adapter/ingest/iql.py:70 conditionally skips writes; examples/ingestion/adapter/ingest/server.py:74 exposes the counts for both CDC and webhooks.🔧 Fix applied.
5 issues (3 errors, 2 warnings) still open:
examples/ingestion/adapter/ingest/demo.py:177- The required SDK subscription path remains absent. The intent requires agents subscribing through “the engine WebSocket and SDK” in the “e2e quality checks pipeline,” but the addedWatcher.open(...)uses a separate example client. Public SDKs still discard subscription pushes (packages/inputlayer-js/src/connection.ts:149; packages/inputlayer-py/src/inputlayer/connection.py:338), and TESTING.md explicitly excludes SDK agents. Neither the new demo nor its tests is included in the release-checkpoint pipeline. Implement and exercise the public SDK path through that pipeline.examples/ingestion/demo.sh:12- The intent requires performance policy as a pipeline “always run before a PR commit.” That source-level integration is absent: make perf-gate remains a standalone manual target, and the added demo.sh:10–12 only builds and runs the example. Its demo.py:28 timeout permits 15-second delivery and does not enforce the millisecond requirement. Add the required pre-commit performance integration and coverage for the introduced serialized ingestion path, or explicitly amend the criterion. This concerns missing implementation, not this run's deferred test or CI results..github/workflows/ci.yml:10- The explicit criterion “Ignore draft PRs” remains unimplemented. The branch leaves the pull_request workflows without draft guards: .github/workflows/ci.yml:10 runs the fast gate for drafts, .github/workflows/full-suite.yml:39 permits release-labeled drafts, and .github/workflows/docker.yml:26 permits their image builds. The added examples also match the existing examples/** fast-gate filter. Add draft exclusion to these entry points or obtain an explicit amendment to the criterion.examples/ingestion/docker-compose.yml:22- Simplification: the intent requires reactive engine/SDK subscriptions and quality gates, not a new ingestion product. The added Postgres/Debezium service and mapper, billing webhook endpoint and signing scheme, configurable relation/type/key mappings, batch/schema-wrapper/tombstone acceptance paths, persistent ingest_revision ledger, retrying adapter service, separate WebSocket client/watcher, Docker stack, demo and ingestion guide exceed that scope. Primary sites: docker-compose.yml:22, :38, :52; debezium.py:21; webhooks.py:38; signing.py:28; changes.py:30; iql.py:17; server.py:89; engine.py:59; watch.py:41; demo.py:36; docs/content/docs/guides/ingestion.mdx:1. Remove these components and implement the required subscription path in the existing SDKs and quality pipeline.examples/ingestion/adapter/ingest/engine.py:139- Engine requests have no response deadline. If the engine stalls after accepting a request while its TCP connection remains open, this await holds Applier's shared lock indefinitely. Debezium's 30-second HTTP timeout does not release that lock, so retries and billing requests accumulate behind it instead of receiving 503. Bound each complete engine request, close the connection on expiry, and raise EngineUnavailable so existing reconnect handling applies. Cover sibling waits at engine.py:88 (authentication), engine.py:111 (execution), engine.py:143 (stream completion), and lock holders at applier.py:43 (declaration) and applier.py:50 (revision reads and writes); both HTTP feeds share server.py:67, and startup retries depend on server.py:94 returning.🔧 Fix applied.
4 issues (3 errors, 1 warning) still open:
examples/ingestion/adapter/ingest/demo.py:177- The required SDK subscription path remains absent. The intent requires agents subscribing through “the engine WebSocket and SDK” in the “e2e quality checks pipeline,” but the addedWatcher.open(...)uses a separate example client. Public SDKs still discard subscription pushes (packages/inputlayer-js/src/connection.ts:149; packages/inputlayer-py/src/inputlayer/connection.py:338), and TESTING.md explicitly excludes SDK agents. Neither the new demo nor its tests is included in the release-checkpoint pipeline. Implement and exercise the public SDK path through that pipeline.examples/ingestion/demo.sh:12- The intent requires performance policy as a pipeline “always run before a PR commit.” That source-level integration is absent: make perf-gate remains a standalone manual target, and the added demo.sh:10–12 only builds and runs the example. Its demo.py:28 timeout permits 15-second delivery and does not enforce the millisecond requirement. Add the required pre-commit performance integration and coverage for the introduced serialized ingestion path, or explicitly amend the criterion. This concerns missing implementation, not this run's deferred test or CI results..github/workflows/ci.yml:10- The explicit criterion “Ignore draft PRs” remains unimplemented. The branch leaves the pull_request workflows without draft guards: .github/workflows/ci.yml:10 runs the fast gate for drafts, .github/workflows/full-suite.yml:39 permits release-labeled drafts, and .github/workflows/docker.yml:26 permits their image builds. The added examples also match the existing examples/** fast-gate filter. Add draft exclusion to these entry points or obtain an explicit amendment to the criterion.examples/ingestion/docker-compose.yml:22- Simplification: the intent requires reactive engine/SDK subscriptions and quality gates, not a new ingestion product. The added Postgres/Debezium service and mapper, billing webhook endpoint and signing scheme, configurable relation/type/key mappings, batch/schema-wrapper/tombstone acceptance paths, persistent ingest_revision ledger, retrying adapter service, separate WebSocket client/watcher, Docker stack, demo and ingestion guide exceed that scope. Primary sites: docker-compose.yml:22, :38, :52; debezium.py:21; webhooks.py:38; signing.py:28; changes.py:30; iql.py:17; server.py:89; engine.py:59; watch.py:41; demo.py:36; docs/content/docs/guides/ingestion.mdx:1. Remove these components and implement the required subscription path in the existing SDKs and quality pipeline.✅ No issues found.
✅ No issues found.
cargo build --bin inputlayer-serverPYTHONPATH=examples/ingestion/adapter tmp/ingestion-test/venv/bin/python -m pytest examples/ingestion/adapter/tests --basetemp=tmp/ingestion-test/pytest -v --log-cli-level=INFOINPUTLAYER_PORT=18080 ADAPTER_PORT=18090 docker compose --project-name nm-ingestion-test --env-file tmp/ingestion-test/stack.env -f examples/ingestion/docker-compose.yml -f tmp/ingestion-test/override.json up -d --no-build --wait inputlayer postgres adapter debeziumINPUTLAYER_PORT=18080 ADAPTER_PORT=18090 docker compose --project-name nm-ingestion-test --env-file tmp/ingestion-test/stack.env -f examples/ingestion/docker-compose.yml -f tmp/ingestion-test/override.json run --rm --no-deps demoPYTHONPATH=examples/ingestion/adapter tmp/ingestion-test/venv/bin/python ~/.no-mistakes/evidence/01M41QR6R67KDDMZKWKPT67D5E/live_checks.pyPYTHONPATH=examples/ingestion/adapter tmp/ingestion-test/venv/bin/python ~/.no-mistakes/evidence/01M41QR6R67KDDMZKWKPT67D5E/restart_checks.pyRemoved disposable containers, volumes, environment, and generated data; verified a clean worktree.ℹ️
examples/ingestion/docker-compose.yml:11- ./demo.sh with its default engine image (ghcr.io/inputlayer/inputlayer:latest, built 2026-10-03 11:18) fails: that image speaks WebSocket protocol 1, which the adapter refuses as designed, so the adapter never becomes healthy. The demo passes with a protocol-2 engine image passed through INPUTLAYER_IMAGE, which the guide documents. Out-of-the-box use depends on publishing a newer engine image.Live validation: ✅ go - 7 of 7 scenarios driven live against the product
cargo build --bin inputlayer-server(engine from this worktree for engine-backed regressions)python -m pytest -v -rsin examples/ingestion/adapter with the real engine binary (95 passed, 0 skipped: revision guard and disconnect race, committed-count derivation, request deadlines, mapping, signatures, startup)COMPOSE_PROJECT_NAME=ilingtest INPUTLAYER_PORT=18080 ADAPTER_PORT=18090 ./demo.shwith default ghcr.io/inputlayer/inputlayer:latest (adapter refused protocol-1 engine)COMPOSE_PROJECT_NAME=ilingtest INPUTLAYER_IMAGE=inputlayer-ing1-main:local INPUTLAYER_PORT=18080 ADAPTER_PORT=18090 ./demo.sh(full 9-step self-checking demo, exit 0)Live POSTs to /webhooks/billing with forged, missing, stale and tampered signaturesdocker pause ilingtest-inputlayer-1then a signed webhook POST (503 deadline),docker unpause, then the same POST followed by its retrydocker compose -p ilingtest down -vteardown and removal of local caches✅ **Document** - passed
✅ No issues found.
✅ No issues found.
🔧 **Lint** - 1 issue found → auto-fixed ✅
examples/ingestion/adapter/pyproject.toml:15- Python lint, formatting, and static-analysis checks remain blocked: Ruff and mypy are absent from PATH, and python3 reports neither module installed. No repository-local tool path was supplied. ShellCheck and git diff --check passed.🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ No issues found.
✅ **Push** - passed
✅ No issues found.
✅ No issues found.