Kafka: close a window before you sign it.
A topic is an open-ended log, not one finite artifact. Makoto evidence needs a bounded subject: a partition, an offset range, and the exact bytes in it. This producer closes a window and signs it; the consumer rebuilds the same bytes from the broker and commits offsets only after the verifier allows them.
python window_producer.py windows
python window_producer.py windows # a second window with identical records
python window_consumer.py windows/orders-p0-0-2/notice.json{"window": "orders-p0-0-2", "notice": "/private/tmp/claude-501/-Users-daaronch--treehouse-usemakoto-dev-e5b6d4-3-usemakoto-dev/7a8e8b40-d349-450c-be40-c39e2d154d0f/scratchpad/integ/kafka/windows/orders-p0-0-2/notice.json"}
COMMITTED window 0-2 (2 records)Implementation boundary. This is a pattern built from the platform’s own APIs and the pinned reference CLI. It is not a shipped adapter, plugin, or package. Executed against an Apache Kafka 4.1.0 broker in a local container with confluent-kafka 2.15.1: one allowed window and two refusals.
Where Makoto sits
The signed artifact is a deterministic rendering of the window: message values in offset order, one per line. Producer and consumer build it with the same function, so the consumer can hash what it actually read.
- Produce and flushIdempotent producer; the window is closed only when every delivery report has returned.
- Sign the windowAn origin statement over the window bytes, with
partition=0;offsets=0-2as source metadata. - AnnounceA notice carries the window, the bundle location, and the anchors. In production it travels through a channel the receiver trusts independently.
- Consume the windowAuto-commit is off. The consumer reads exactly the announced offsets and hashes what it got.
- Verify, then check the windowThe verifier compares the consumer’s digest with the signed artifact; the consumer then checks the signed offsets against the offsets it read. Only then are offsets committed.
1. Install
Every platform on this site uses the same two files from the integration index: makoto_boundary.py, a standard-library helper that signs, hands off, and verifies through the pinned CLI, and makoto_bootstrap.py, which creates one key per claim role and the receiver’s trust policy. Put both next to the code below.
git clone https://github.com/makoto-project/makoto.git /opt/makoto
git -C /opt/makoto checkout 276008eeec0f10d5322c4c627b8c103222fee841
(cd /opt/makoto && uv sync --locked --dev)
export MAKOTO_CORE=/opt/makoto
uv pip install "confluent-kafka==2.15.1"uv run makoto_bootstrap.py /srv/makoto \
--source-kind urn:example:source:kafka-offset-window \
--source-uri kafka://localhost:9092/orders \
--operation-type urn:example:operation:none
export MAKOTO_KEYS=/srv/makoto/keys MAKOTO_POLICY=/srv/makoto/policy.json2. The producer
Save this as window_producer.py.
"""Produce a batch, close its offset window, sign the window, and announce it.
python window_producer.py /srv/makoto/windows
"""
import json
import sys
import uuid
from pathlib import Path
from confluent_kafka import Producer
import makoto_boundary as mb
BROKER = "localhost:9092"
TOPIC, PARTITION = "orders", 0
RECIPIENT = "example:analytics-team"
def window_bytes(values: list[bytes]) -> bytes:
"""The window artifact: message values in offset order, one per line.
Producer and consumer must build it identically."""
return b"".join(value + b"\n" for value in values)
def produce(root: Path) -> None:
producer = Producer({"bootstrap.servers": BROKER, "enable.idempotence": True})
delivered: list[tuple[int, bytes]] = []
def on_delivery(err, msg):
if err is not None:
raise RuntimeError(err)
delivered.append((msg.offset(), msg.value()))
for order in ({"order_id": 1001, "region": "us", "total": 42.0},
{"order_id": 1002, "region": "gb", "total": 17.5}):
producer.produce(TOPIC, json.dumps(order, sort_keys=True).encode(), partition=PARTITION,
on_delivery=on_delivery)
if producer.flush(10) != 0:
raise RuntimeError("messages still in flight; the window is not closed")
delivered.sort()
start, end = delivered[0][0], delivered[-1][0] + 1 # [start, end)
window = f"{TOPIC}-p{PARTITION}-{start}-{end}"
run = root / window
artifact = run / f"{window}.ndjson"
run.mkdir(parents=True, exist_ok=True)
artifact.write_bytes(window_bytes([value for _, value in delivered]))
event = lambda name: f"urn:uuid:{uuid.uuid5(uuid.NAMESPACE_URL, f'{window}/{name}')}"
head = mb.sign_origin(
artifact, run / "attestations" / "01-window.dsse.json",
source_kind="urn:example:source:kafka-offset-window",
source_uri=f"kafka://{BROKER}/{TOPIC}",
source_metadata={"version": f"partition={PARTITION};offsets={start}-{end}"},
event_id=event("window"),
)
anchors = mb.create_handoff(
head, artifact, run / "attestations", run / "bundle",
recipient=RECIPIENT, bundle_id=event("bundle"), media_type="application/x-ndjson",
)
notice = {"topic": TOPIC, "partition": PARTITION, "start": start, "end": end,
"bundle": str(run / "bundle"), "anchors": anchors}
(run / "notice.json").write_text(json.dumps(notice))
print(json.dumps({"window": window, "notice": str(run / "notice.json")}))
if __name__ == "__main__":
produce(Path(sys.argv[1]).resolve())3. The consumer
Save this as window_consumer.py.
"""Read a signed window from the topic and commit offsets only if Makoto allows it.
python window_consumer.py /srv/makoto/windows/orders-p0-0-2/notice.json
"""
import copy
import hashlib
import json
import sys
from pathlib import Path
from confluent_kafka import Consumer, TopicPartition
import makoto_boundary as mb
from window_producer import BROKER, RECIPIENT, window_bytes
GROUP = "finance-orders"
def consume(notice_path: Path) -> int:
notice = json.loads(notice_path.read_text())
consumer = Consumer({
"bootstrap.servers": BROKER,
"group.id": GROUP,
"enable.auto.commit": False, # offsets move only after a verdict
"auto.offset.reset": "earliest",
})
partition = TopicPartition(notice["topic"], notice["partition"], notice["start"])
consumer.assign([partition])
values: list[bytes] = []
while len(values) < notice["end"] - notice["start"]:
msg = consumer.poll(10)
if msg is None:
raise RuntimeError("window is incomplete on the broker")
if msg.error():
raise RuntimeError(msg.error())
values.append(msg.value())
# The expected artifact is what THIS consumer read, not what the bundle claims.
anchors = copy.deepcopy(notice["anchors"])
anchors["artifact"]["digest"]["sha256"] = hashlib.sha256(window_bytes(values)).hexdigest()
try:
mb.verify(notice["bundle"], anchors, recipient=RECIPIENT,
report_path=notice_path.parent / "verification.json")
except mb.MakotoDenied as denied:
consumer.close() # nothing committed: the window will be read again, or quarantined
print(f"REFUSED window {notice['start']}-{notice['end']}: {denied}", file=sys.stderr)
return 1
# Makoto proved these bytes are the signed bytes. It did not prove they came from
# these offsets: identical records elsewhere in the log hash the same. Check the
# signed window against the window this consumer actually read.
head = notice["anchors"]["head"].removeprefix("sha256:")
statement = json.loads(mb.payload(Path(notice["bundle"]) / "attestations" / f"{head}.dsse.json"))
signed_window = statement["predicate"]["source"]["version"]
read_window = f"partition={notice['partition']};offsets={notice['start']}-{notice['end']}"
if signed_window != read_window:
consumer.close()
print(f"REFUSED window {notice['start']}-{notice['end']}: evidence signs {signed_window}", file=sys.stderr)
return 1
# Hand the verified records to the application here, then commit past the window.
consumer.commit(offsets=[TopicPartition(notice["topic"], notice["partition"], notice["end"])],
asynchronous=False)
consumer.close()
print(f"COMMITTED window {notice['start']}-{notice['end']} ({len(values)} records)")
return 0
if __name__ == "__main__":
sys.exit(consume(Path(sys.argv[1]).resolve()))4. Run it
python window_producer.py windows
python window_producer.py windows # a second window with identical records
python window_consumer.py windows/orders-p0-0-2/notice.json{"window": "orders-p0-0-2", "notice": "/private/tmp/claude-501/-Users-daaronch--treehouse-usemakoto-dev-e5b6d4-3-usemakoto-dev/7a8e8b40-d349-450c-be40-c39e2d154d0f/scratchpad/integ/kafka/windows/orders-p0-0-2/notice.json"}
COMMITTED window 0-2 (2 records)5. What a gate failure looks like
Point the consumer at offsets the evidence does not cover. Shifted by one, the records come back in a different order, the bytes differ, and the verifier refuses:
jq '.start += 1 | .end += 1' notice.json > shift1.json
python window_consumer.py shift1.json
REFUSED window 1-3: Makoto deny (E_EXPECTED_ARTIFACT): step 11 E_EXPECTED_ARTIFACT: expected artifact set does not matchShifted by two, the consumer reads the second window. Its records are byte-for-byte identical to the first, so the verifier allows the bytes. The window check refuses it:
jq '.start += 2 | .end += 2' notice.json > shift2.json
python window_consumer.py shift2.json
REFUSED window 2-4: evidence signs partition=0;offsets=0-2In both cases no offset is committed, so the consumer group has not advanced past data it could not verify.
Bytes are not positions
The second refusal is the lesson of this page. Makoto authenticates exact bytes. Identical records at different offsets hash identically, so an artifact digest cannot tell a replay of the same values from the window that was signed. Whenever position is part of meaning, as it is for offsets, time windows, or partitions, put the position in signed metadata and compare it yourself after the verifier allows the bytes.
Other attachment points on this platform
- Source window
- An authorized finalizer closes declared partition and offset ranges, computes or resolves the exact window artifact, and creates an origin statement.
- Stream transform
- After an output window commits, append a transformation statement binding exact input window and predecessor digests.
- Schema Registry
- Carry schema IDs and serialization details as context or profile references. A schema ID does not become a Makoto predicate automatically.
- Cross-team topic
- Sign a handoff that commits to exact windows, heads, profiles, recipient, and final artifact representation.
What the evidence can say
It can bind one closed stream window to authorized claims and predecessors. It cannot prove device firmware, infer trust from a producer ID, or turn ongoing CDC into a complete history without an explicit finalization boundary.
Troubleshooting
Connect to ipv6#[::1]:9092 failed: Connection refused- librdkafka tried IPv6 first because the broker advertises
localhostand the container published only an IPv4 port. It fell back to IPv4 and every run completed; the message is noise, not a failure. RuntimeError: window is incomplete on the broker- Retention, compaction, or a truncated partition removed part of the window. The window can no longer be verified; quarantine it rather than committing past it.
- Offsets were committed for a refused window
- Auto-commit must be off. Commit explicitly, and only after both the verifier and the window check pass.
Close windows on a real cluster.
Transactions, compacted topics, and Schema Registry serialization all affect what the window bytes are. Tell us how your producers finalize.