From caf866d66eefad7bca5f514693f04de17733b9cb Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 2 Nov 2026 01:32:57 +0100 Subject: [PATCH] Prepare definitive SMTP rejection proof and builder mail handoff Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a198ed-70cc-7362-a569-d5dbef39b86a --- ...rm-mail-builder-close-review-2026-11-02.md | 66 ++++++ ...ered-authentication-return-2026-11-02.json | 51 +++++ ...mail-native-expiry-fixture-2026-11-02.json | 27 +++ scripts/mail_return_native.py | 18 +- scripts/mail_return_operation.py | 208 ++++++++++++++++-- tests/test_smtp_rejection_operation.py | 197 +++++++++++++++++ ...P-0015-communication-runtime-acceptance.md | 28 ++- ...latform-mail-operational-follow-through.md | 81 +++++++ 8 files changed, 645 insertions(+), 31 deletions(-) create mode 100644 docs/release/platform-mail-builder-close-review-2026-11-02.md create mode 100644 docs/release/platform-mail-delivered-authentication-return-2026-11-02.json create mode 100644 docs/release/platform-mail-native-expiry-fixture-2026-11-02.json create mode 100644 tests/test_smtp_rejection_operation.py create mode 100644 workplans/CSOC-WP-0018-platform-mail-operational-follow-through.md diff --git a/docs/release/platform-mail-builder-close-review-2026-11-02.md b/docs/release/platform-mail-builder-close-review-2026-11-02.md new file mode 100644 index 0000000..57f9e64 --- /dev/null +++ b/docs/release/platform-mail-builder-close-review-2026-11-02.md @@ -0,0 +1,66 @@ +# T02 builder acceptance — concrete remaining operation + +Prepared, not approved or applied. The received original Gmail message proves +SPF/DKIM/DMARC PASS aligned to coulomb.social. Both selected replies correlate to +the actual accepted verification Message-ID; read-only native inbox access and +temporary-egress cleanup are verified. Receipt: +`platform-mail-delivered-authentication-return-2026-11-02.json`. + +The approved `.invalid` test produced a real, definitive SMTP refusal. Its original +durable reservation is failed/permanent_rejection, nonretryable, with no accepted +Message-ID and a canceled challenge. It did not produce an asynchronous DSN. +The earlier bounded-check review explicitly excluded using unaccepted sends as +DSN correlation evidence; this proposal asks for a distinct, narrowly defined +failure-authority path and explicit builder acceptance rather than reclassifying +that refusal as an accepted-message DSN. + +## Exact native operation + +Target only `csoc-t02-20261102@delivery-check.invalid`, username +`csoc-t02-bounce-20261102`. Retained private native packet: +`/tmp/t02-controlled-mail-20261102-8bz4engv/smtp-rejection.json`. + +Packet digest: +`0642902e84857a027e3478e08ea708617eb2cd79436b23d921d55ca53a599ebe`. +Plan digest: +`452fe723aeca76650c22e0a349f7f8c6534b868bc938284574f19d5a3761c017`. + +Preparation succeeded natively with no SQL/audit mutation. The source-bound packet +pins the receiver identity/image/PVC and caller policy. It reproduces the actual +owner's original operation key and canonical payload fingerprint, verifies the +exact recipient/username and admitted caller/binding, and requires failed, +permanent_rejection, retryable=false, provider_ref absent and challenge canceled. +Unknown, accepted, temporary/retryable, altered and unadmitted records refuse. + +On approval, apply one global-recipient hard_bounce suppression row, inspect its +receipt, prepare an explicit linked release and apply/inspect that release. The +SQL transaction verifies that all five other tables and every unrelated +suppression row remain unchanged. Final recipient state must match its original +unsuppressed state. No mail is sent and no existing suppression is released. +An uncertain write is inspected and reconciled; it is not repeated. Durable +private intent/decision metadata precedes SQL. This failure path retains metadata, +not a fabricated raw MIME report. The strict accepted-Message-ID DSN path remains +unchanged and refuses unaccepted messages. + +## Retention and acceptance boundary + +The actual pinned receiver successfully ran the reviewed expiry core in a private, +disposable synthetic fixture as UID10001. It held evidence before30days, previewed +the exact eligible copy, expired it at the boundary, retained decision metadata, +held incomplete operations and made repeated expiry a no-op. SQL/network access +was prohibited. No provider raw copy was backdated and the runtime clock was not +changed. Receipt: `platform-mail-native-expiry-fixture-2026-11-02.json`. +After the suppression/release, run the real native empty-raw expiry preview/apply +and verify retained metadata/ledger state. This does not claim a real provider +copy has already aged30days. + +Requested builder acceptance: use this actual synchronous hard-rejection native +suppression/release proof and the controlled native expiry rehearsal to finish +T02 once verified. Keep the first actual provider-DSN observation, actual copied +raw disposal deadlines and pre-production custody review as live operational +follow-ups in proposed `CSOC-WP-0018`. The user must explicitly approve that +handoff/acceptance; it is not inferred from the old SMTP or builder-policy approval. + +The full gate passes **776 tests**, including required browsers, with zero +failures/errors/skips. Twelve new rejection cases include the actual native pod +program/SQLite assembly; original DSN, release and stopped-state tests still pass. diff --git a/docs/release/platform-mail-delivered-authentication-return-2026-11-02.json b/docs/release/platform-mail-delivered-authentication-return-2026-11-02.json new file mode 100644 index 0000000..6ebcae5 --- /dev/null +++ b/docs/release/platform-mail-delivered-authentication-return-2026-11-02.json @@ -0,0 +1,51 @@ +{ + "schema": "coulomb.delivered-authentication-selected-reply.v1", + "verified_at": "2026-11-02T00:24:08.442136+00:00", + "approval_decision_id": "4fe1be4e-e8f7-452b-be94-987186df5850", + "packet_sha256": "0331e11c5c471eaf3e62226b50089849d2267731cc1c8c99e92e647259d88ae9", + "context": { + "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", + "deployment_uid": "2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72", + "generation": 79, + "image": "forgejo.coulomb.social/coulomb/email-connect@sha256:5d79fd1fec15d3a279383820f170d263de3a55f5308df01a0a34732ebaea6d3d", + "namespace_uid": "5678c5d7-e4f5-4a62-8f24-1c6e1d906319", + "pod_name": "email-connect-5559b4d9c7-7zp8d", + "pod_uid": "658e1d20-5f4d-4154-b40d-2be34cdf4b61", + "pvc_uid": "724d2dc1-c668-40df-a29c-ec90167c95ad" + }, + "reference": "<179357577675.1.17092244377121438005@coulomb.social>", + "binding": { + "accepted_test_binding_verified": true, + "caller_policy_sha256": "63c15c098d3ac2b73c5ffd7af743193b4bbdd05c0baa37a1d6666a558471140c", + "legacy_adoption": true, + "recipient": "bernd.worsch@gmail.com" + }, + "provider_message_uid": "6", + "selected_reply_sha256": "2e3e5dbd287d9ffdac9aebf0634e850ec09b6828c15bab2659a2fb26fae68b5f", + "reply_from_confirmed_human_operator": true, + "reference_chain_contains_accepted_message": true, + "original_eml_attachment_count": 1, + "original_canonical_sha256": "900856f27d91fe909b3689b9139a78c912851cb31049914c657860e624728daa", + "original_from_platform_verified": true, + "original_to_operator_verified": true, + "authentication_evidence": "mx.google.com Authentication-Results in original received Gmail message attached by confirmed operator", + "authentication_results": { + "dkim": "pass", + "spf": "pass", + "dmarc": "pass" + }, + "dkim_signing_domain": "coulomb.social", + "spf_mailfrom": "platform@coulomb.social", + "dmarc_header_from": "coulomb.social", + "alignment_verified": true, + "native_read_only_inbox": true, + "temporary_policy_absent": true, + "source_sha256": { + "mail_return_native.py": "00e6ee93de618a264f3c3506a9488907c820917d39093f6248d1135b3c508242", + "mail_return_operation.py": "79c70453cc07415e6f73e69b78e7aef5e8902772a8ea3f4b5c33f32516a9e458", + "selected_mailbox_check.py": "f082568d3bd149a2f11322b421def8eb566a703173fbd368ae6bd30c009be736" + }, + "raw_body_or_bearer_handles_printed": false, + "verification_consumed_by_check": false, + "raw_local_copy_expiry_at": "2026-12-02T00:02:19.925598+00:00" +} diff --git a/docs/release/platform-mail-native-expiry-fixture-2026-11-02.json b/docs/release/platform-mail-native-expiry-fixture-2026-11-02.json new file mode 100644 index 0000000..05b105d --- /dev/null +++ b/docs/release/platform-mail-native-expiry-fixture-2026-11-02.json @@ -0,0 +1,27 @@ +{ + "context": { + "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", + "namespace_uid": "5678c5d7-e4f5-4a62-8f24-1c6e1d906319", + "deployment_uid": "2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72", + "generation": 79, + "pod_uid": "658e1d20-5f4d-4154-b40d-2be34cdf4b61", + "pod_name": "email-connect-5559b4d9c7-7zp8d", + "pvc_uid": "724d2dc1-c668-40df-a29c-ec90167c95ad", + "image": "forgejo.coulomb.social/coulomb/email-connect@sha256:5d79fd1fec15d3a279383820f170d263de3a55f5308df01a0a34732ebaea6d3d" + }, + "result": { + "schema": "coulomb.native-expiry-fixture.v1", + "runtime_uid": 10001, + "synthetic_fixture_only": true, + "provider_raw_backdated": false, + "runtime_clock_changed": false, + "before_30_days_held": true, + "exact_preview_before_delete": true, + "eligible_synthetic_copy_expired": true, + "decision_metadata_retained": true, + "incomplete_operation_held": true, + "repeated_expiry_noop": true, + "sql_or_network_access": false, + "core_sha256": "4c0a2ca95d14b9c82330354de3cf8fbb6d00c5c07e8db2287699154def992171" + } +} diff --git a/scripts/mail_return_native.py b/scripts/mail_return_native.py index 7bcab20..5efde72 100644 --- a/scripts/mail_return_native.py +++ b/scripts/mail_return_native.py @@ -102,6 +102,8 @@ elif action=='list': elif action=='prepare': raw=base64.b64decode(r['raw_base64'],validate=True) result=m.prepare(ledger,raw,r['recipient'],policy,r['review'],invitation_payload=r.get('invitation_payload')) +elif action=='prepare-rejection': + result=m.prepare_rejection(ledger,r['recipient'],r['username'],policy,r['review']) elif action=='release-untracked': result=m.prepare_untracked_release(ledger, audit if audit.exists() else None, r['recipient'], r['reason'], r['created_at'], r['review']) @@ -353,6 +355,7 @@ def main(argv=None): "observe", "list", "prepare", + "prepare-rejection", "release", "release-untracked", "apply", @@ -364,6 +367,7 @@ def main(argv=None): parser.add_argument("--packet", type=Path) parser.add_argument("--message", type=Path) parser.add_argument("--recipient") + parser.add_argument("--username") parser.add_argument("--reason") parser.add_argument("--created-at") parser.add_argument("--review") @@ -378,10 +382,17 @@ def main(argv=None): return 0 require(args.packet is not None and args.packet.is_absolute(), "private_packet_required") operation.private_directory(args.packet.parent) - if args.action in {"prepare", "release", "release-untracked", "prepare-expiry"}: + if args.action in { + "prepare", + "prepare-rejection", + "release", + "release-untracked", + "prepare-expiry", + }: require(not args.packet.exists(), "new_packet_required") request.update( recipient=args.recipient, + username=args.username, reason=args.reason, created_at=args.created_at, review=args.review, @@ -452,7 +463,10 @@ def main(argv=None): if args.action == "apply": if packet["plan"].get("action") == "expire-raw": request["action"] = "apply-expiry" - elif packet["plan"]["action"] == "suppress": + elif ( + packet["plan"]["action"] == "suppress" + and packet["plan"].get("evidence_kind") != "permanent-smtp-rejection" + ): require( args.message is not None and args.message.is_file() diff --git a/scripts/mail_return_operation.py b/scripts/mail_return_operation.py index 75fd18a..f07aca3 100644 --- a/scripts/mail_return_operation.py +++ b/scripts/mail_return_operation.py @@ -233,6 +233,127 @@ def connect(path, *, writable=False): return db +def correlate_rejection(db, recipient, username, document): + """Bind a definitive SMTP failure to its original durable reservation. + + This is a separate authority from an accepted-message DSN. Unknown outcomes, + retryable failures and caller-supplied rejection claims never qualify. + """ + caller = selected_policy(document, "user-engine") + rows = db.execute( + "SELECT v.request_id,v.registration_id,v.email,v.username,v.client_id," + "v.tenant,v.display_name,v.caller_id,v.canceled_at,v.consumed_at," + "a.operation_key,a.fingerprint,a.state,a.error,a.retryable,a.provider_ref " + "FROM verifications v JOIN verification_attempts a ON a.request_id=v.request_id " + "WHERE v.email=? AND v.username=?", + (recipient.lower(), username), + ).fetchall() + require(len(rows) == 1, "one_recorded_rejection_required") + ( + request_id, + registration, + email, + recorded_username, + client, + tenant, + display, + recorded_caller, + canceled, + consumed, + key, + fingerprint, + state, + error, + retryable, + reference, + ) = rows[0] + require( + state == "failed" + and error == "permanent_rejection" + and retryable == 0 + and reference is None + and canceled is not None + and consumed is None, + "definitive_permanent_rejection_required", + ) + require( + recorded_caller == "user-engine" + or (recorded_caller == "" and caller.get("adopt_legacy_verifications") is True), + "recorded_rejection_caller_denied", + ) + require(binding_allowed(caller, tenant, client), "caller_binding_denied") + payload = { + "registration_id": registration, + "normalized_email": email, + "preferred_username": recorded_username, + "client_id": client, + "tenant": tenant, + "display_name": display, + } + identity = [tenant, client, registration] + if recorded_caller: + identity.insert(0, recorded_caller) + require( + digest(payload) == fingerprint + and hashlib.sha256(json.dumps(identity).encode()).hexdigest() == key, + "rejection_reservation_binding_changed", + ) + binding = { + "kind": "verification-rejection", + "id": request_id, + "caller_id": "user-engine", + "tenant": tenant, + "client_id": client, + "recipient": email.lower(), + "username": recorded_username, + "provider_ref": None, + } + evidence = { + "operation_key": key, + "fingerprint": fingerprint, + "state": state, + "error": error, + "retryable": False, + "provider_ref": None, + "canceled_at": canceled, + } + return binding, evidence + + +def prepare_rejection(path, recipient, username, document, review, *, now=None): + require( + isinstance(recipient, str) + and 3 <= len(recipient) <= 320 + and isinstance(username, str) + and 1 <= len(username) <= 128, + "selected_rejection_identity_required", + ) + require( + isinstance(review, str) and 10 <= len(review.strip()) <= 500, + "explicit_operator_provenance_review_required", + ) + with closing(connect(path)) as db: + db.execute("BEGIN") + binding, evidence = correlate_rejection(db, recipient, username, document) + before = suppression(db, binding["recipient"]) + require(before is None, "existing_suppression_requires_review") + created = timestamp(now or datetime.now(UTC)) + return { + "schema": SCHEMA, + "action": "suppress", + "created_at": created, + "binding": binding, + "policy_sha256": digest(document), + "evidence_kind": "permanent-smtp-rejection", + "failure_evidence": evidence, + "evidence_sha256": digest(evidence), + "operator_review": review, + "before": None, + "after": [binding["recipient"], "hard_bounce", created], + "scope": "recipient-global across callers and tenants", + } + + def suppression(db, recipient): row = db.execute( "SELECT email,reason,created_at FROM suppressions WHERE email=?", (recipient,) @@ -319,9 +440,7 @@ def list_suppressions(path, audit_root=None): } -def prepare_untracked_release( - path, audit_root, email, reason, created_at, review, *, now=None -): +def prepare_untracked_release(path, audit_root, email, reason, created_at, review, *, now=None): """Release one ledger row that no suppress operation accounts for. A row created by this operator keeps its linked release. This path exists @@ -488,13 +607,22 @@ def apply(path, plan, raw, document, expected, audit_root): private_directory(audit_root) if plan["action"] == "suppress": require(digest(document) == plan["policy_sha256"], "caller_policy_changed") - require( - hashlib.sha256(raw).hexdigest() == plan["evidence_sha256"], "selected_evidence_changed" - ) - require( - permanent_dsn(raw, plan["binding"]["recipient"]) == plan["binding"]["provider_ref"], - "dsn_correlation_changed", - ) + if plan.get("evidence_kind") == "permanent-smtp-rejection": + require(raw is None, "rejection_has_no_message") + require( + digest(plan["failure_evidence"]) == plan["evidence_sha256"], + "rejection_evidence_changed", + ) + else: + require(plan.get("evidence_kind") is None, "evidence_kind_unknown") + require( + hashlib.sha256(raw).hexdigest() == plan["evidence_sha256"], + "selected_evidence_changed", + ) + require( + permanent_dsn(raw, plan["binding"]["recipient"]) == plan["binding"]["provider_ref"], + "dsn_correlation_changed", + ) elif plan["action"] == "release": original = completed_operation( audit_root, plan["source_plan_sha256"], "suppression_applied" @@ -529,21 +657,28 @@ def apply(path, plan, raw, document, expected, audit_root): def commit(path, plan, raw, document, expected, directory): durable(directory / "plan.json", canonical(plan) + b"\n") - if plan["action"] == "suppress": + if plan["action"] == "suppress" and plan.get("evidence_kind") is None: durable(directory / "message.eml", raw) with closing(connect(path, writable=True)) as db: try: db.execute("BEGIN IMMEDIATE") recipient = plan["binding"]["recipient"] + preserved = protected_ledger_digest(db, recipient) if plan["action"] == "suppress": - bound = correlate( - db, - plan["binding"]["provider_ref"], - recipient, - plan["binding"]["caller_id"], - document, - plan["invitation_payload"], - ) + if plan.get("evidence_kind") == "permanent-smtp-rejection": + bound, evidence = correlate_rejection( + db, recipient, plan["binding"]["username"], document + ) + require(evidence == plan["failure_evidence"], "rejection_record_changed") + else: + bound = correlate( + db, + plan["binding"]["provider_ref"], + recipient, + plan["binding"]["caller_id"], + document, + plan["invitation_payload"], + ) require(bound == plan["binding"], "accepted_outbound_changed") require( plan["before"] is None @@ -573,6 +708,10 @@ def commit(path, plan, raw, document, expected, directory): ) else: db.execute("DELETE FROM suppressions WHERE email=?", (recipient,)) + require( + protected_ledger_digest(db, recipient) == preserved, + "unrelated_ledger_state_changed", + ) db.commit() except BaseException: db.rollback() @@ -585,10 +724,39 @@ def commit(path, plan, raw, document, expected, directory): "plan_sha256": expected, "status": status, "completed_at": timestamp(datetime.now(UTC)), + "unrelated_ledger_rows_preserved": True, + "protected_ledger_sha256": preserved, } ), ) - return {"status": status, "plan_sha256": expected, "scope": plan["scope"], "mail_sent": False} + return { + "status": status, + "plan_sha256": expected, + "scope": plan["scope"], + "mail_sent": False, + "unrelated_ledger_rows_preserved": True, + "protected_ledger_sha256": preserved, + } + + +def protected_ledger_digest(db, selected_recipient): + state = {} + for table in ( + "deliveries", + "send_attempts", + "verifications", + "verification_attempts", + "caller_buckets", + "suppressions", + ): + query = 'SELECT * FROM "' + table + '"' + rows = ( + db.execute(query + " WHERE email<>?", (selected_recipient,)) + if table == "suppressions" + else db.execute(query) + ) + state[table] = sorted(canonical(row).hex() for row in rows) + return digest(state) def inspect(path, directory): diff --git a/tests/test_smtp_rejection_operation.py b/tests/test_smtp_rejection_operation.py new file mode 100644 index 0000000..6964f86 --- /dev/null +++ b/tests/test_smtp_rejection_operation.py @@ -0,0 +1,197 @@ +"""Definitive SMTP rejection authority, distinct from accepted-message DSNs.""" + +import copy +import hashlib +import json +import sqlite3 +from datetime import UTC, datetime + +import pytest + +from tests.test_mail_return_native import Cluster, native, request +from tests.test_mail_return_operation import operation + +RECIPIENT = "fixture@example.invalid" +USERNAME = "fixture-rejected-user" +POLICY = { + "schema_version": 1, + "callers": [ + { + "caller_id": "user-engine", + "bindings": [["tenant:one", "client:one"]], + "adopt_legacy_verifications": True, + } + ], +} + + +@pytest.fixture +def rejected(tmp_path): + path = tmp_path / "transactional.db" + audit = tmp_path / "audit" + audit.mkdir(mode=0o700) + payload = { + "registration_id": "registration-one", + "normalized_email": RECIPIENT, + "preferred_username": USERNAME, + "client_id": "client:one", + "tenant": "tenant:one", + "display_name": "Fixture rejection", + } + key = hashlib.sha256( + json.dumps(["tenant:one", "client:one", "registration-one"]).encode() + ).hexdigest() + with sqlite3.connect(path) as db: + db.executescript(""" + CREATE TABLE verifications (request_id PRIMARY KEY, registration_id, + email,username,client_id,tenant,display_name,caller_id,canceled_at,consumed_at); + CREATE TABLE verification_attempts(operation_key PRIMARY KEY,fingerprint, + request_id,state,error,retryable,provider_ref); + CREATE TABLE suppressions(email PRIMARY KEY,reason,created_at); + CREATE TABLE deliveries(event_id PRIMARY KEY,provider_ref); + CREATE TABLE send_attempts(event_id PRIMARY KEY,fingerprint,state); + CREATE TABLE caller_buckets(caller_id PRIMARY KEY,tokens,updated_at); + """) + db.execute( + "INSERT INTO verifications VALUES(?,?,?,?,?,?,?,?,?,?)", + ( + "request-one", + "registration-one", + RECIPIENT, + USERNAME, + "client:one", + "tenant:one", + "Fixture rejection", + "", + "2026-01-01T00:00:00Z", + None, + ), + ) + db.execute( + "INSERT INTO verification_attempts VALUES(?,?,?,?,?,?,?)", + ( + key, + operation.digest(payload), + "request-one", + "failed", + "permanent_rejection", + 0, + None, + ), + ) + db.execute( + "INSERT INTO suppressions VALUES(?,?,?)", + ("unrelated@example.test", "manual", "2025-01-01T00:00:00Z"), + ) + db.execute("INSERT INTO caller_buckets VALUES(?,?,?)", ("user-engine", 5, 100)) + return path, audit + + +def rows(path): + with sqlite3.connect(path) as db: + tables = [r[0] for r in db.execute("SELECT name FROM sqlite_master WHERE type='table'")] + return { + name: db.execute('SELECT * FROM "' + name + '" ORDER BY 1').fetchall() + for name in tables + } + + +def prepare(rejected, document=POLICY): + return operation.prepare_rejection( + rejected[0], + RECIPIENT, + USERNAME, + document, + "Operator reviewed the definitive recorded SMTP failure", + ) + + +def test_native_style_rejection_apply_and_linked_release_preserve_other_tables(rejected): + path, audit = rejected + before = rows(path) + plan = prepare(rejected) + assert plan["binding"]["provider_ref"] is None + assert plan["evidence_kind"] == "permanent-smtp-rejection" + result = operation.apply(path, plan, None, POLICY, operation.digest(plan), audit) + assert result["status"] == "suppression_applied" + assert not (audit / operation.digest(plan) / "message.eml").exists() + after = rows(path) + assert len(after["suppressions"]) == len(before["suppressions"]) + 1 + assert {k: v for k, v in after.items() if k != "suppressions"} == { + k: v for k, v in before.items() if k != "suppressions" + } + release = operation.prepare_release( + path, + audit, + operation.digest(plan), + "Release the explicitly selected builder test rejection", + ) + operation.apply(path, release, None, POLICY, operation.digest(release), audit) + assert rows(path) == before + assert ( + operation.expire_raw(audit, now=datetime.now(UTC), execute=False)["expired_raw_copies"] + == [] + ) + + +@pytest.mark.parametrize( + "column,value", + [ + ("state", "in_flight"), + ("state", "accepted"), + ("error", "provider_timeout"), + ("retryable", 1), + ("retryable", None), + ("provider_ref", ""), + ("fingerprint", "wrong"), + ("operation_key", "wrong"), + ], +) +def test_unknown_retryable_accepted_or_mismatched_failures_never_authorize(rejected, column, value): + with sqlite3.connect(rejected[0]) as db: + db.execute('UPDATE verification_attempts SET "' + column + '"=?', (value,)) + before = rows(rejected[0]) + with pytest.raises(operation.Refused): + prepare(rejected) + assert rows(rejected[0]) == before and list(rejected[1].iterdir()) == [] + + +def test_policy_adoption_and_binding_are_required(rejected): + policy = copy.deepcopy(POLICY) + policy["callers"][0]["adopt_legacy_verifications"] = False + with pytest.raises(operation.Refused): + prepare(rejected, policy) + policy["callers"][0]["adopt_legacy_verifications"] = True + policy["callers"][0]["bindings"] = [] + with pytest.raises(operation.Refused): + prepare(rejected, policy) + + +def test_failure_changes_between_prepare_and_sql_refuse_without_suppression(rejected): + plan = prepare(rejected) + with sqlite3.connect(rejected[0]) as db: + db.execute("UPDATE verification_attempts SET state='in_flight'") + before = rows(rejected[0]) + with pytest.raises(operation.Refused): + operation.apply(rejected[0], plan, None, POLICY, operation.digest(plan), rejected[1]) + assert rows(rejected[0]) == before + + +def test_actual_native_pod_assembly_handles_rejection_without_a_mime_message( + rejected, tmp_path, monkeypatch +): + cluster = Cluster(rejected, tmp_path, monkeypatch) + cluster.policy.write_text(json.dumps(POLICY)) + prepared = native.run_node( + request( + "prepare-rejection", + recipient=RECIPIENT, + username=USERNAME, + review="Operator reviewed the authoritative failed SMTP test", + ), + cluster, + ) + result = native.run_node( + request("apply", expected_context=prepared["context"], plan=prepared["result"]), cluster + ) + assert result["result"]["status"] == "suppression_applied" diff --git a/workplans/CSOC-WP-0015-communication-runtime-acceptance.md b/workplans/CSOC-WP-0015-communication-runtime-acceptance.md index 977ea79..10b828d 100644 --- a/workplans/CSOC-WP-0015-communication-runtime-acceptance.md +++ b/workplans/CSOC-WP-0015-communication-runtime-acceptance.md @@ -44,17 +44,27 @@ policy none. Verify was not used and the reply was not suppressed. Receipt: [selected reply](../docs/release/platform-mail-selected-reply-2026-11-02.json). Real permanent-bounce correlation, native suppression/release and retention acceptance remain open; the synchronous .invalid refusal is not a DSN. -The operator and expiry have source/native-adapter and real SQLite assembly proof. -Latest full gate:764 passed, zero failures/errors/skips, including required browser -journeys. Builder policy112e14bb stays approved. No blanket mailbox scan or -automatic replies are authorized. +A distinct definitive-SMTP-rejection path is implemented and natively prepared: +exact original reservation key/payload fingerprint, admitted caller/binding and +nonretryable permanent failure all verify. No suppression has applied. The actual +receiver passed the30-day boundary rehearsal with synthetic evidence only; no +provider copy was aged and the runtime clock was unchanged. Latest full gate: +776 passed, zero failures/errors/skips, including required browsers. The +[concrete builder review](../docs/release/platform-mail-builder-close-review-2026-11-02.md) +is pending decision408e21be-a526-4996-848f-0ec618dbcd78: exact .invalid suppression/ +linked release and builder acceptance, with actual provider-DSN, real raw-copy +expiry and pre-production custody follow-through kept live in proposed +CSOC-WP-0018. Do not infer that changed acceptance from earlier approvals. +Independent authentication receipt: +[delivered original](../docs/release/platform-mail-delivered-authentication-return-2026-11-02.json). +Builder policy112e14bb stays approved. No blanket scan or automatic replies. T01 caller policy, T03 independent ledger recovery and the narrowed T04 implementation/native rehearsal/application release are complete. T02 owns the platform sender and return-mail operation. Platform-root with Bernd as human operator is explicitly accepted; see decision9392ef52 below. Sender admission, -transition and selected read-only inbox access are verified; return-message -correlation remains open. Suppression +transition, selected inbox access, delivered authentication and reply correlation +are verified. Native suppression handling and retention also remain open. Backup-specific Gmail receipt does not close those gates. T05 exposes productive flow verdicts and T06 the remaining native reset-authority owner return. @@ -88,9 +98,9 @@ id: CSOC-WP-0015-T02 status: progress needs_human: true blocked_on: human -decision_id: "4fe1be4e-e8f7-452b-be94-987186df5850" -blocking_reason: "The selected Gmail reply and original authentication headers are verified. A real permanent DSN is not yet correlated through the operator, and copied-return retention is not accepted. Do not repeat the reply fetch." -intervention_note: "The 00:59 reply attaches the original verification message. Gmail reports DKIM, SPF and DMARC pass for platform@coulomb.social. Verify was not used. The .invalid registration refusal is not a DSN. Native bounce suppression and retention acceptance remain open." +decision_id: "408e21be-a526-4996-848f-0ec618dbcd78" +blocking_reason: "Delivered authentication and actual reply correlation are verified. Await exact SMTP-rejection suppression/release and builder-handoff approval." +intervention_note: "Approve or decline docs/release/platform-mail-builder-close-review-2026-11-02.md: exact .invalid suppression/linked release, builder T02 acceptance only after verification, and actual DSN/raw-expiry/custody follow-through in proposed CSOC-WP-0018. No more login, test sends or mail export is needed. Earlier approvals remain valid; no suppression has applied." depends_on: [RPF-WP-0068-T04, EMAIL-WP-0010-T04] priority: high state_hub_task_id: "0604cb15-72c7-5d15-bb42-0335d4ec80df" diff --git a/workplans/CSOC-WP-0018-platform-mail-operational-follow-through.md b/workplans/CSOC-WP-0018-platform-mail-operational-follow-through.md new file mode 100644 index 0000000..42025f8 --- /dev/null +++ b/workplans/CSOC-WP-0018-platform-mail-operational-follow-through.md @@ -0,0 +1,81 @@ +--- +id: CSOC-WP-0018 +type: workplan +title: "Platform mail operational follow-through" +domain: communication +repo: coulomb-social +status: proposed +flavor: residual +owner: codex +topic_slug: coulomb-social +created: "2026-11-02" +updated: "2026-11-02" +origin: residual +origin_ref: CSOC-WP-0015 +--- + +Proposed explicit operational handoff from CSOC-WP-0015-T02, pending the user's +builder acceptance decision. This does not itself close T02 or claim live DSN or +mature provider-evidence expiry. The approved builder operating policy112e14bb +and bounded inbox purpose4fe1be4e remain in force. No new caller, reader, +background scanner, automatic reply or provider deletion is admitted. + +## T01 — Observe the first selected real provider DSN + +```task +id: CSOC-WP-0018-T01 +status: wait +priority: medium +``` + +When Bernd selects an actual permanent provider DSN, retain private provenance, +bind its original accepted Message-ID and authoritative recipient/caller/tenant/ +client, and exercise the unchanged strict DSN operator with explicit suppression +and linked release if appropriate. Unknown/temporary/ambiguous returns remain held. +Do not manufacture a provider DSN or scan unrelated mail to satisfy this task. +Existing selected IMAP approval is limited to the two T02 test requests; any +additional inbox retrieval needs an explicit selected scope or manual export. +Evidence must distinguish this real DSN from T02's synchronous SMTP rejection. + +## T02 — Expire actual copied raw evidence within 30 days + +```task +id: CSOC-WP-0018-T02 +status: wait +wait_until: "2026-12-01T23:45:00+00:00" +priority: high +``` + +Bernd/platform-root owns the private local copies at +`/tmp/t02-controlled-mail-20261102-8bz4engv/`. Remove only these exact copied raw +files by their deadlines, preserving minimal decision/correlation metadata and +provider originals: + +- `9be8d58ca540bc370883f66f35f1a332f5ffcd75f2810f0948d2b538d1063dd2.eml`: + 2026-12-01T23:55:14.370151Z. +- `2e3e5dbd287d9ffdac9aebf0634e850ec09b6828c15bab2659a2fb26fae68b5f.eml`: + 2026-12-02T00:02:19.925598Z. + +Verify private ownership, exact contents/hash and retained decision metadata +before removal; record absence after. Earlier temporary-directory cleanup is +recorded as earlier disposal, never as a successful mature30-day native expiry. +Also handle any actual eligible native `/data/mail-returns/` copied DSN through +its exact preview/apply path, preserving pending challenges, suppression decisions, +incomplete operations and originals. No provider copy is backdated or clock +advanced. T02's synthetic native runtime fixture is implementation proof only. + +## T03 — Review builder custody before production + +```task +id: CSOC-WP-0018-T03 +status: wait +priority: medium +``` + +Before production, obtain Bernd/platform-root's disposition for the provider +inbox originals and minimal operation/binding metadata held through the builder +phase. Inventory includes native `/data/mail-returns/` and private operator files; +the current SQLite-only archive does not back up those paths. Agree the operating +backup/offboarding treatment and expiry enforcement without deleting other readers' +records or broadening mailbox access. Keep CSOC-WP-0015-T05 productive user-flow +verdicts and T06 reset authority separate from this operating follow-through.