b758a7c242
---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.
71 lines
2.5 KiB
Python
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)) |