fix: preserve reverse migration record hashes
This commit is contained in:
parent
b649ea7168
commit
9adeb013cd
2 changed files with 30 additions and 2 deletions
|
|
@ -413,7 +413,7 @@ async def export_bundle(store: PostgresPortStore, *, source_revision: str | None
|
|||
"apiConsumers": [_body(row, created=True) for row in consumers],
|
||||
"apiKeys": [{"id": row["id"], "apiConsumerId": row["api_consumer_id"], "keyPrefix": row["key_prefix"], "keyHash": row["key_hash"], "scopes": row["scopes"], "status": row["status"], "createdAt": _timestamp(row["created_at"])} for row in keys],
|
||||
"widgets": [_body(row, created=True) for row in widgets],
|
||||
"interactionEvents": [{**dict((row["payload"] or {}).get("legacy") or {}), "id": row["id"], "widgetId": (row["subject_refs"] or {}).get("widget"), "eventType": row["event_type"], "occurredAt": _timestamp(row["occurred_at"]), "recordedAt": _timestamp(row["recorded_at"]), "correlationId": row["correlation_id"]} for row in events],
|
||||
"interactionEvents": [_export_interaction(row) for row in events],
|
||||
"migrationRuns": [{"id": row["id"], "source": row["source"], "schemaVersion": row["schema_version"], "bundleSha256": row["bundle_sha256"], "dryRun": row["dry_run"], "status": row["status"], "counts": row["counts"], "diagnostics": row["diagnostics"], "createdAt": _timestamp(row["created_at"])} for row in runs],
|
||||
}
|
||||
high_water = max((_timestamp(row["recorded_at"]) for row in events), default=None)
|
||||
|
|
@ -424,8 +424,32 @@ async def export_bundle(store: PostgresPortStore, *, source_revision: str | None
|
|||
return bundle
|
||||
|
||||
|
||||
def _export_interaction(row: Any) -> dict[str, Any]:
|
||||
legacy = dict((row["payload"] or {}).get("legacy") or {})
|
||||
if legacy:
|
||||
# Imported records retain their exact source representation so reverse
|
||||
# export is a lossless rollback artifact, including optional timestamp
|
||||
# and correlation fields that were absent in the source.
|
||||
return legacy
|
||||
return {
|
||||
"id": row["id"],
|
||||
"widgetId": (row["subject_refs"] or {}).get("widget"),
|
||||
"eventType": row["event_type"],
|
||||
"occurredAt": _timestamp(row["occurred_at"]),
|
||||
"recordedAt": _timestamp(row["recorded_at"]),
|
||||
"correlationId": row["correlation_id"],
|
||||
"metadata": row["payload"],
|
||||
}
|
||||
|
||||
|
||||
def _body(row: Any, *, created: bool = False, updated: bool = False) -> dict[str, Any]:
|
||||
value = dict(row["body"] or {})
|
||||
if value.get("id") == row["id"]:
|
||||
# Import stores the complete source record as its body. Returning that
|
||||
# record verbatim keeps reverse export hash- and field-exact; records
|
||||
# created natively lack the generated database identity and are enriched
|
||||
# from their columns below.
|
||||
return value
|
||||
if created:
|
||||
value["createdAt"] = _timestamp(row["created_at"])
|
||||
if updated:
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ def bundle() -> dict:
|
|||
"hubs": [{"id": "hub-1", "slug": "ops-hub", "name": "Ops Hub", "createdAt": "2026-08-20T10:00:00Z", "updatedAt": "2026-08-20T11:00:00Z"}],
|
||||
"hubCapabilityManifests": [{"id": "manifest-1", "hubId": "hub-1", "manifestVersion": "1.0", "status": "active"}],
|
||||
"apiConsumers": [{"id": "consumer-1", "slug": "ops-hub", "name": "ops-hub", "hubCapabilityManifestId": "manifest-1"}],
|
||||
"apiKeys": [{"id": "key-1", "apiConsumerId": "consumer-1", "keyPrefix": "ch_prefix", "keyHash": "a" * 64, "scopes": "framework:read"}],
|
||||
"apiKeys": [{"id": "key-1", "apiConsumerId": "consumer-1", "keyPrefix": "ch_prefix", "keyHash": "a" * 64, "scopes": "framework:read", "status": "active", "createdAt": "2026-08-21T10:00:00Z"}],
|
||||
"widgets": [{"id": "widget-1", "hubId": "hub-1", "name": "Readiness", "widgetType": "ops-readiness-gate"}],
|
||||
"interactionEvents": [{"id": "event-1", "widgetId": "widget-1", "eventType": "ops-endpoint-verified", "metadata": {"status": 401}, "createdAt": "2026-08-21T11:00:00Z"}],
|
||||
"migrationRuns": [{"id": "run-1", "source": "inter-hub", "schemaVersion": "core-hub.migration.v1", "bundleSha256": "b" * 64, "dryRun": False, "status": "imported", "counts": {}, "diagnostics": {}, "createdAt": "2026-08-19T09:00:00Z"}],
|
||||
|
|
@ -93,6 +93,10 @@ def test_import_is_idempotent_and_exports_all_seven_collections(tmp_path):
|
|||
assert exported["records"]["apiKeys"][0]["keyHash"] == "a" * 64
|
||||
assert "fullKey" not in json.dumps(exported)
|
||||
assert validate_bundle(exported)["ok"] is True
|
||||
assert (
|
||||
validate_bundle(exported)["contentHashes"]
|
||||
== validate_bundle(bundle())["contentHashes"]
|
||||
)
|
||||
|
||||
|
||||
def test_dry_run_does_not_record_import(tmp_path):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue