Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
341836e
fix: diagnose execution refusals and bound Kafka JVM heap pressure
BobbyAxerol Oct 1, 2026
7d17c29
ops: record broker GC rollout and measured consumer recovery boundary
BobbyAxerol Oct 1, 2026
07c8462
docs: preserve automatic reconnect failures in rollout evidence
BobbyAxerol Oct 1, 2026
855c914
fix(kafka): bound projector recovery and separate durable delivery fr…
BobbyAxerol Oct 1, 2026
04ac02e
docs: record scoped Kafka recovery rollout and acceptance
BobbyAxerol Oct 1, 2026
729436e
fix(projector): drain on signals and instrument bounded handoff
BobbyAxerol Oct 1, 2026
881ace4
docs: record paired live handoff evidence and remaining availability gap
BobbyAxerol Oct 1, 2026
9a1a054
fix(projector): isolate watermark requests from data fetch during han…
BobbyAxerol Oct 1, 2026
0fe9d37
fix: add bounded canonical hot backup for query handoff
BobbyAxerol Oct 1, 2026
13718f4
fix(query): type hot-backup lineage refusals and record guarded rehea…
BobbyAxerol Oct 1, 2026
4fe0d6a
docs: attribute hot-backup rehearsal guard expiry with cache evidence
BobbyAxerol Oct 1, 2026
2756f39
docs: record hot-backup live acceptance decision boundary
BobbyAxerol Oct 1, 2026
8d546d9
docs(plan): link approved TS readiness correction to backup rollout
BobbyAxerol Oct 1, 2026
b62d033
docs: resume hot-backup rehearsal after TS readiness acceptance
BobbyAxerol Oct 1, 2026
a957ae2
fix: require broker-confirmed progress for canonical hot backup
BobbyAxerol Oct 1, 2026
c8015ca
fix: retain latest canonical quality on ineligible hot reads
BobbyAxerol Oct 1, 2026
44f899f
docs: record exact hot-backup runtime and rollback packet
BobbyAxerol Oct 1, 2026
e3ccf87
docs: record hot-backup production acceptance and scoped cleanup
BobbyAxerol Oct 1, 2026
cc8f61d
docs: plan consolidated execution data contract closure
BobbyAxerol Oct 1, 2026
272a4f5
docs: journal EDC start and execution evidence regressions
BobbyAxerol Oct 1, 2026
f0479de
docs: consolidate execution data dependency review and approval gate
BobbyAxerol Oct 1, 2026
12785ab
fix: share execution reference proof validation with packaged SDK
BobbyAxerol Oct 1, 2026
bfa25d1
docs: consolidate execution data closure and owner review boundary
BobbyAxerol Oct 1, 2026
76a8815
docs: record native custody proof and scoped cleanup evidence
BobbyAxerol Oct 1, 2026
7bd8096
docs: record execution read-set lock regression and cleanup
BobbyAxerol Oct 1, 2026
c7cfc1d
docs: record native settlement and package execution-data evidence
BobbyAxerol Oct 1, 2026
1391d38
docs(edc): record paper tick provenance repair evidence
BobbyAxerol Oct 1, 2026
a0ed1b6
docs(edc): record model-specific native readset tests
BobbyAxerol Oct 1, 2026
851ece8
docs(edc): record native paper integration and sender trace
BobbyAxerol Oct 1, 2026
9603f16
docs(edc): record executor intent transport closure slice
BobbyAxerol Oct 1, 2026
a1738cb
docs(edc): record native dispatch guard candidate and remaining gates
BobbyAxerol Oct 1, 2026
7c1793a
docs(edc): consolidate core execution closure for owner review
BobbyAxerol Oct 1, 2026
1eb7a33
docs(edc): resume closure and record deadline and reference ceiling p…
BobbyAxerol Oct 1, 2026
2af1efc
docs(edc): record native metadata pin and Redis proof
BobbyAxerol Oct 1, 2026
478043f
docs(edc): record book proof correction and recovery prerequisite
BobbyAxerol Oct 1, 2026
a243096
fix(reference): retain verified component clocks and capture lineage
BobbyAxerol Oct 1, 2026
537a6d6
docs: consolidate execution data closure plan for owner review
BobbyAxerol Oct 1, 2026
a377aff
docs: record resumed execution ordering verification
BobbyAxerol Oct 1, 2026
8ee22fc
docs: record atomic execution state Redis evidence
BobbyAxerol Oct 1, 2026
e3f2327
docs: record execution invalidation race and cleanup evidence
BobbyAxerol Oct 1, 2026
9c3535f
docs: record validated execution transition evidence
BobbyAxerol Oct 1, 2026
e097671
docs: record ordered projector authority and publish tests
BobbyAxerol Oct 1, 2026
dfd59ca
docs: record ordered snapshot worker integration evidence
BobbyAxerol Oct 1, 2026
6d0a08c
docs: record execution restart and legacy adoption evidence
BobbyAxerol Oct 1, 2026
7b2f103
docs: record quiet retention parity and cleanup evidence
BobbyAxerol Oct 1, 2026
07a508a
docs: consolidate execution data closure for owner review
BobbyAxerol Oct 1, 2026
a9f317d
docs: record exact-feed recovery proof and remaining EDC closure
BobbyAxerol Oct 1, 2026
fff8c8e
docs: freeze consolidated execution data closure for owner review
BobbyAxerol Oct 1, 2026
8ca721c
docs: record ordered delta proof and remaining EDC dependencies
BobbyAxerol Oct 1, 2026
813b084
docs: record native paper data ownership and isolated PG proof
BobbyAxerol Oct 1, 2026
305f6ab
docs: consolidate execution dependency closure for owner approval
BobbyAxerol Oct 1, 2026
3b10bee
docs: record package execution boundary proof and cleanup
BobbyAxerol Oct 1, 2026
35904f0
docs: bind EDC caller inventory to runtime evidence
BobbyAxerol Oct 1, 2026
fe6a6ba
docs: record execution identity regression and native parity boundary
BobbyAxerol Oct 1, 2026
59b5140
docs: record native typed risk parity and caller integration boundary
BobbyAxerol Oct 1, 2026
298e47b
docs: record typed risk caller and reservation rollback evidence
BobbyAxerol Oct 2, 2026
30c6ea2
docs: consolidate execution data closure for owner review
BobbyAxerol Oct 2, 2026
b0d10a1
docs: record performance data dependency and recovery regression
BobbyAxerol Oct 2, 2026
c36068a
docs: record complete valuation publication and database evidence
BobbyAxerol Oct 2, 2026
de74d99
docs: record valuation snapshot race and isolated PG recovery proof
BobbyAxerol Oct 2, 2026
13b6689
docs: consolidate EDC execution dependency closure for owner review
BobbyAxerol Oct 2, 2026
413cd61
docs: record EDC valuation expiry proof and isolated PG evidence
BobbyAxerol Oct 2, 2026
9f110f1
docs: record retained equity proof and Gateway acceptance tests
BobbyAxerol Oct 2, 2026
89ded8a
docs: record typed valuation reader evidence and remaining closure
BobbyAxerol Oct 2, 2026
787f75a
docs: record equity business-state fence and PG evidence
BobbyAxerol Oct 2, 2026
93e8d90
docs: consolidate execution data closure review and approval gates
BobbyAxerol Oct 2, 2026
ae7ec62
docs: record EDC owner refresh proof and remaining closure gates
BobbyAxerol Oct 2, 2026
a348c89
docs: record native valuation evidence and remaining EDC integration
BobbyAxerol Oct 2, 2026
76a34cb
docs: record live caller inventory before EDC activation
BobbyAxerol Oct 2, 2026
ba4f734
docs: record native currency performance proof and EDC boundaries
BobbyAxerol Oct 2, 2026
f40f90c
docs: record bounded deployed trade and mark attribution window
BobbyAxerol Oct 2, 2026
bbef3fa
docs: consolidate execution data closure scope for owner review
BobbyAxerol Oct 2, 2026
052fdac
docs: record verified typed recovery race and scoped cleanup
BobbyAxerol Oct 2, 2026
8247196
docs: record currency provenance proof regression and PG evidence
BobbyAxerol Oct 2, 2026
294d7c9
docs(edc): record native owner provenance tests and remaining integra…
BobbyAxerol Oct 2, 2026
85d0973
docs(edc): record native owner publication proof and runtime boundary
BobbyAxerol Oct 2, 2026
6fe85bd
docs: record TS owner scope tests and real SQL proof cost
BobbyAxerol Oct 2, 2026
e519034
docs(edc): attest scoped TS native owner handoff and preserve availab…
BobbyAxerol Oct 2, 2026
d6d2637
docs(release): prepare v2.2.2 source and runtime convergence packet
BobbyAxerol Oct 2, 2026
1ca285a
docs(release): attest Query candidate and exact rollback handoff
BobbyAxerol Oct 2, 2026
b7b9ddf
docs(release): record explicit remote convergence approval
BobbyAxerol Oct 2, 2026
6966788
fix(ci): format canonical hot-backup Kafka regression
BobbyAxerol Oct 2, 2026
d9e7f15
fix(ci): wire bounded isolated Kafka recovery fault controller
BobbyAxerol Oct 2, 2026
42e2b70
docs(release): seal v2.2.2 scoped Query runtime evidence
BobbyAxerol Oct 2, 2026
9ab2bd0
test(release): align SDK version contract with certified 2.0.7
BobbyAxerol Oct 2, 2026
03d0805
docs(release): resolve frozen certificate through final tag attestation
BobbyAxerol Oct 2, 2026
4defb2d
docs(release): reconcile Binance recovery rollout and scoped consumer…
BobbyAxerol Oct 2, 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
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ jobs:
- name: Start isolated Kafka (KRaft, plaintext, no ACLs)
run: |
docker run -d --name kn-kafka --hostname localhost \
--label qdl.test.namespace=kn-native-integration \
-e KAFKA_HEAP_OPTS="-Xmx512m -Xms256m" \
apache/kafka@sha256:9516fb7634bad307d17c33b589fde9023003b0cb761374f500002b980a3149b9
for attempt in $(seq 1 60); do
Expand All @@ -301,13 +302,41 @@ jobs:
done

- name: Committed reads, no-commit reader, projector stage A/B, market cache (Kafka + Redis)
timeout-minutes: 30
run: |
fault_dir=$(mktemp -d)
broker_id=$(docker inspect --format '{{.Id}}' kn-kafka)
test "$(docker inspect --format '{{index .Config.Labels "qdl.test.namespace"}}' "$broker_id")" = kn-native-integration
# This controller targets the captured immutable CI container ID only.
(
trap 'docker unpause "$broker_id" >/dev/null 2>&1 || true' EXIT
for attempt in $(seq 1 18000); do
if test -f "$fault_dir/ready"; then
docker pause "$broker_id"
printf 'isolated CI broker paused\n' > "$fault_dir/paused"
sleep 10
docker unpause "$broker_id"
printf 'isolated CI broker restored\n' > "$fault_dir/restored"
exit 0
fi
sleep 0.1
done
echo 'Recovery test did not reach the fault boundary' >&2
exit 1
) &
controller=$!
trap 'kill "$controller" 2>/dev/null || true; wait "$controller" 2>/dev/null || true; docker unpause "$broker_id" >/dev/null 2>&1 || true' EXIT
docker run --rm --network container:kn-kafka \
-e QDL_KN_TEST_KAFKA=localhost:9092 \
-e QDL_KN_TEST_REDIS=redis://localhost:6379/0 \
-e QDL_KN_TEST_REDIS_EXCLUSIVE=redis://localhost:6380/0 \
-e QDL_RECOVERY_FAULT_DIR=/fault \
-v "$fault_dir:/fault" \
-v "$PWD:/workspace" -w /workspace rust:1.82-slim \
sh -c 'apt-get update -qq && apt-get install -y --no-install-recommends clang cmake libclang-dev libcurl4-openssl-dev libssl-dev libzstd-dev make pkg-config zlib1g-dev >/dev/null && cargo test -p qdl-stream-gateway --test committed_reads --locked -- --ignored && cargo test -p qdl-projector --locked -- --ignored --test-threads=1'
wait "$controller"
test -s "$fault_dir/receipt.json"
cat "$fault_dir/receipt.json"

- name: Remove isolated Kafka and Redis
if: always()
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6,674 changes: 6,674 additions & 0 deletions DATA_LAYER_UNIFIED_IMPLEMENTATION_PLAN.md

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions docker-compose.v2-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ x-kafka-env: &kafka-env
KAFKA_LOG_RETENTION_MS: 86400000
KAFKA_LOG_SEGMENT_BYTES: 16777216
KAFKA_LOG_DIRS: /var/lib/kafka/data
KAFKA_HEAP_OPTS: -Xms256m -Xmx256m
KAFKA_HEAP_OPTS: -Xms512m -Xmx768m
KAFKA_JVM_PERFORMANCE_OPTS: -XX:+UseG1GC -XX:MaxGCPauseMillis=100

x-kafka: &kafka
logging: *logging
Expand All @@ -72,14 +73,15 @@ x-kafka: &kafka
timeout: 5s
retries: 36
start_period: 20s
# Coordinator recovery exceeded 512 MiB despite a 256 MiB JVM heap. Keep
# native/direct-buffer headroom bounded without enlarging the Java heap.
# Retain native/direct-buffer headroom outside the bounded 768 MiB heap.
# Explicit G1 avoids small-container Serial GC ergonomics.
# R1.25 (2026-09-17): at 768 MiB the kernel memcg OOM-killer killed kafka1's
# JVM twice in 70 s once raw ingest reached ~930 rec/s (anon-rss 391 MB, the
# rest page cache and mmap'd segment indexes, which the memcg charges). Docker
# showed oom_killed=false because PID 1 is the entrypoint script, so the
# crash loop was invisible to every health check. 1536 MiB is 2x the
# measured working set at 2x the morning's load; the heap stays 256 MiB.
# measured working set at 2x the morning's load. The 2026-10-01 heap
# correction uses at most half this limit after observed Full-GC pressure.
mem_limit: 1536m
cpus: 0.75

Expand Down
101 changes: 8 additions & 93 deletions qdl/data_quality/execution_mark_index.py
Original file line number Diff line number Diff line change
@@ -1,93 +1,8 @@
"""Pure validation for the explicit execution MARK/INDEX quiet contract."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Mapping


_VALID_RECENCY_MODES = frozenset({
"STRICT_EVENT_SESSION_LIVE",
"COMPONENT_SESSION_LIVE",
})
_MIN_COMPONENT_CADENCE_MS = 250
_MAX_COMPONENT_CADENCE_MS = 120_000


@dataclass(frozen=True, slots=True)
class QuietExecutionMarkIndexEvidence:
"""Bounded session/component ages derived from immutable response labels."""

session_liveness_ms: int
session_checked_age_ms: int
component_mark_age_ms: int
component_index_age_ms: int
component_mark_quiet_after_ms: int
component_index_quiet_after_ms: int


def validate_quiet_execution_mark_index_evidence(
labels: Mapping[str, str],
*,
at_ns: int,
max_session_liveness_ms: int | None,
) -> QuietExecutionMarkIndexEvidence:
"""Validate the signed live-view proof without rewriting event lineage.

Only an execution MARK/INDEX request already selected by its caller reaches
this helper. It therefore validates the current session and paired-component
cadence that make an old, unchanged source event observable. Missing or
malformed evidence always fails closed.
"""

if max_session_liveness_ms is None or max_session_liveness_ms < 1:
raise ValueError("quiet execution MARK/INDEX contract is incomplete")
if (
labels.get("event_recency_policy") != "OBSERVE"
or labels.get("recency_mode") not in _VALID_RECENCY_MODES
or labels.get("provider_session_state") != "LIVE"
):
raise ValueError("quiet execution MARK/INDEX session evidence is not live")
try:
session_liveness_ms = int(labels["provider_session_liveness_ms"])
session_checked_at_ns = int(labels["provider_session_checked_at_ns"])
component_values = tuple(
(
name,
int(labels[f"component_{name.lower()}_received_at_ns"]),
int(labels[f"component_{name.lower()}_quiet_after_ms"]),
)
for name in ("MARK", "INDEX")
)
except (KeyError, TypeError, ValueError) as error:
raise ValueError("quiet execution MARK/INDEX evidence is malformed") from error
if (
session_liveness_ms < 0
or session_checked_at_ns <= 0
or session_checked_at_ns > at_ns
):
raise ValueError("quiet execution MARK/INDEX session clock is invalid")
session_checked_age_ms = (at_ns - session_checked_at_ns) // 1_000_000
if session_liveness_ms + session_checked_age_ms > max_session_liveness_ms:
raise ValueError("quiet execution MARK/INDEX provider session exceeded its SLA")

component_ages: dict[str, tuple[int, int]] = {}
for name, receipt_ns, cadence_ms in component_values:
if (
receipt_ns <= 0
or not _MIN_COMPONENT_CADENCE_MS <= cadence_ms <= _MAX_COMPONENT_CADENCE_MS
or receipt_ns > at_ns
):
raise ValueError("quiet execution MARK/INDEX component evidence is invalid")
age_ms = (at_ns - receipt_ns) // 1_000_000
if age_ms > cadence_ms:
raise ValueError("quiet execution MARK/INDEX component exceeded its cadence")
component_ages[name] = (age_ms, cadence_ms)
return QuietExecutionMarkIndexEvidence(
session_liveness_ms=session_liveness_ms,
session_checked_age_ms=int(session_checked_age_ms),
component_mark_age_ms=component_ages["MARK"][0],
component_index_age_ms=component_ages["INDEX"][0],
component_mark_quiet_after_ms=component_ages["MARK"][1],
component_index_quiet_after_ms=component_ages["INDEX"][1],
)
"""Compatibility import for the SDK-shared execution MARK/INDEX proof validator."""

from qdl_sdk.execution_quality import (
QuietExecutionMarkIndexEvidence as QuietExecutionMarkIndexEvidence,
)
from qdl_sdk.execution_quality import (
validate_quiet_execution_mark_index_evidence as validate_quiet_execution_mark_index_evidence,
)
34 changes: 22 additions & 12 deletions qdl/query/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,6 @@
QueryBackendError,
SourceMetadata,
)
from qdl.query.service import (
BatchItemResult,
BatchQueryResult,
QueryResult,
QueryServiceError,
ReferenceBatchItemResult,
ReferenceBatchQueryResult,
ReadinessItemResult,
ReadinessResult,
V2QueryService,
WarmupResult,
)
from qdl.warmup.contracts import (
IntervalSourcePolicy,
WarmupSpecification,
Expand Down Expand Up @@ -132,3 +120,25 @@
"WarmupSpecification",
"WarmupTimeRange",
]


# SDK contracts also import this package; service loading must not create a cycle.
_SERVICE_EXPORTS = frozenset({
"BatchItemResult", "BatchQueryResult", "QueryResult", "QueryServiceError",
"ReferenceBatchItemResult", "ReferenceBatchQueryResult", "ReadinessItemResult",
"ReadinessResult", "V2QueryService", "WarmupResult",
})


def __getattr__(name):
if name not in _SERVICE_EXPORTS:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
from importlib import import_module

value = getattr(import_module("qdl.query.service"), name)
globals()[name] = value
return value


def __dir__():
return sorted(set(globals()) | _SERVICE_EXPORTS)
11 changes: 9 additions & 2 deletions qdl/reference/execution_live.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,12 +311,19 @@ def _result_from_response(
):
raise ValueError("execution MARK/INDEX live view identity/provenance mismatch")
fields = self._fields(request, envelope)
component_lineage = paired_mark_index_lineage(envelope)
observed_at_ns = int(envelope.source_event_time_ns)
labels = [
("native_symbol", request.instrument.native_symbol),
("execution_view", "STABLE_STREAM_GATEWAY"),
("freshness_basis", freshness_basis),
("source_event_time_ns", str(observed_at_ns)),
("component_mark_source_event_time_ns", str(component_lineage.mark_source_event_time_ms * 1_000_000)),
("component_index_source_event_time_ns", str(component_lineage.index_source_event_time_ms * 1_000_000)),
("component_mark_received_at_ns", str(component_lineage.mark_received_at_ns)),
("component_index_received_at_ns", str(component_lineage.index_received_at_ns)),
("component_mark_capture_id", component_lineage.mark_capture_id.hex()),
("component_index_capture_id", component_lineage.index_capture_id.hex()),
("provider_confirmation_ns", str(int(envelope.received_at_ns))),
("connection_generation", str(int(envelope.connection_generation))),
("gateway_lease_epoch", str(int(payload["lease_epoch"]))),
Expand All @@ -328,11 +335,11 @@ def _result_from_response(
),
]
if event_recency_policy is StalePolicy.OBSERVE:
labels.extend(self._quiet_labels(
labels = list({**dict(labels), **dict(self._quiet_labels(
envelope,
response,
max_session_liveness_ms=max_session_liveness_ms,
))
))}.items())
observation = ReferenceObservation(
instrument_uid=request.instrument.instrument_uid,
instrument_revision=request.instrument.metadata_revision,
Expand Down
15 changes: 14 additions & 1 deletion qdl/reference/local_mark_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,22 @@ def attach_cache(self, *, backend, relax_to_alpha: bool = True) -> "CacheRefresh
return self

async def read(self, *, instrument_uid: str, **kwargs):
result = await self._read_cache(instrument_uid=instrument_uid, **kwargs)
if (result.record is None and getattr(self._backend, "hot_client", None) is not None
and result.reason in {"STALE", "COMPONENT_STALE", "NOT_READY",
"MARKET_CACHE_NOT_READY", "MARKET_CACHE_UNAVAILABLE", "MARKET_CACHE_BACKUP_UNAVAILABLE"}):
# Exactly one bounded attempt; identity/fence/gap/session refusals
# are not healed by remembering an older view from another replica.
return await self._read_cache(instrument_uid=instrument_uid, prefer_hot=True, **kwargs)
return result

async def _read_cache(self, *, instrument_uid: str, prefer_hot: bool = False, **kwargs):
binding = self._bindings.get(instrument_uid)
if binding is not None:
stored, state = await asyncio.to_thread(self._backend.latest_stored_event, binding)
if prefer_hot:
stored, state = await asyncio.to_thread(self._backend.latest_stored_event, binding, prefer_hot=True)
else:
stored, state = await asyncio.to_thread(self._backend.latest_stored_event, binding)
reason = None if state == "OK" else f"MARKET_CACHE_{state}"
if stored is not None:
try:
Expand Down
Loading
Loading