From 869adf0e294c08b0410f4ff0fd405931c7407e2d Mon Sep 17 00:00:00 2001 From: ExoHayvan <18trevor3695@gmail.com> Date: Wed, 15 Jul 2026 10:08:00 -0400 Subject: [PATCH] chore: complete phase-1 step-141 --- docs/local-audit-event-schema.md | 8 ++- src/aethermesh_core/local_audit_event.py | 58 ++++++++++++++++++ src/aethermesh_core/local_startup.py | 76 ++++++++++++++++++++++++ tests/test_local_audit_event.py | 2 +- tests/test_local_shutdown.py | 25 ++++++-- tests/test_local_startup.py | 72 ++++++++++++++++++++++ 6 files changed, 235 insertions(+), 6 deletions(-) diff --git a/docs/local-audit-event-schema.md b/docs/local-audit-event-schema.md index 2157aa9..68cffd9 100644 --- a/docs/local-audit-event-schema.md +++ b/docs/local-audit-event-schema.md @@ -18,7 +18,7 @@ Store one event per UTF-8 newline-delimited JSON (JSONL) line, normally under th | `creator_node_id` | non-empty string or `null` | Original creator node when known; `null` explicitly records that it is not known. | | `local_run_id` | non-empty string | Local invocation/run that produced the event. | -Supported event types are `node_initialized`, `manifest_created`, `work_submitted`, `validation_attempted`, `validation_result`, `lineage_linked`, and `contribution_record_updated`. +Supported event types are `node_initialized`, `manifest_created`, `work_submitted`, `validation_attempted`, `validation_result`, `lineage_linked`, `contribution_record_updated`, `capability_advertised`, and `node.shutdown`. ## Optional references @@ -37,6 +37,12 @@ Omit optional fields when they do not apply. Their absence is valid and has no e Hashes and signatures are optional. This format neither creates nor requires them unless existing local validation already produces them. +## Capability advertisement events + +A `capability_advertised` event records one validated local startup-manifest claim after startup artifacts have been created. It requires `capability_advertisement_action` (`created`, `refreshed`, or `replaced`), `node_id`, `capability_id`, `manifest_ref`, `manifest_digest`, `advertisement_payload_digest`, `validation_status`, `validation_receipt_refs`, `lineage_refs`, and a string-only `contribution_attribution` map. The event stores digests and safe relative references rather than the full advertisement payload, so local runtime details are not copied into the audit log. + +Startup appends one event per capability advertisement. Normal repeat startup is `refreshed`; explicit creator-identity reset is `replaced`; creating or upgrading a manifest advertisement is `created`. Rejected manifests fail before this event is appended. + ## Examples A minimal initialization event intentionally has no optional references: diff --git a/src/aethermesh_core/local_audit_event.py b/src/aethermesh_core/local_audit_event.py index d24d39b..cd0eb64 100644 --- a/src/aethermesh_core/local_audit_event.py +++ b/src/aethermesh_core/local_audit_event.py @@ -18,6 +18,7 @@ "validation_result", "lineage_linked", "contribution_record_updated", + "capability_advertised", "node.shutdown", } ) @@ -51,6 +52,13 @@ "validation_receipt_ref", "lineage_refs", "contribution_attribution_refs", + "capability_advertisement_action", + "node_id", + "capability_id", + "manifest_digest", + "advertisement_payload_digest", + "validation_receipt_refs", + "contribution_attribution", } ) @@ -124,6 +132,8 @@ def validate_local_audit_event(event: Mapping[str, Any]) -> dict[str, Any]: _require_text_mapping(document[field], f"local audit event.{field}") if document["event_type"] == "node.shutdown": _validate_shutdown_event(document) + if document["event_type"] == "capability_advertised": + _validate_capability_advertisement_event(document) return document @@ -214,3 +224,51 @@ def _validate_shutdown_event(document: Mapping[str, Any]) -> None: f"node.shutdown event is missing required context: {', '.join(missing)}" ) _require_text(document["creator_node_id"], "node.shutdown.creator_node_id") + + +def _validate_capability_advertisement_event(document: Mapping[str, Any]) -> None: + """Require compact provenance for one local capability advertisement.""" + + required_fields = ( + "capability_advertisement_action", + "node_id", + "capability_id", + "manifest_ref", + "manifest_digest", + "advertisement_payload_digest", + "validation_status", + "validation_receipt_refs", + "lineage_refs", + "contribution_attribution", + ) + missing = [field for field in required_fields if field not in document] + if missing: + raise LocalAuditEventError( + "capability_advertised event is missing required context: " + f"{', '.join(missing)}" + ) + if document["capability_advertisement_action"] not in { + "created", + "refreshed", + "replaced", + }: + raise LocalAuditEventError( + "capability_advertised action must be created, refreshed, or replaced" + ) + for field in ( + "node_id", + "capability_id", + "manifest_digest", + "advertisement_payload_digest", + "validation_status", + ): + _require_text(document[field], f"capability_advertised.{field}") + _require_text(document["creator_node_id"], "capability_advertised.creator_node_id") + _require_local_paths( + document["validation_receipt_refs"], + "capability_advertised.validation_receipt_refs", + ) + _require_text_mapping( + document["contribution_attribution"], + "capability_advertised.contribution_attribution", + ) diff --git a/src/aethermesh_core/local_startup.py b/src/aethermesh_core/local_startup.py index 6d3b89f..da8fc5d 100644 --- a/src/aethermesh_core/local_startup.py +++ b/src/aethermesh_core/local_startup.py @@ -16,6 +16,7 @@ reset_identity, ) from aethermesh_core.json_io import atomic_create_json, atomic_write_json +from aethermesh_core.local_audit_event import append_local_audit_event from aethermesh_core.local_runtime_config import ( DEFAULT_RUNTIME_PATHS, LOCAL_RUNTIME_CONFIG_PATH, @@ -256,7 +257,9 @@ def start_local_node( receipt_ref = _next_artifact_ref( root, config, "validation_receipts", "startup-validation", timestamp ) + advertisement_action = "refreshed" if reset_creator_identity and identity_existed: + advertisement_action = "replaced" _write_default_manifest( manifest_path, node_id=identity.node_id, @@ -268,6 +271,7 @@ def start_local_node( replace_existing=True, ) elif not manifest_path.exists(): + advertisement_action = "created" _write_default_manifest( manifest_path, node_id=identity.node_id, @@ -280,6 +284,7 @@ def start_local_node( ) manifest = _load_manifest(manifest_path) if "capability_advertisements" not in manifest: + advertisement_action = "created" manifest["capability_advertisements"] = _default_capability_advertisements( creator_node_id=creator_node_id, manifest_ref=_relative_ref(root, manifest_path), @@ -360,6 +365,18 @@ def start_local_node( ) ) handle.write("\n") + _append_capability_advertisement_audit_events( + root, + advertisements=manifest["capability_advertisements"], + action=advertisement_action, + timestamp=timestamp.replace("+00:00", "Z"), + node_id=identity.node_id, + creator_node_id=creator_node_id, + manifest_ref=_relative_ref(root, manifest_path), + manifest_hash=manifest_hash, + validation_receipt_ref=receipt_ref, + lineage_ref=lineage_ref, + ) except OSError as exc: raise LocalStartupError( f"could not record startup lineage or log: {exc}" @@ -698,6 +715,65 @@ def _document_hash(document: dict[str, object]) -> str: return "sha256:" + sha256(encoded).hexdigest() +def _append_capability_advertisement_audit_events( + root: Path, + *, + advertisements: object, + action: str, + timestamp: str, + node_id: str, + creator_node_id: str, + manifest_ref: str, + manifest_hash: str, + validation_receipt_ref: str, + lineage_ref: str, +) -> None: + """Append one digest-only audit event for each validated local claim.""" + + for advertisement in cast(list[dict[str, object]], advertisements): + validation = cast(dict[str, object], advertisement["validation"]) + attribution = cast(dict[str, object], advertisement["contribution_attribution"]) + required_receipt_ref = cast(str, validation["required_receipt_ref"]) + work_record_ref = cast(str, attribution["work_record_ref"]) + capability_id = cast(str, advertisement["capability_id"]) + append_local_audit_event( + root / "logs" / "local-audit-events.jsonl", + { + "schema_version": 1, + "event_id": ( + f"capability-advertisement:{action}:{validation_receipt_ref}:" + f"{capability_id}" + ), + "timestamp": timestamp, + "event_type": "capability_advertised", + "actor_node_id": node_id, + "creator_node_id": creator_node_id, + "local_run_id": f"local-startup:{validation_receipt_ref}", + "capability_advertisement_action": action, + "node_id": node_id, + "capability_id": capability_id, + "manifest_ref": manifest_ref, + "manifest_digest": manifest_hash, + "advertisement_payload_digest": _document_hash(advertisement), + "validation_status": "passed", + "validation_receipt_refs": [required_receipt_ref], + "lineage_refs": [lineage_ref], + "contribution_attribution": { + "creator_node_id": creator_node_id, + "attribution_node_id": node_id, + "work_record_ref": work_record_ref, + }, + "related_file_paths": [ + manifest_ref, + required_receipt_ref, + validation_receipt_ref, + lineage_ref, + work_record_ref, + ], + }, + ) + + def _next_artifact_ref( root: Path, config: LocalRuntimeConfig, diff --git a/tests/test_local_audit_event.py b/tests/test_local_audit_event.py index 6b394e1..1e8f34d 100644 --- a/tests/test_local_audit_event.py +++ b/tests/test_local_audit_event.py @@ -16,7 +16,7 @@ def test_validates_required_fields_and_allows_missing_optional_fields(self) -> N event = _minimal_event() self.assertEqual(validate_local_audit_event(event), event) - self.assertEqual(len(AUDIT_EVENT_TYPES), 8) + self.assertEqual(len(AUDIT_EVENT_TYPES), 9) self.assertIsNone(event["creator_node_id"]) def test_appends_parseable_jsonl_events_with_local_references(self) -> None: diff --git a/tests/test_local_shutdown.py b/tests/test_local_shutdown.py index 1271fe7..964e15c 100644 --- a/tests/test_local_shutdown.py +++ b/tests/test_local_shutdown.py @@ -9,6 +9,7 @@ from aethermesh_core.cli import main from aethermesh_core.local_shutdown import ( LocalShutdownError, + _audit_event_exists, _latest_startup_run_id, _latest_validation_context, shutdown_local_node, @@ -135,6 +136,7 @@ def test_shutdown_appends_auditable_context_for_each_startup_instance(self) -> N events = [ json.loads(line) for line in audit_path.read_text(encoding="utf-8").splitlines() + if json.loads(line).get("event_type") == "node.shutdown" ] self.assertEqual(len(events), 2) @@ -165,8 +167,12 @@ def test_shutdown_records_unavailable_optional_validation_context_honestly( start = start_local_node(root).to_dict() (root / str(start["validation_receipt_path"])).unlink() shutdown_local_node(root) - event = json.loads( - (root / "logs" / "local-audit-events.jsonl").read_text(encoding="utf-8") + event = next( + json.loads(line) + for line in (root / "logs" / "local-audit-events.jsonl") + .read_text(encoding="utf-8") + .splitlines() + if json.loads(line).get("event_type") == "node.shutdown" ) self.assertIsNone(event["validation_receipt_ref"]) @@ -185,8 +191,12 @@ def test_shutdown_uses_latest_receipt_without_changing_startup_instance( json.dumps({"validation_status": "pass"}), encoding="utf-8" ) shutdown_local_node(root) - event = json.loads( - (root / "logs" / "local-audit-events.jsonl").read_text(encoding="utf-8") + event = next( + json.loads(line) + for line in (root / "logs" / "local-audit-events.jsonl") + .read_text(encoding="utf-8") + .splitlines() + if json.loads(line).get("event_type") == "node.shutdown" ) self.assertEqual( @@ -214,6 +224,7 @@ def test_missing_receipts_still_produce_distinct_shutdown_events(self) -> None: for line in (root / "logs" / "local-audit-events.jsonl") .read_text(encoding="utf-8") .splitlines() + if json.loads(line).get("event_type") == "node.shutdown" ] self.assertEqual(len(events), 2) @@ -249,6 +260,12 @@ def test_latest_validation_context_handles_unreadable_or_malformed_receipts( non_object_context, ("receipts/non-object.json", "unavailable") ) + def test_audit_event_lookup_returns_false_for_an_unreadable_log(self) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + self.assertFalse( + _audit_event_exists(Path(temp_dir) / "missing.jsonl", "event-001") + ) + def test_latest_startup_run_id_handles_missing_or_malformed_logs(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: path = Path(temp_dir) / "startup.log" diff --git a/tests/test_local_startup.py b/tests/test_local_startup.py index 6b8c4ad..d8d8cd0 100644 --- a/tests/test_local_startup.py +++ b/tests/test_local_startup.py @@ -155,6 +155,78 @@ def test_clean_startup_creates_auditable_artifacts_and_preserves_identity( first["validation_receipt_path"], ) self.assertEqual(advertisement["network_mode"], "local-only-no-p2p") + advertisement_audit_events = [ + json.loads(line) + for line in (runtime / "logs" / "local-audit-events.jsonl") + .read_text(encoding="utf-8") + .splitlines() + ] + self.assertEqual(len(advertisement_audit_events), 2) + for event, startup, action in zip( + advertisement_audit_events, + (first, second), + ("created", "refreshed"), + strict=True, + ): + self.assertEqual(event["event_type"], "capability_advertised") + self.assertEqual(event["capability_advertisement_action"], action) + self.assertEqual(event["creator_node_id"], startup["creator_node_id"]) + self.assertEqual(event["node_id"], startup["node_id"]) + self.assertEqual(event["capability_id"], advertisement["capability_id"]) + self.assertEqual(event["manifest_digest"], startup["manifest_hash"]) + self.assertEqual( + event["validation_receipt_refs"], + [advertisement["validation"]["required_receipt_ref"]], + ) + self.assertEqual(event["lineage_refs"], [startup["lineage_path"]]) + self.assertEqual( + event["contribution_attribution"]["creator_node_id"], + startup["creator_node_id"], + ) + self.assertNotIn("description", event) + self.assertRegex( + event["advertisement_payload_digest"], r"^sha256:[0-9a-f]{64}$" + ) + + def test_reset_startup_appends_a_replaced_capability_advertisement_event( + self, + ) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + runtime = Path(temp_dir) + start_local_node(runtime) + reset = start_local_node(runtime, reset_creator_identity=True) + + events = [ + json.loads(line) + for line in (runtime / "logs" / "local-audit-events.jsonl") + .read_text(encoding="utf-8") + .splitlines() + ] + + self.assertEqual( + [event["capability_advertisement_action"] for event in events], + ["created", "replaced"], + ) + self.assertEqual(events[-1]["creator_node_id"], reset.creator_node_id) + + def test_rejected_manifest_does_not_append_capability_advertisement_event( + self, + ) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + runtime = Path(temp_dir) + start_local_node(runtime) + audit_path = runtime / "logs" / "local-audit-events.jsonl" + before = audit_path.read_text(encoding="utf-8") + (runtime / "manifests" / "local-node-manifest.json").write_text( + "{not json", encoding="utf-8" + ) + + with self.assertRaisesRegex( + LocalStartupError, "startup manifest JSON is malformed" + ): + start_local_node(runtime) + + self.assertEqual(audit_path.read_text(encoding="utf-8"), before) def test_existing_identity_missing_manifest_fails_closed(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: