diff --git a/contracts/events/v1/README.md b/contracts/events/v1/README.md index 944cd53..35517a2 100644 --- a/contracts/events/v1/README.md +++ b/contracts/events/v1/README.md @@ -40,7 +40,7 @@ Field ownership is deliberately narrow: - `400 invalid_event`: schema, canonical form, or sensitive/unknown member violation. Terminal until the producer fixes the payload. - `409 idempotency_conflict`: same permanent key with a different payload. Terminal and audited; never overwrite the first Event. - `422 unsupported_schema_version`: unknown major/revision. Terminal for that payload. -- Evidence `pending`, `processing` and `failed` are valid Event states. Bell keeps the Event and resolves/degrades evidence independently. +- Evidence `pending`, `processing`, `success` and `failed` are valid Event states. Bell keeps the Event and resolves/degrades evidence independently. v1 is closed (`additionalProperties=false`). Producers may enable a compatible revision only after all relays and Bell validate it. Any removed/renamed required field, changed meaning, enum narrowing, identity/canonicalization change, or new required member publishes a new major path such as `/v2`. During the compatibility window Bell keeps the previous version endpoint. Rollback disables the new producer version and resumes the last accepted version; it does not delete Event, Receipt, Outbox or audit facts. diff --git a/contracts/evidence/v1/README.md b/contracts/evidence/v1/README.md index be638e8..d157dfd 100644 --- a/contracts/evidence/v1/README.md +++ b/contracts/evidence/v1/README.md @@ -6,7 +6,7 @@ This contract shares metadata about a logical evidence object. It never grants o - `pending`: capture was accepted but no processing started. - `processing`: capture or encoding is in progress. -- `available`: `content_type` and SHA-256 `integrity` are required. Access authorization is negotiated outside this payload by the machine-identity/connector work. +- `success`: capture completed; `content_type` and SHA-256 `integrity` are required. Access authorization is negotiated outside this payload by the machine-identity/connector work. - `failed`: `failure.code` and `retryable` are required. Bell keeps the immutable Event and renders evidence unavailable; it must not reject or close the Alert because evidence failed. - HTTP `404` means an unknown logical reference. `410` means expired evidence. Both degrade evidence only, not the Event. @@ -15,7 +15,7 @@ The payload forbids arbitrary properties, so filesystem paths, camera credential ## Ownership - Brain may request evidence but maps only logical metadata it actually knows. -- Sense is the default evidence owner and advances the status monotonically for a given capture attempt: `pending -> processing -> available|failed`. It must retain the same `evidence_id` while status changes. +- Sense is the default evidence owner and advances the status monotonically for a given capture attempt: `pending -> processing -> success|failed`. It must retain the same `evidence_id` while status changes. - A relay transports the reference unchanged and must not resolve it into a path or URL. - Bell stores the latest evidence metadata separately from its immutable Event. Evidence failure/expiry never changes Alert ack/close state. diff --git a/contracts/evidence/v1/evidence-reference.schema.json b/contracts/evidence/v1/evidence-reference.schema.json index 01c3461..5d47952 100644 --- a/contracts/evidence/v1/evidence-reference.schema.json +++ b/contracts/evidence/v1/evidence-reference.schema.json @@ -18,7 +18,7 @@ "evidence_id": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"}, "owner_id": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"}, "type": {"enum": ["snapshot", "clip"]}, - "status": {"enum": ["pending", "processing", "available", "failed"]}, + "status": {"enum": ["pending", "processing", "success", "failed"]}, "captured_at": {"type": "string", "format": "date-time"}, "status_updated_at": {"type": "string", "format": "date-time"}, "expires_at": {"type": "string", "format": "date-time"}, @@ -45,7 +45,7 @@ }, "allOf": [ { - "if": {"properties": {"status": {"const": "available"}}, "required": ["status"]}, + "if": {"properties": {"status": {"const": "success"}}, "required": ["status"]}, "then": {"required": ["content_type", "integrity"], "not": {"required": ["failure"]}} }, { diff --git a/contracts/evidence/v1/examples/success.json b/contracts/evidence/v1/examples/success.json index a54fb28..a6cdf68 100644 --- a/contracts/evidence/v1/examples/success.json +++ b/contracts/evidence/v1/examples/success.json @@ -3,7 +3,7 @@ "evidence_id": "ev-school-east-0001", "owner_id": "sense-school-a", "type": "snapshot", - "status": "available", + "status": "success", "captured_at": "2026-08-31T00:00:01.125Z", "status_updated_at": "2026-08-31T00:00:02.450Z", "expires_at": "2026-09-07T00:00:01.125Z", diff --git a/contracts/evidence/v1/openapi.json b/contracts/evidence/v1/openapi.json index 8a0dbbc..4f2c084 100644 --- a/contracts/evidence/v1/openapi.json +++ b/contracts/evidence/v1/openapi.json @@ -9,7 +9,7 @@ {"name": "evidence_id", "in": "path", "required": true, "schema": {"type": "string"}} ], "responses": { - "200": {"description": "Current metadata, including pending or failed states", "content": {"application/json": {"schema": {"$ref": "./evidence-reference.schema.json"}}}}, + "200": {"description": "Current metadata, including pending, processing, success or failed states", "content": {"application/json": {"schema": {"$ref": "./evidence-reference.schema.json"}}}}, "404": {"description": "Unknown logical reference", "content": {"application/problem+json": {"schema": {"$ref": "../../events/v1/problem.schema.json"}}}}, "410": {"description": "Evidence expired; event remains valid", "content": {"application/problem+json": {"schema": {"$ref": "../../events/v1/problem.schema.json"}}}} } diff --git a/contracts/tests/evidence-v1/fixtures/sensitive-field.json b/contracts/tests/evidence-v1/fixtures/sensitive-field.json index b8cc67b..93a2a8f 100644 --- a/contracts/tests/evidence-v1/fixtures/sensitive-field.json +++ b/contracts/tests/evidence-v1/fixtures/sensitive-field.json @@ -3,7 +3,7 @@ "evidence_id": "ev-sensitive-0001", "owner_id": "sense-school-a", "type": "snapshot", - "status": "available", + "status": "success", "captured_at": "2026-08-31T00:00:01.125Z", "status_updated_at": "2026-08-31T00:00:02.450Z", "content_type": "image/jpeg", diff --git a/contracts/tests/evidence-v1/test_contract.py b/contracts/tests/evidence-v1/test_contract.py index 1122927..4735b5f 100644 --- a/contracts/tests/evidence-v1/test_contract.py +++ b/contracts/tests/evidence-v1/test_contract.py @@ -27,9 +27,16 @@ class EvidenceV1ContractTests(unittest.TestCase): self.assertEqual([], self.errors_for(payload)) def test_state_specific_metadata_is_enforced(self) -> None: - available = load_json(CONTRACTS / "evidence" / "v1" / "examples" / "success.json") - del available["integrity"] - self.assertTrue(self.errors_for(available)) + success = load_json(CONTRACTS / "evidence" / "v1" / "examples" / "success.json") + for required in ("content_type", "integrity"): + with self.subTest(success_requires=required): + candidate = copy.deepcopy(success) + del candidate[required] + self.assertTrue(self.errors_for(candidate)) + + legacy_available = copy.deepcopy(success) + legacy_available["status"] = "available" + self.assertTrue(self.errors_for(legacy_available)) failed = load_json(CONTRACTS / "evidence" / "v1" / "examples" / "failed.json") del failed["failure"]