Files
acdl/core/outbox_writer.py
T
Jon Chery b758a7c242 refactor(P21): rename acdl_platform/ -> core/ (REQ-53)
---ci---
project: acdl
phase: 21
milestone: v1.6
status: execute
---/ci---

Rename the acdl_platform/ package to core/ across the directory, all
imports in tests/scripts/pipelines/workflows, and doc references. The
package is imported as core.confidence_signal / core.contract_resolver /
core.outbox_writer. The deploy workflow's platform-repo checkout dir is
renamed acdl-platform/ -> platform/ (workspace path, not the python
package). Both .gitea + .github workflows stay byte-identical.

Note: the original target name 'platform/' shadows Python's stdlib
platform module (pytest's import uuid -> platform.system() fails when
the repo root is on sys.path, which every test does). 'core/' avoids
the clash while honoring the intent (drop the verbose acdl_platform).

Tests: 154 pass. run_ci.sh green.
2026-07-22 18:21:12 +00:00

71 lines
2.5 KiB
Python

"""ACDL Outbox Writer — write an evidence event to the DynamoDB outbox.
ARCHITECTURE.md §9: DynamoDB outbox, RPO=0 (synchronous write before
ack). The event is hash-chained (SHA-256 over canonical JSON); the first
event has prev_event_hash="GENESIS". D-P10-3: the spike writes ONE
CONFIDENCE_COMPUTED event.
The outbox table (Phase 08): acdl-outbox, PAY_PER_REQUEST, PK contractId,
SK eventType#eventTs, TTL expire_at = now + 365d (D-044).
CLI: outbox_writer.py <event.json> (uses AWS creds from env)
"""
import datetime
import hashlib
import json
import os
import sys
import boto3
OUTBOX_TABLE = "acdl-outbox"
REGION = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
def _canonical_hash(event):
"""SHA-256 over canonical JSON (sort_keys, compact separators)."""
canonical = json.dumps(event, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
def write_event(event, outbox_table=OUTBOX_TABLE, region=REGION):
"""Write an evidence event to the DynamoDB outbox. Returns the item dict."""
contract_id = event["contractId"]
event_type = event.get("eventType", "CONFIDENCE_COMPUTED")
event_ts = event.get("ts") or datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
sk = f"{event_type}#{event_ts}"
# Chain: first event = GENESIS (D-P10-3 spike writes one event).
prev_hash = event.get("prev_event_hash", "GENESIS")
event_hash = _canonical_hash(event)
item = {
"contractId": {"S": contract_id},
"eventType#eventTs": {"S": sk},
"payload": {"S": json.dumps(event, sort_keys=True)},
"prev_event_hash": {"S": prev_hash},
"hash": {"S": event_hash},
"environment": {"S": str(event.get("environment", ""))},
"stack": {"S": str(event.get("stack", ""))},
"score": {"N": str(event.get("score", 0))},
"band": {"S": str(event.get("band", ""))},
"expire_at": {"N": str(int((datetime.datetime.now(datetime.timezone.utc) +
datetime.timedelta(days=365)).timestamp()))},
}
session = boto3.Session(region_name=region)
dyn = session.client("dynamodb")
dyn.put_item(TableName=outbox_table, Item=item)
return item
if __name__ == "__main__":
if len(sys.argv) != 2:
print("usage: outbox_writer.py <event.json>", file=sys.stderr)
sys.exit(2)
with open(sys.argv[1], "r") as fh:
event = json.load(fh)
item = write_event(event)
print(json.dumps({k: list(v.values())[0] for k, v in item.items()}, indent=2))