diff --git a/docs/release/mail-return-builder-policy-approval-2026-11-01.json b/docs/release/mail-return-builder-policy-approval-2026-11-01.json new file mode 100644 index 0000000..09410e8 --- /dev/null +++ b/docs/release/mail-return-builder-policy-approval-2026-11-01.json @@ -0,0 +1,15 @@ +{ + "schema": "coulomb.builder-mail-policy-approval.v1", + "decision_id": "112e14bb-4b5d-46d7-b365-84628f72f115", + "status": "resolved", + "decided_by": "Bernd Worsch", + "decided_at": "2026-11-01T22:15:18.419286Z", + "user_response": "Approve this builder policy", + "scope": "Bernd/platform-root handles selected replies and bounces. A provenance-reviewed permanent bounce may suppress its correlated recipient across all callers/tenants, with explicit reviewed release. Copied raw returns expire after 30 days. The provider inbox and minimal decision metadata remain in Bernd custody through the builder phase, with disposition reviewed before production. No automatic inbox scanning or deletion.", + "operator_policy_approved": true, + "credential_reader_or_http_caller_granted": false, + "source_sha256": { + "scripts/mail_return_operation.py": "0aa616e4a5c8271901d7ebd4436327ff2551637e74934171a838b728944ba6e2", + "scripts/mail_return_native.py": "56713fef4bfd494d6dee152c83a8b04ac03de563c88076f08c1a3477740238b3" + } +} diff --git a/docs/release/mail-return-operator-rehearsal-2026-11-01.json b/docs/release/mail-return-operator-rehearsal-2026-11-01.json new file mode 100644 index 0000000..cff107b --- /dev/null +++ b/docs/release/mail-return-operator-rehearsal-2026-11-01.json @@ -0,0 +1,41 @@ +{ + "schema": "coulomb.mail-return-boundary-rehearsal.v1", + "completed_at": "2026-11-01T21:36:20.488959+00:00", + "source_sha256": { + "src/email_connect/__init__.py": "0f1ae5e9251f80b0bd5220b1d7bf91735d6ea273e07aa26cb7852a7950be7087", + "src/email_connect/adapter_contract.py": "5059b2eaaac2408f4e247bb468a97960eb3f823e63c3eeb151a1c2250e1f3bbf", + "src/email_connect/callers.py": "06fe961b9030dba2c0b26b92f8097e8fbd24e205d5fa79642aa9af60382886dc", + "src/email_connect/cli.py": "6ac6ae8acd091ba2d2102589be3b75fe3a48392309e40e61f8383deb3329ad42", + "src/email_connect/config.py": "6ca89fb7c3e1f35bc4faedfba8c3313d4366b2cb7830ef88ed3f7d2313f7867e", + "src/email_connect/evidence.py": "42cca03234a79dc78033ec1fbaf0b1785e6089ef3f3128e2e9d666ed239a3582", + "src/email_connect/mailbox.py": "7f0fe7e19ce1f814269609c4236152042f0f49bf3b517114b3b3f6f482471362", + "src/email_connect/models.py": "7f8136646b663a097280286dccce76a90abb7190ea0712fff7da32d47526521c", + "src/email_connect/parser.py": "786b291256ddfac5a8622c743622508cdd9f45f06156d0f5487ce399fad1287c", + "src/email_connect/recipients.py": "68792f44f0d2ba204649542aa8bd6b0f209d89c675baa4fc0767f1250a29045f", + "src/email_connect/reporting.py": "8f8ce5f98e4347b778893d08a9e58f2bab3b44b80c159fcf52d0b5772429737c", + "src/email_connect/scanner.py": "6266cbad2dbc610c1f6df1c051ded2ee55b43d7ff30af6eab38721924931c1ed", + "src/email_connect/storage.py": "57fbd6d3b2e7541c0bf52d87a4f7361a4ff4a03511620d2a1c3789632b73afeb", + "src/email_connect/transactional.py": "36fdfd8cb58f10148cd491a7e34d7aea798e4289fce760af70a48776bede204d" + }, + "probe_sha256": "7da072da173de79f52a351ee29238993d1f59975b2d0d62040147369085c7785", + "transactional_source_matches_native": true, + "smtp_transport": "in-process synthetic replacement; no SMTP connection", + "message_id_correlates_with_ledger_and_synthetic_bounce": true, + "explicit_reply_to_or_return_path_headers": false, + "scanner_deduplicates_evidence": true, + "scanner_updates_transactional_suppression": false, + "ordinary_sender_suppress_and_unsuppress_denied": true, + "synthetic_operator_suppression_blocks_send_and_survives_restart": true, + "suppression_scope": "recipient-global across callers and tenants", + "explicit_synthetic_operator_unsuppression_verified": true, + "selected_return_operator_source_sha256": "6f2dc3038b373cc11581bc8e5aa15e07757f2b262221c3917bb11a4f5be186c2", + "selected_operator_correlates_actual_owner_payload_and_accepted_message_id": true, + "selected_operator_suppresses_across_callers_and_explicitly_releases": true, + "selected_operator_preserves_all_five_other_ledger_tables": true, + "native_operator_admission_proved": false, + "received_mail_authenticity_or_correlation_authority_proved": false, + "python_network_operations_blocked": true, + "temporary_ledgers_removed": true, + "mail_sent": false, + "production_mailbox_accessed": false +} diff --git a/docs/release/mail-return-operator-review-2026-11-01.md b/docs/release/mail-return-operator-review-2026-11-01.md new file mode 100644 index 0000000..49954c1 --- /dev/null +++ b/docs/release/mail-return-operator-review-2026-11-01.md @@ -0,0 +1,88 @@ +# Builder-stage selected return handling + +Implementation/preparation for CSOC-WP-0015-T02. Native sender/inbox/bounce +acceptance remains open. This does not activate a mailbox scanner or HTTP caller. + +`scripts/mail_return_operation.py` provides an explicit operator CLI for selected +permanent DSNs. It uses the existing transactional SQLite schema and changes only +one recipient's suppression row per operation. No new table, service, credential, +automatic response, SQL history purge or send retry is introduced. + +## Operation and authority + +Preparation requires one selected MIME delivery-status report, an explicit +operator provenance review, the current non-secret caller policy and an accepted +outbound record. It refuses temporary/ambiguous/unstructured reports, unmatched +Message-IDs, different recipients, unadmitted caller/tenant/client bindings and +unknown send outcomes. A parsed Message-ID does not authenticate a return; the +human/operator still reviews its provenance. Policy adoption of historical +verification records must be explicit. Invitation correlation also requires the +original caller payload and reproduces its actual stored fingerprint and event +mapping, including the admitted historical mapping. + +The prepared packet contains the binding and hashes, with no raw MIME copy. Apply +requires the same source hash, exact plan digest, selected message and current +caller policy. It rechecks accepted outbound state and the target suppression +inside `BEGIN IMMEDIATE`. Private plan/evidence/intent files are fsynced before +commit; completed evidence is separate. A prior attempt refuses replay. Inspection +reports actual before/after/later state. Explicit reconciliation takes the stopped +operation's lock and verifies actual state without repeating SQL. A running lock, +unrelated later state or inconsistent receipt refuses reconciliation. + +Suppression is **global for that recipient across callers and tenants**. This is +the current service's behavior. Source tests do not admit a native operator or +approve that policy on the user's behalf. Release is a separate reviewed plan +linked to the completed original operation and exact suppression preimage. It +retains unrelated decisions and all send/challenge history. + +Run `python3 scripts/mail_return_operation.py --help` for the CLI. `prepare` and +`release` save new private plans; `apply` requires `--expect-digest`. `inspect` +reads an existing operation; `reconcile` verifies a stopped outcome. Local paths +are explicit. Native deployment/transport and real return provenance still need +the current sender/inbox evidence; no native operator mutation was executed by +this preparation. + +## Proposed evidence retention + +For builder operation, propose 30 days for the operator's copied raw DSN, measured +from completed handling. `expire-raw` defaults to preview and returns an exact +timestamp/digest; execution requires that timestamp and digest. It validates the +retained plan, completed operation and original raw hash before removing only the +copied evidence. It preserves original mailbox messages and selected source +files, ledger records, pending/consumed/cancelled challenges, suppression rows and +decision/binding metadata. Incomplete attempts are held for review. No automatic +timer or provider-mailbox deletion is added. + +Keep the minimal private decision metadata for the builder phase and review its +expiry before production or offboarding. This is a proposal requiring operator +agreement, not a claim of approved retention or a provider deletion capability. +The broader service/client retention obligations remain with WP-0012. + +## Evidence + +Twenty-eight isolated operator cases pass, including actual SQLite transactions, +two real abrupt process deaths, stopped-state reconciliation, lock refusal, +invitation/verification correlation, caller-boundary refusals, explicit release, +CLI/source checks and raw expiry with complete unrelated-record preservation. +Nineteen founder-launcher cases pass independently. The native adapter and its +fourteen assembly cases are described in `native-mail-return-review-2026-11-01.md`. + +The extended existing no-network rehearsal also exercises actual email-connect +source and its six-table store. An accepted invitation is correlated from the +real scoped handler's fingerprint; the selected operator blocks both original +and other-tenant sends and explicitly releases it. All five other tables stay +unchanged by each operator mutation. Ordinary HTTP callers still cannot suppress +or release. Network operations are prohibited and all synthetic stores are +removed. Receipt: `mail-return-operator-rehearsal-2026-11-01.json`. + +These checks prove source behavior. Native transport, selected provider delivery +and authentication, operator inbox/reply access, actual return handling and policy/ +retention agreement remain T02 closure requirements. + +Follow-through: native transport is implemented and its live read-only observation +passes. The user approved the builder policy in decision +112e14bb-4b5d-46d7-b365-84628f72f115, including global-recipient suppression/release, +30-day copied-raw expiry and builder-phase inbox/metadata custody with review before +production. Explicit native writes and actual provider/inbox evidence remain open. +The core also fsyncs the newly created operation directory's parent +before SQL, and holds attempts that stopped before their first plan was durable. diff --git a/docs/release/native-mail-return-observation-2026-11-01-r1.json b/docs/release/native-mail-return-observation-2026-11-01-r1.json new file mode 100644 index 0000000..cf74332 --- /dev/null +++ b/docs/release/native-mail-return-observation-2026-11-01-r1.json @@ -0,0 +1,37 @@ +{ + "schema": "coulomb.native-mail-return-observation.v1", + "observed_at": "2026-11-01T22:12:46.694952+00:00", + "context": { + "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", + "namespace_uid": "5678c5d7-e4f5-4a62-8f24-1c6e1d906319", + "deployment_uid": "2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72", + "generation": 76, + "pod_uid": "e0e4e2c0-67f9-4d23-a8ba-33e245127695", + "pod_name": "email-connect-66cf8b84dd-f6rjh", + "pvc_uid": "724d2dc1-c668-40df-a29c-ec90167c95ad", + "image": "forgejo.coulomb.social/coulomb/email-connect@sha256:5d79fd1fec15d3a279383820f170d263de3a55f5308df01a0a34732ebaea6d3d", + "caller_policy_sha256": "63c15c098d3ac2b73c5ffd7af743193b4bbdd05c0baa37a1d6666a558471140c" + }, + "result": { + "status": "native_return_observed", + "table_counts": { + "caller_buckets": 1, + "deliveries": 2, + "send_attempts": 0, + "suppressions": 1, + "verification_attempts": 127, + "verifications": 127 + }, + "ledger_sha256": "d3b5809ab6dd092bb0d2302bc07c80929bb1022bd1d9b588b739e5f85b048481", + "audit_root_present": false, + "ledger_modified": false + }, + "source_sha256": { + "mail_return_native.py": "56713fef4bfd494d6dee152c83a8b04ac03de563c88076f08c1a3477740238b3", + "mail_return_operation.py": "0aa616e4a5c8271901d7ebd4436327ff2551637e74934171a838b728944ba6e2" + }, + "mail_sent": false, + "mailbox_accessed": false, + "credential_fetched": false, + "operator_mutation_executed": false +} diff --git a/docs/release/native-mail-return-observation-2026-11-01.json b/docs/release/native-mail-return-observation-2026-11-01.json new file mode 100644 index 0000000..3cbae68 --- /dev/null +++ b/docs/release/native-mail-return-observation-2026-11-01.json @@ -0,0 +1,37 @@ +{ + "schema": "coulomb.native-mail-return-observation.v1", + "observed_at": "2026-11-01T22:04:53.145449+00:00", + "context": { + "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", + "namespace_uid": "5678c5d7-e4f5-4a62-8f24-1c6e1d906319", + "deployment_uid": "2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72", + "generation": 76, + "pod_uid": "e0e4e2c0-67f9-4d23-a8ba-33e245127695", + "pod_name": "email-connect-66cf8b84dd-f6rjh", + "pvc_uid": "724d2dc1-c668-40df-a29c-ec90167c95ad", + "image": "forgejo.coulomb.social/coulomb/email-connect@sha256:5d79fd1fec15d3a279383820f170d263de3a55f5308df01a0a34732ebaea6d3d", + "caller_policy_sha256": "63c15c098d3ac2b73c5ffd7af743193b4bbdd05c0baa37a1d6666a558471140c" + }, + "result": { + "status": "native_return_observed", + "table_counts": { + "caller_buckets": 1, + "deliveries": 2, + "send_attempts": 0, + "suppressions": 1, + "verification_attempts": 127, + "verifications": 127 + }, + "ledger_sha256": "d3b5809ab6dd092bb0d2302bc07c80929bb1022bd1d9b588b739e5f85b048481", + "audit_root_present": false, + "ledger_modified": false + }, + "source_sha256": { + "mail_return_native.py": "56713fef4bfd494d6dee152c83a8b04ac03de563c88076f08c1a3477740238b3", + "mail_return_operation.py": "3de2439eebca0cde16b81a3ca04aa2e1233f0a0abaac341fdd4772f316098efe" + }, + "mail_sent": false, + "mailbox_accessed": false, + "credential_fetched": false, + "operator_mutation_executed": false +} diff --git a/docs/release/native-mail-return-review-2026-11-01.md b/docs/release/native-mail-return-review-2026-11-01.md new file mode 100644 index 0000000..5590ee1 --- /dev/null +++ b/docs/release/native-mail-return-review-2026-11-01.md @@ -0,0 +1,97 @@ +# Native selected-return operator + +CSOC-WP-0015-T02 preparation. Native observation succeeds; no live suppression, +expiry, mail submission or mailbox access has executed through this adapter. + +`scripts/mail_return_native.py` connects the selected-return operation to the +existing receiver. It introduces no HTTP operator caller, standing credential, +workload, scheduled scanner or Kubernetes resource. The existing root operator +SSH route transports a source-bound request to the fixed node. The node verifies +the exact cluster, namespace, deployment, sole Ready receiver, ReplicaSet owner, +pinned image and mounted Bound PVC. The receiver checks UID 10001, actual installed +transactional/caller source hashes and the configured database/caller-policy paths. +It never reads the SMTP password or caller-token files. + +The read-only native observation sees all six expected tables, two historical +delivery receipts, 127 verification records and one existing suppression. It finds +no operator audit directory. There are no invitation send-attempt fingerprints; +the historical deliveries therefore cannot authorize a correlated invitation +suppression. Legacy verification adoption still follows the admitted policy. +Latest source-bound receipt: `native-mail-return-observation-2026-11-01-r1.json`. + +## Commands + +Default observation: + +```bash +python3 scripts/mail_return_native.py +``` + +`prepare` takes a specifically selected local `.eml`, recipient, explicit operator +provenance review and fresh private packet path. Invitation handling additionally +requires the exact original caller payload. `release` prepares a separate plan +linked to an existing native suppression operation. `apply` requires that retained +packet and its exact digest; a suppression also requires the same selected file. +The packet binds both current source files and native identities/policy. Raw MIME +travels through encrypted SSH/stdin and is copied only into the private native +audit on explicit apply; the workstation packet stores no raw MIME. + +`inspect` verifies a retained operation's current state. `reconcile` requires the +packet digest and verifies a stopped outcome without repeating SQL. These can +inspect a replacement receiver only when the same deployment, image and PVC +identity remain. Apply requires the original receiver/generation and policy. +Lost replies and replacement during a write remain unconfirmed until inspected; +the adapter never automatically repeats a write or restores an older ledger. + +`prepare-expiry` creates an exact private preview for eligible 30-day copied raw +returns; `apply` verifies the same preview/time and deletes only those copies. +`inspect` verifies absent copies against retained expiry intents. `reconcile` +settles only the original selection after a lost or partial expiry response. +Original mailbox messages, caller/tenant bindings, decision records, challenges +and suppression rows are preserved by this expiry operation. + +The audit lives at `/data/mail-returns//`, owned by the existing +receiver UID with private directory/files. The new directory's parent is fsynced +before any SQL intent/mutation, preventing a lost directory entry from erasing +the retained operation after power loss. Incomplete directories remain held. +This does not extend the existing SQLite-only backup archive to audit files; +include this location in the operating custody/offboarding inventory before +native operation is accepted. + +## Approved builder operating policy + +The user explicitly approved this policy in the thread. Resolved decision: +`112e14bb-4b5d-46d7-b365-84628f72f115`. Receipt: +`mail-return-builder-policy-approval-2026-11-01.json`. + +Platform-root remains accountable, with Bernd as human operator. Use explicit +selected reply/bounce handling. A provenance-reviewed permanent bounce may +suppress its correlated recipient across all callers/tenants; release is another +explicit reviewed action. Unmatched/temporary/ambiguous returns stay review items. +No automatic replies or blanket mailbox scan are introduced. + +Expire the operator's copied raw return evidence after 30 days. Retain minimal +decision/binding metadata while the builder service operates. Bernd retains +custody of the provider inbox during the builder phase and reviews its disposition +before production, together with decision-metadata expiry. This proposes a +builder-phase hold; it creates no automatic deletion of the shared provider +inbox or other readers' records. Policy agreement is complete. Actual inbox and +selected-return proof remain required before T02 can close. + +## Validation + +Fourteen adapter tests use modeled Kubernetes and actual SQLite child processes. +The fixture explicitly substitutes UID/paths and installed-module identity; its +SQL/core operation code is real and network operations are denied. Cases cover +native identity/PVC/source/policy drift, exact selected preparation and apply, +unrelated-table preservation, release, lost replies, receiver replacement, private +packets, reviewed source changes, SSH data/code separation and controlled raw +expiry including missing-proof refusal and interrupted-result reconciliation. +Twenty-eight core cases cover correlation, durable intent, real abrupt process +deaths, stopped-state reconciliation, release, privacy and retention. These are +source/assembly checks; the live observation proves only its read-only boundary. + +The full repository gate passes **744 tests**, including required established +browser journeys, with zero failures/errors/skips. Command: +`TMPDIR=/var/tmp make check`. The existing private-output guard requires a test +root outside the unrelated `/tmp/.git` artifact; the guard itself is unchanged. diff --git a/docs/release/native-mail-return-validation-2026-11-01.json b/docs/release/native-mail-return-validation-2026-11-01.json new file mode 100644 index 0000000..20ddc17 --- /dev/null +++ b/docs/release/native-mail-return-validation-2026-11-01.json @@ -0,0 +1,30 @@ +{ + "schema": "coulomb.native-mail-return-validation.v1", + "recorded_at": "2026-11-01T22:13:50.841490+00:00", + "source_sha256": { + "scripts/mail_return_operation.py": "0aa616e4a5c8271901d7ebd4436327ff2551637e74934171a838b728944ba6e2", + "scripts/mail_return_native.py": "56713fef4bfd494d6dee152c83a8b04ac03de563c88076f08c1a3477740238b3", + "tests/test_mail_return_operation.py": "4ce44a4710084857402fbc6178a026a43406e3d48d1fee54d34dd3554b40fe70", + "tests/test_mail_return_native.py": "77cfc9aa6b3e8309d2764a237796e20e29d16333475bb43353d18536dd1bbe96" + }, + "focused_tests": { + "native_assembly": 14, + "core": 28 + }, + "full_gate": { + "command": "TMPDIR=/var/tmp make check", + "tests": 744, + "failures": 0, + "errors": 0, + "skipped": 0, + "required_browser_journeys_included": true, + "junit_sha256": "1e1147576c391b631eef495cf577f823f7f9ca621b50317cc0e7263a6c93a3d3" + }, + "native_read_only_observation": "native-mail-return-observation-2026-11-01-r1.json", + "native_write_executed": false, + "credential_fetch_or_mailbox_read": false, + "mail_sent": false, + "policy_agreement": "pending_user_response", + "credential_admission": "approved_but_native_execution_unverified", + "task_status": "progress" +} diff --git a/docs/release/platform-mail-admission-review-2026-11-01.md b/docs/release/platform-mail-admission-review-2026-11-01.md new file mode 100644 index 0000000..db1dc65 --- /dev/null +++ b/docs/release/platform-mail-admission-review-2026-11-01.md @@ -0,0 +1,110 @@ +# Current platform SMTP admission + +T02 remains in progress. The builder-stage path reuses the existing controller and +sender executor, with one founder terminal session for the shared credential +reader. It introduces no additional mail service or automatic mailbox scanner. + +The October 31 execution instructions are superseded for a new attempt. Current +email-connect generation is 76, with the same deployment identity, sender, +image and caller policy. All five current server dry-runs pass. The dedicated +service account, SecretStore, ExternalSecret and target Secret are absent; +there is no foundation execution to resume. No credentials or message bodies +were read during this observation. + +The reviewed foundation is now: + +`sha256:d9f8785060db902f969e5d8d0917494858b2114e8f93ae812dda3a059c090844` + +It binds generation 76 and the current credential tooling. Its three resources +and credential scope are unchanged; ExternalSecret defaults remain pinned. +The sender adapter now expects this exact foundation and specification. The 27 owner admission/controller tests +pass. The complete email-connect suite passes 270 tests, with 21 existing optional +container-harness skips. The launcher has nineteen passing checks covering default +read-only behavior, exact consent, terminal requirements, drift, private receipts, +replay refusal, existing decisions, failed session revocation and the bounded +pre-controller login retry. The operator and launcher have 46 focused checks in total. + +## Current attended return + +Founder approval is now recorded in resolved decision +`66c13f9d-73b8-4976-8c44-65a9f5ee04da`; canonical CCR state is `approved`. +The first Warden attempt ended at 21:32:04Z with exit 10, callback_timeout, +client_exit 2, helper_present false. Warden reports session revocation unconfirmed; +this is preserved as a failed login, not a successful or verified revoked session. +No foundation journal exists. Independent native checks still find all targets +absent and the exact application fingerprint unchanged. + +The user observed platform-root login followed by a tegwick OTP prompt. A bounded +issuer-log query finds an openbao-admin MFA failure at 21:30:28Z. This suggests a +mixed browser identity session; it does not establish a callback transport defect. +A synthetic HTTP endpoint on 127.0.0.1:8250 was reachable from Windows using both +localhost and 127.0.0.1, with no login or credentials in that check. + +Use this reviewed retry after that specific pre-controller login failure: + +```bash +python3 /home/worsch/coulomb-social/scripts/admit-platform-smtp.py --retry-login +``` + +It preserves the first attempt, verifies its exact approval/decision, confirms +the controller never started and all native preconditions still match, then +creates the exclusive `login-retry-01` directory. It never asks for or writes +another approval. A scoped browser launcher accepts only the OpenBao administrator +KeyCape authorization request and approved loopback callback, preserves PKCE/state, +adds prompt=login and opens Edge InPrivate. Sign in as platform-root throughout; +stop if another account's OTP is requested. Warden's containment, output checks +and revocation remain unchanged. The retry's own outcome still needs verification. + +## One necessary human step + +The original command below records the first approval. That approval is now +complete; use `--retry-login` above for the outstanding identity act. + +Run in your workstation terminal: + +```bash +python3 /home/worsch/coulomb-social/scripts/admit-platform-smtp.py --execute +``` + +Review the displayed scope, type `ADMIT PLATFORM SMTP`, then complete the browser +OIDC/MFA login. The exact scope is: approve email-connect as a third reader of the +existing platform SMTP record, alongside telemetry and Authelia. OpenBao access +covers the whole exact record; ESO projects only SMTP_PASSWORD. The dedicated +namespace/service-account role has a 15-minute maximum token lifetime. The helper +preserves the mailbox and existing readers, and verifies SMTP AUTH without mail +submission. It grants no IMAP access, suppression caller or new message template. + +The helper records the attended platform/email-connect owner review, creates +or uses the open CCR founder decision, resolves it as the attending founder and +uses canonical decision sync. These actions run in the human's terminal only +after their explicit confirmation. The agent does not self-approve the CCR. +Warden then contains the administration session, executes the silent foundation +controller and verifies its own revocation/cleanup. The foundation repeats native +identity and admission checks before any change. + +Return only the final JSON. Receipts are private beneath +`~/.local/state/coulomb-social/smtp-/`. A pre-existing directory, +changed source/native state, failed Warden result or incomplete receipt stops the +procedure. Do not delete receipts or retry an uncertain attempt. Agent verification +must inspect the journal, Warden outcome and native projection before completing +the owner handoff. A foundation success does not switch the application sender. + +This human step is required by the existing credential workflow in +`railiance-platform/docs/credential-change-approval.md`: β€œThe **founder** resolves +the State Hub decision.” Warden's current plan independently returns +`founder_required`, with one `oidc_login` act, under organization posture `build`. +Builder mode permits simpler operation; it does not provide that human identity. + +## Remaining T02 acceptance + +After verified custody, seal the current sender proposal, use its existing +supervised transition and verify actual sender/ledger continuity. Then prove +selected delivered-message authentication and inbox/reply access. For builder +operation use explicit, selected return handling: correlate the accepted outbound +Message-ID and authoritative recipient/caller binding, review a permanent bounce, +and apply/release the existing global-recipient suppression with private evidence. +Unknown/temporary returns remain review items. No unsolicited mail, blanket scan +or automatic replies are authorized by the credential step. Agree and prove +bounded evidence expiry without removing pending challenges or suppressions. + +The original T02 requirements remain live; this packet is not closure evidence. diff --git a/docs/release/platform-mail-foundation-current-2026-11-01.json b/docs/release/platform-mail-foundation-current-2026-11-01.json new file mode 100644 index 0000000..f277dc5 --- /dev/null +++ b/docs/release/platform-mail-foundation-current-2026-11-01.json @@ -0,0 +1,100 @@ +{ + "proposal_digest": "sha256:d9f8785060db902f969e5d8d0917494858b2114e8f93ae812dda3a059c090844", + "ccr_status": "proposed", + "foundation_spec_sha256": "f5c897b8d7439131b4902f83198bd74a35bd6124bda21f427581833bd19dfa22", + "deployment_generation": 76, + "native_protected_fingerprint": "sha256:d220a6ccf651928b3e2ff14c7f95ed381937a26cd03129c8ef02fed934df663b", + "all_foundation_targets_absent": true, + "target_secret_metadata_absent": true, + "native_mutations": false, + "proposal": { + "schema": "platform.email-connect-smtp-foundation-proposal.v1", + "foundation": { + "schema": "platform.email-connect-smtp-foundation.v1", + "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", + "namespace_uid": "5678c5d7-e4f5-4a62-8f24-1c6e1d906319", + "deployment_uid": "2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72", + "deployment_generation": 76, + "resources": [ + { + "apiVersion": "v1", + "automountServiceAccountToken": false, + "kind": "ServiceAccount", + "metadata": { + "name": "email-connect-platform-smtp-eso", + "namespace": "email-connect" + } + }, + { + "apiVersion": "external-secrets.io/v1", + "kind": "SecretStore", + "metadata": { + "name": "openbao-email-connect-platform-smtp", + "namespace": "email-connect" + }, + "spec": { + "provider": { + "vault": { + "auth": { + "kubernetes": { + "mountPath": "kubernetes", + "role": "email-connect-platform-smtp-eso", + "serviceAccountRef": { + "audiences": [ + "openbao" + ], + "name": "email-connect-platform-smtp-eso" + } + } + }, + "path": "platform", + "server": "http://openbao.openbao.svc:8200", + "version": "v2" + } + } + } + }, + { + "apiVersion": "external-secrets.io/v1", + "kind": "ExternalSecret", + "metadata": { + "name": "email-connect-platform-smtp", + "namespace": "email-connect" + }, + "spec": { + "data": [ + { + "remoteRef": { + "conversionStrategy": "Default", + "decodingStrategy": "None", + "key": "workloads/railiance-telemetry/smtp", + "metadataPolicy": "None", + "property": "SMTP_PASSWORD" + }, + "secretKey": "password" + } + ], + "refreshInterval": "1h", + "secretStoreRef": { + "kind": "SecretStore", + "name": "openbao-email-connect-platform-smtp" + }, + "target": { + "creationPolicy": "Owner", + "deletionPolicy": "Retain", + "name": "email-connect-platform-smtp", + "template": { + "engineVersion": "v2", + "mergePolicy": "Replace", + "type": "Opaque" + } + } + } + } + ] + }, + "admission_digest": "sha256:e7d8a0bea1626427d2927d8eaa7b939cae80306a6f4cbea205b76c5e615910c8", + "kubeconfig": "/home/worsch/.kube/config-railiance01", + "controller_sha256": "4f1355c0cd48368fa1ae4dcb0138296b3880359329a4ace35dee51783b0ff13f" + } +} diff --git a/docs/release/platform-mail-sender-current-2026-11-01.json b/docs/release/platform-mail-sender-current-2026-11-01.json new file mode 100644 index 0000000..db76196 --- /dev/null +++ b/docs/release/platform-mail-sender-current-2026-11-01.json @@ -0,0 +1,231 @@ +{ + "schema": "coulomb.platform-mail-sender-candidate.v1", + "observed_at": "2026-11-01T21:13:23.358494+00:00", + "deployment_uid": "2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72", + "deployment_generation": 76, + "image": "forgejo.coulomb.social/coulomb/email-connect@sha256:5d79fd1fec15d3a279383820f170d263de3a55f5308df01a0a34732ebaea6d3d", + "new_resources": [ + { + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": "email-connect-platform-smtp-eso", + "namespace": "email-connect" + }, + "automountServiceAccountToken": false + }, + { + "apiVersion": "external-secrets.io/v1", + "kind": "SecretStore", + "metadata": { + "name": "openbao-email-connect-platform-smtp", + "namespace": "email-connect" + }, + "spec": { + "provider": { + "vault": { + "server": "http://openbao.openbao.svc:8200", + "path": "platform", + "version": "v2", + "auth": { + "kubernetes": { + "mountPath": "kubernetes", + "role": "email-connect-platform-smtp-eso", + "serviceAccountRef": { + "name": "email-connect-platform-smtp-eso", + "audiences": [ + "openbao" + ] + } + } + } + } + } + } + }, + { + "apiVersion": "external-secrets.io/v1", + "kind": "ExternalSecret", + "metadata": { + "name": "email-connect-platform-smtp", + "namespace": "email-connect" + }, + "spec": { + "refreshInterval": "1h", + "secretStoreRef": { + "kind": "SecretStore", + "name": "openbao-email-connect-platform-smtp" + }, + "target": { + "name": "email-connect-platform-smtp", + "creationPolicy": "Owner", + "deletionPolicy": "Retain", + "template": { + "type": "Opaque" + } + }, + "data": [ + { + "secretKey": "password", + "remoteRef": { + "key": "workloads/railiance-telemetry/smtp", + "property": "SMTP_PASSWORD" + } + } + ] + } + } + ], + "deployment_json_patch": [ + { + "op": "test", + "path": "/metadata/uid", + "value": "2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72" + }, + { + "op": "test", + "path": "/metadata/resourceVersion", + "value": "83485315" + }, + { + "op": "test", + "path": "/spec/template/spec/containers/0/image", + "value": "forgejo.coulomb.social/coulomb/email-connect@sha256:5d79fd1fec15d3a279383820f170d263de3a55f5308df01a0a34732ebaea6d3d" + }, + { + "op": "test", + "path": "/spec/template/spec/containers/0/envFrom", + "value": [ + { + "configMapRef": { + "name": "email-connect-config" + } + }, + { + "secretRef": { + "name": "email-connect-runtime", + "optional": false + } + } + ] + }, + { + "op": "test", + "path": "/spec/template/spec/containers/0/env", + "value": [ + { + "name": "EMAIL_CONNECT_CALLERS_FILE", + "value": "/etc/email-connect-callers/callers.json" + } + ] + }, + { + "op": "replace", + "path": "/spec/template/spec/containers/0/envFrom", + "value": [ + { + "configMapRef": { + "name": "email-connect-config" + } + } + ] + }, + { + "op": "replace", + "path": "/spec/template/spec/containers/0/env", + "value": [ + { + "name": "EMAIL_CONNECT_CALLERS_FILE", + "value": "/etc/email-connect-callers/callers.json" + }, + { + "name": "EMAIL_CONNECT_SMTP_USERNAME", + "value": "platform@coulomb.social" + }, + { + "name": "EMAIL_CONNECT_SMTP_PASSWORD", + "valueFrom": { + "secretKeyRef": { + "name": "email-connect-platform-smtp", + "key": "password", + "optional": false + } + } + } + ] + } + ], + "configmap_json_patch": [ + { + "op": "test", + "path": "/metadata/uid", + "value": "558d3338-c4c2-490c-a5e5-af206c39eb51" + }, + { + "op": "test", + "path": "/data/EMAIL_CONNECT_SENDER", + "value": "forgejo@coulomb.social" + }, + { + "op": "replace", + "path": "/data/EMAIL_CONNECT_SENDER", + "value": "platform@coulomb.social" + } + ], + "native_server_dry_run": [ + { + "kind": "ServiceAccount", + "name": "email-connect-platform-smtp-eso", + "accepted": true + }, + { + "kind": "SecretStore", + "name": "openbao-email-connect-platform-smtp", + "accepted": true + }, + { + "kind": "ExternalSecret", + "name": "email-connect-platform-smtp", + "accepted": true + }, + { + "kind": "Deployment", + "name": "email-connect", + "accepted": true, + "only_envFrom_and_env_changed": true + }, + { + "kind": "ConfigMap", + "name": "email-connect-config", + "accepted": true, + "only_sender_changed": true + } + ], + "live_caller_policy": { + "name": "email-connect-callers-user-engine-276257849bf2", + "sha256": "276257849bf260d844e8e0767deda7edd2f3d807f12794b869f871b75b255a07", + "callers": [ + { + "caller_id": "user-engine", + "operations": [ + "send", + "delivery-status", + "status", + "verify", + "consume", + "cancel" + ], + "templates": [ + "family_member.invited", + "family_invitation.resent" + ] + } + ] + }, + "platform_mailbox_admission_executed": false, + "resources_installed": false, + "mail_sent": false, + "secrets_read": false, + "execution_ready": false, + "preparer_sha256": "eebbeed10af5e2dd82691580078d74d4d3b44ddbd52f7b84035ffb14f8df1889" +} diff --git a/docs/release/platform-mail-t02-validation-2026-11-01.json b/docs/release/platform-mail-t02-validation-2026-11-01.json new file mode 100644 index 0000000..fdba730 --- /dev/null +++ b/docs/release/platform-mail-t02-validation-2026-11-01.json @@ -0,0 +1,47 @@ +{ + "schema": "coulomb.t02-preparation-validation.v1", + "recorded_at": "2026-11-01T21:54:48.733527+00:00", + "source_sha256": { + "scripts/admit-platform-smtp.py": "370d5cb4ef0bbce3c204d9947f71bb0e69767653d74ff6b8c644612e512090cf", + "scripts/smtp-private-browser/xdg-open": "2a32e3e062a06d99c51ec3c889349749c670a8d7840d5f4c4da17173b4fcfb76", + "scripts/mail_return_operation.py": "6f2dc3038b373cc11581bc8e5aa15e07757f2b262221c3917bb11a4f5be186c2", + "scripts/rehearse-mail-return-boundary.py": "7da072da173de79f52a351ee29238993d1f59975b2d0d62040147369085c7785", + "tests/test_smtp_admission_launcher.py": "52d32f7800d6e66bb2bc503149a55bb921a5f2f1041f47ed12a2b8fdf317d924", + "tests/test_mail_return_operation.py": "855e96fad67650f42ccf85d2e15ea80c84d62afa52ae385b2c533fca50458c33" + }, + "full_gate": { + "command": "TMPDIR=/var/tmp make check", + "tests": 729, + "failures": 0, + "errors": 0, + "skipped": 0, + "includes_required_browser_journeys": true, + "junit_sha256": "6a3c2a22178916417b615ae578f1da147f8bb5fbc3355c5a773513c659287509" + }, + "platform_owner_tests": { + "passed": 27 + }, + "email_connect_owner_tests": { + "passed": 270, + "existing_optional_harness_skips": 21 + }, + "founder_decision_id": "66c13f9d-73b8-4976-8c44-65a9f5ee04da", + "ccr_status": "approved", + "first_attended_result": { + "warden_exit_code": 10, + "category": "callback_timeout", + "helper_present": false, + "revocation_confirmed": false, + "controller_receipt_present": false + }, + "loopback_probe": { + "synthetic_http_only": true, + "windows_to_wsl_localhost_8250": true, + "windows_to_wsl_127_0_0_1_8250": true + }, + "native_admission_verified": false, + "sender_rollout_verified": false, + "mail_sent_by_agent": false, + "mailbox_read_by_agent": false, + "task_status": "progress" +} diff --git a/scripts/admit-platform-smtp.py b/scripts/admit-platform-smtp.py new file mode 100644 index 0000000..1969518 --- /dev/null +++ b/scripts/admit-platform-smtp.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python3 +"""One attended founder approval/login for the reviewed platform SMTP reader. + +Default is read-only. Does not switch the sender, read inboxes or submit mail. +""" + +from __future__ import annotations + +import argparse +import hashlib +import importlib +import json +import os +import subprocess +import sys +import urllib.request +from datetime import UTC, datetime +from pathlib import Path + +PLATFORM = Path("/home/worsch/railiance-platform") +EXPECTED = "sha256:d9f8785060db902f969e5d8d0917494858b2114e8f93ae812dda3a059c090844" +CCR = "CCR-2026-0056" +HUB = "http://127.0.0.1:8000" +PHRASE = "ADMIT PLATFORM SMTP" +SCOPE = ( + "As founder, platform owner and email-connect owner, admit email-connect as a third " + "reader of the existing platform SMTP record alongside telemetry and Authelia. " + "The new role reads that whole exact record; ESO projects only SMTP_PASSWORD. " + "Tokens last at most 15 minutes. Preserve the mailbox and existing readers. " + "This step creates the scoped projection and verifies SMTP AUTH without sending mail. " + "It grants no IMAP access, suppression caller or additional transactional template." +) + + +def require(condition, message): + if not condition: + raise RuntimeError(message) + + +def owner(): + sys.dont_write_bytecode = True + sys.path.insert(0, str(PLATFORM / "scripts")) + return importlib.import_module("email_connect_smtp_foundation") + + +def preflight(foundation, expected_status="proposed"): + ccr, proposal, value = foundation.prepare() + require(value == EXPECTED, "Proposal changed; refresh the review before execution") + require(ccr["status"] == expected_status, "Existing CCR state requires inspection") + native = foundation.Native() + bundle = proposal["foundation"] + for name, key in (("kube-system", "cluster_uid"), ("email-connect", "namespace_uid")): + obj = native.get("namespace", name, namespace=None) + require(obj and obj["metadata"]["uid"] == bundle[key], "Cluster identity changed") + before = foundation.protected_application(native, bundle) + for wanted in bundle["resources"]: + require( + native.get(wanted["kind"], wanted["metadata"]["name"]) is None, + "Existing target requires inspection", + ) + require( + native.target_metadata("email-connect-platform-smtp") is None, + "Existing target Secret requires inspection", + ) + return ccr, proposal, before + + +def write_private(path, value): + fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) + with os.fdopen(fd, "w") as stream: + json.dump(value, stream, indent=2) + stream.write("\n") + stream.flush() + os.fsync(stream.fileno()) + fd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY) + try: + os.fsync(fd) + finally: + os.close(fd) + + +def private_json(path): + info = path.lstat() + require( + path.is_file() + and info.st_uid == os.getuid() + and not info.st_mode & 0o077 + and not any(p.is_symlink() for p in (path, *path.parents)), + "Private original receipt required", + ) + return json.loads(path.read_text()) + + +def retry_login(foundation): + require(sys.stdin.isatty(), "Run this command in your own terminal") + original = Path.home() / ".local/state/coulomb-social" / ("smtp-" + EXPECTED[7:]) + approval = private_json(original / "approval.json") + outcome = private_json(original / "outcome.json") + require( + approval.get("proposal_digest") == outcome.get("proposal_digest") == EXPECTED + and approval.get("scope") == SCOPE + and outcome.get("warden_exit_code") == 10 + and not (original / "foundation.jsonl").exists(), + "Only a proven pre-controller login failure can use this retry", + ) + ccr, proposal, before = preflight(foundation, expected_status="approved") + require( + before == approval["protected_application"], "Application changed since admission review" + ) + decision_id = ccr.get("state_hub", {}).get("decision_id") + require(decision_id == outcome.get("decision_id"), "Original founder decision changed") + validator = foundation.admission.module( + "attended_retry_ccr", PLATFORM / "scripts/credential-change.py" + ) + decision = validator.state_hub_get_json(HUB, "/decisions/" + decision_id) + require( + decision.get("status") == "resolved" + and decision.get("decided_by") == "Bernd Worsch" + and decision.get("rationale", "").startswith("APPROVE:") + and EXPECTED in decision["rationale"], + "Original exact founder approval required", + ) + print("Reusing your recorded approval; completing only the outstanding private-browser login.") + print("Sign in as platform-root throughout. Stop if another account's OTP is requested.") + directory = original / "login-retry-01" + directory.mkdir(mode=0o700) + intent = { + **approval, + "retry_of": str(original / "outcome.json"), + "retry_started_at": datetime.now(UTC).isoformat(), + "launcher_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), + "foundation_receipt": str(directory / "foundation.jsonl"), + } + write_private(directory / "approval.json", intent) + return run_controller( + foundation, proposal, before, directory, intent, decision_id, private_browser=True + ) + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + actions = parser.add_mutually_exclusive_group() + actions.add_argument("--execute", action="store_true") + actions.add_argument("--retry-login", action="store_true") + args = parser.parse_args(argv) + require(__debug__, "Optimized Python is unsupported") + foundation = owner() + if args.retry_login: + return retry_login(foundation) + ccr, proposal, before = preflight(foundation) + print(SCOPE) + print("Reviewed proposal:", EXPECTED) + if not args.execute: + print("Read-only preflight passed; no approval or runtime changes made.") + return 0 + require(sys.stdin.isatty(), "Run this command in your own terminal") + require( + input(f"Type {PHRASE} to approve this exact scope: ").strip() == PHRASE, + "Approval not given", + ) + # Recheck after human review; do not approve or write a journal for stale scope. + ccr, proposal, second = preflight(foundation) + require(second == before, "Application changed during review") + directory = Path.home() / ".local/state/coulomb-social" / ("smtp-" + EXPECTED[7:]) + directory.parent.mkdir(mode=0o700, parents=True, exist_ok=True) + info = directory.parent.lstat() + require( + info.st_uid == os.getuid() + and not info.st_mode & 0o077 + and not any(p.is_symlink() for p in (directory.parent, *directory.parent.parents)), + "Private caller-owned state directory required", + ) + directory.mkdir(mode=0o700) # Exclusive: a lost response requires inspection. + receipt = directory / "foundation.jsonl" + intent = { + "schema": "coulomb.attended-smtp-admission.v1", + "proposal_digest": EXPECTED, + "approved_at": datetime.now(UTC).isoformat(), + "approved_by": "Bernd Worsch (attended founder/platform/email-connect owner)", + "scope": SCOPE, + "protected_application": before, + "launcher_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), + "foundation_receipt": str(receipt), + } + write_private(directory / "approval.json", intent) + validator = foundation.admission.module( + "attended_ccr", PLATFORM / "scripts/credential-change.py" + ) + intake = foundation.admission.module("attended_intake", PLATFORM / "scripts/ccr_intake.py") + # This runs in the founder's terminal, after their explicit review. The agent + # never resolves this decision or impersonates an owner approval. + ccr["review"]["comments"].append( + { + "at": intent["approved_at"], + "reviewer": "railiance-platform-owner (Bernd Worsch)", + "decision": "approved", + "comment": SCOPE + " Exact proposal " + EXPECTED, + } + ) + validator.dump_yaml(foundation.admission.CCR, ccr) + decision_id = (ccr.get("state_hub") or {}).get("decision_id") + if not decision_id: + decision_id = intake.request_decision(foundation.admission.CCR, validator, HUB)["id"] + else: + decision = validator.state_hub_get_json(HUB, "/decisions/" + decision_id) + require(decision["status"] == "open", "Existing founder decision requires inspection") + rationale = ( + f"APPROVE: {CCR}; exact proposal {EXPECTED}; {SCOPE} " + "Approval entered by the founder in the attended terminal." + ) + request = urllib.request.Request( + HUB + "/decisions/" + decision_id + "/resolve", + data=json.dumps({"rationale": rationale, "decided_by": "Bernd Worsch"}).encode(), + headers={"Content-Type": "application/json"}, + method="POST", + ) + with urllib.request.urlopen(request, timeout=20) as response: + require(json.load(response)["status"] == "resolved", "Founder decision unconfirmed") + validator.sync_state_hub_decision(foundation.admission.CCR, HUB) + return run_controller(foundation, proposal, before, directory, intent, decision_id) + + +def run_controller( + foundation, proposal, before, directory, intent, decision_id, *, private_browser=False +): + receipt = directory / "foundation.jsonl" + env = { + **os.environ, + "BAO_ADDR": "http://127.0.0.1:18200", + "VAULT_ADDR": "http://127.0.0.1:18200", + "PYTHONDONTWRITEBYTECODE": "1", + } + if private_browser: + env["PATH"] = str(Path(__file__).resolve().parent / "smtp-private-browser") + ( + os.pathsep + env.get("PATH", "") + ) + result = subprocess.run( + [ + "warden", + "access", + "openbao-platform-admin-login", + "--exec", + "--", + "python3", + str(PLATFORM / "scripts/email_connect_smtp_foundation.py"), + "--execute", + "--expect-digest", + EXPECTED, + "--receipt", + str(receipt), + ], + env=env, + check=False, + ) + outcome = { + **intent, + "warden_exit_code": result.returncode, + "decision_id": decision_id, + "finished_at": datetime.now(UTC).isoformat(), + } + write_private(directory / "outcome.json", outcome) + require(result.returncode == 0, "Retain receipts; Warden outcome requires inspection") + data = receipt.read_bytes() + require(data.endswith(b"\n"), "Incomplete foundation receipt requires inspection") + final = json.loads(data.splitlines()[-1]) + require( + final["status"] == "foundation_admitted_projection_ready" + and final["proposal_digest"] == EXPECTED, + "Foundation result requires inspection", + ) + require( + foundation.protected_application(foundation.Native(), proposal["foundation"]) == before, + "Application changed; inspect before the sender step", + ) + print( + json.dumps( + { + "status": "attended_foundation_completed", + "proposal_digest": EXPECTED, + "receipt": str(receipt), + "outcome": str(directory / "outcome.json"), + "warden_exit_code": 0, + "sender_changed": False, + "mail_sent": False, + } + ) + ) + return 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except (RuntimeError, OSError, ValueError, KeyError) as error: + print( + f"Stopped: {error}. Preserve existing receipts; do not retry an attempt blindly.", + file=sys.stderr, + ) + sys.exit(1) diff --git a/scripts/mail_return_native.py b/scripts/mail_return_native.py new file mode 100644 index 0000000..5b3d261 --- /dev/null +++ b/scripts/mail_return_native.py @@ -0,0 +1,465 @@ +#!/usr/bin/env python3 +"""Fixed native transport for the selected-return operator; observe by default. + +No mailbox read, SMTP send, credential fetch or Kubernetes resource mutation. +Explicit apply/reconcile touches only the reviewed private audit and suppression. +""" + +from __future__ import annotations + +import argparse +import base64 +import hashlib +import json +import re +import shlex +import subprocess +import sys +from datetime import UTC, datetime +from pathlib import Path + +import mail_return_operation as operation + +ROOT = Path(__file__).resolve().parent +IMAGE = ( + "forgejo.coulomb.social/coulomb/email-connect@sha256:" + "5d79fd1fec15d3a279383820f170d263de3a55f5308df01a0a34732ebaea6d3d" +) +CLUSTER = "a553c742-0115-43d4-99a4-a5ca56fe0786" +NAMESPACE = "5678c5d7-e4f5-4a62-8f24-1c6e1d906319" +DEPLOYMENT = "2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72" +TRANSACTIONAL = "36fdfd8cb58f10148cd491a7e34d7aea798e4289fce760af70a48776bede204d" +CALLERS = "06fe961b9030dba2c0b26b92f8097e8fbd24e205d5fa79642aa9af60382886dc" +SCHEMA = "coulomb.native-mail-return.v1" +FILES = ("mail_return_native.py", "mail_return_operation.py") +require = operation.require + +BOOTSTRAP = r""" +import hashlib,json,os,sys,tempfile +from pathlib import Path +sys.dont_write_bytecode=True +raw=sys.stdin.buffer.read(4*1024**2+1) +assert __debug__ and len(raw)<=4*1024**2 +r=json.loads(raw) +assert set(r['sources'])=={'mail_return_native.py','mail_return_operation.py'} +assert sum(len(s.encode()) for s in r['sources'].values())<=128*1024 +hashes={n:hashlib.sha256(v.encode()).hexdigest() for n,v in r['sources'].items()} +assert hashes==r['source_sha256'] +if r.get('reviewed_source_sha256') is not None:assert hashes==r['reviewed_source_sha256'] +with tempfile.TemporaryDirectory(prefix='mail-return-source-') as directory: + for name,source in r['sources'].items(): + fd=os.open(Path(directory)/name,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600) + with os.fdopen(fd,'w') as stream:stream.write(source) + sys.path.insert(0,directory) + import mail_return_native as native + try:print(json.dumps(native.run_node(r))) + except Exception as error: + print(json.dumps({'status':'native_return_requires_inspection','failure_type':type(error).__name__, + 'retry_automatically':False})) + sys.exit(1) +""" + +POD_PROGRAM = r""" +import base64,hashlib,json,os,sys,types +from pathlib import Path +from datetime import datetime +sys.dont_write_bytecode=True +from email_connect import transactional,callers +r=json.load(sys.stdin) +assert __debug__ and os.getuid()==10001 +actual=hashlib.sha256(Path(transactional.__file__).read_bytes()).hexdigest() +assert actual==r['transactional_sha256'] +assert hashlib.sha256(Path(callers.__file__).read_bytes()).hexdigest()==r['callers_sha256'] +assert os.environ.get('EMAIL_CONNECT_CALLERS_FILE')=='/etc/email-connect-callers/callers.json' +database=os.environ.get('EMAIL_CONNECT_DATABASE_PATH','/data/email-connect.db') +assert database=='/data/email-connect.db' +p=Path('/etc/email-connect-callers/callers.json') +assert p.stat().st_size<=64*1024 +policy=json.loads(p.read_text()) +m=types.ModuleType('selected_mail_return') +exec(compile(r['core_source'],'','exec'),m.__dict__) +context={'caller_policy_sha256':m.digest(policy)} +if r.get('policy_sha256') is not None:assert context['caller_policy_sha256']==r['policy_sha256'] +ledger=Path('/data/email-connect.db') +audit=Path('/data/mail-returns') +action=r['action'] +if action=='observe': + with m.closing(m.connect(ledger)) as db: + db.execute('BEGIN') + tables={row[0] for row in db.execute("SELECT name FROM sqlite_master WHERE type='table'")} + assert tables=={'deliveries','verifications','suppressions','send_attempts', + 'verification_attempts','caller_buckets'} + size=db.execute('PRAGMA page_count').fetchone()[0]*db.execute('PRAGMA page_size').fetchone()[0] + assert 0= 2, "recipient_report_required") + for block in blocks: + require(not block.defects, "defective_report") + for reference in block.get_all("Original-Message-ID", []): + references.add(str(reference).strip()) + if block.get("Final-Recipient") is None: + continue + require( + all(len(block.get_all(k, [])) == 1 for k in ("Final-Recipient", "Action", "Status")), + "ambiguous_report", + ) + kind, separator, address = str(block["Final-Recipient"]).partition(";") + require(separator and kind.strip().lower() == "rfc822", "recipient_type_unsupported") + failures.append( + ( + address.strip().lower(), + str(block["Action"]).strip().lower(), + str(block["Status"]).strip(), + ) + ) + # One selected recipient prevents applying a whole multi-recipient report. + require(len(failures) == 1, "single_recipient_report_required") + address, action, status = failures[0] + require( + address == recipient.lower() + and action == "failed" + and re.fullmatch(r"5\.\d{1,3}\.\d{1,3}", status), + "permanent_recipient_failure_required", + ) + for part in message.walk(): + if part.get_content_type() in {"message/rfc822", "text/rfc822-headers"}: + attached = part.get_payload() + if isinstance(attached, list): + for original in attached: + references.update(str(v).strip() for v in original.get_all("Message-ID", [])) + elif isinstance(attached, str): + original = BytesParser(policy=policy.default).parsebytes(attached.encode()) + references.update(str(v).strip() for v in original.get_all("Message-ID", [])) + require(len(references) == 1, "unambiguous_original_message_id_required") + reference = references.pop() + require(re.fullmatch(r"<[^<>\s]{1,250}>", reference), "original_message_id_invalid") + return reference + + +def selected_policy(document, caller_id): + require(document.get("schema_version") == 1, "policy_schema_changed") + callers = [p for p in document["callers"] if p.get("caller_id") == caller_id] + require(len(callers) == 1 and caller_id == "user-engine", "reviewed_caller_required") + return callers[0] + + +def binding_allowed(caller, tenant, client): + require( + all( + isinstance(v, str) and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,127}", v) + for v in (tenant, client) + ), + "invalid_binding", + ) + return [tenant, client] in caller["bindings"] or any( + fnmatch.fnmatchcase(tenant, t) and fnmatch.fnmatchcase(client, c) + for t, c in caller.get("binding_patterns", []) + ) + + +def correlate(db, reference, recipient, caller_id, document, invitation_payload=None): + caller = selected_policy(document, caller_id) + # The stored SMTP Message-ID, not the caller-facing delivery reference, is + # authoritative. Reject duplicate references across either send mechanism. + verification = db.execute( + "SELECT v.request_id,v.email,v.tenant,v.client_id,v.caller_id FROM " + "verification_attempts a JOIN verifications v ON a.request_id=v.request_id " + "WHERE a.provider_ref=? AND a.state='accepted'", + (reference,), + ).fetchall() + invitation = db.execute( + "SELECT d.event_id,a.fingerprint FROM deliveries d JOIN send_attempts a " + "ON a.event_id=d.event_id WHERE d.provider_ref=?", + (reference,), + ).fetchall() + require(len(verification) + len(invitation) == 1, "one_accepted_outbound_required") + if verification: + request_id, email, tenant, client, recorded_caller = verification[0] + require( + email.lower() == recipient.lower() + and ( + recorded_caller == caller_id + or (recorded_caller == "" and caller.get("adopt_legacy_verifications") is True) + ), + "recipient_or_caller_mismatch", + ) + require(binding_allowed(caller, tenant, client), "caller_binding_denied") + return { + "kind": "verification", + "id": request_id, + "caller_id": caller_id, + "tenant": tenant, + "client_id": client, + "recipient": email.lower(), + "provider_ref": reference, + } + # Invitation rows retain a fingerprint, not plaintext recipient/binding. + # Require the original caller payload and reproduce the admitted mapping. + require(isinstance(invitation_payload, dict), "original_invitation_payload_required") + payload = dict(invitation_payload) + tenant, client = payload.get("tenant"), payload.get("client_id") + if (tenant, client) == (None, None) and caller.get("legacy_invitation_binding"): + tenant, client = caller["legacy_invitation_binding"] + require(binding_allowed(caller, tenant, client), "caller_binding_denied") + require( + payload.get("source") == caller["source"] + and payload.get("type") in caller["templates"] + and payload.get("data", {}).get("primary_email", "").lower() == recipient.lower(), + "invitation_scope_mismatch", + ) + event = payload.get("id") + require( + isinstance(event, str) + and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,127}", event) + and not event.startswith("ec2_"), + "invalid_event_id", + ) + key = ( + event + if (caller.get("adopt_legacy_invitations") or caller.get("legacy_invitation_binding")) + else ("ec2_" + hashlib.sha256(json.dumps([caller_id, event]).encode()).hexdigest()) + ) + payload.update(id=key, source="user-engine") + candidates = [digest(payload)] + if caller.get("adopt_legacy_invitations"): + legacy = dict(payload) + legacy.pop("client_id", None) + candidates.append(digest(legacy)) + require( + invitation[0][0] == key and invitation[0][1] in candidates, + "invitation_fingerprint_mismatch", + ) + return { + "kind": "invitation", + "id": key, + "caller_id": caller_id, + "tenant": tenant, + "client_id": client, + "recipient": recipient.lower(), + "provider_ref": reference, + } + + +def connect(path, *, writable=False): + path = Path(path).absolute() + require( + path.is_file() and not any(p.is_symlink() for p in (path, *path.parents)), + "real_existing_ledger_required", + ) + db = sqlite3.connect( + path.as_uri() + ("?mode=rw" if writable else "?mode=ro"), uri=True, timeout=10 + ) + db.execute("PRAGMA trusted_schema=OFF") + if not writable: + db.execute("PRAGMA query_only=ON") + return db + + +def suppression(db, recipient): + row = db.execute( + "SELECT email,reason,created_at FROM suppressions WHERE email=?", (recipient,) + ).fetchone() + return list(row) if row else None + + +def prepare(path, raw, recipient, document, trust_note, *, invitation_payload=None, now=None): + now = now or datetime.now(UTC) + require( + isinstance(trust_note, str) and 10 <= len(trust_note.strip()) <= 500, + "explicit_operator_provenance_review_required", + ) + reference = permanent_dsn(raw, recipient) + with closing(connect(path)) as db: + db.execute("BEGIN") + bound = correlate(db, reference, recipient, "user-engine", document, invitation_payload) + before = suppression(db, bound["recipient"]) + require(before is None, "existing_suppression_requires_review") + return { + "schema": SCHEMA, + "action": "suppress", + "created_at": timestamp(now), + "binding": bound, + "policy_sha256": digest(document), + "evidence_sha256": hashlib.sha256(raw).hexdigest(), + "operator_review": trust_note, + "invitation_payload": invitation_payload, + "before": before, + "after": [bound["recipient"], "hard_bounce", timestamp(now)], + "scope": "recipient-global across callers and tenants", + } + + +def private_directory(path): + path = Path(path) + require( + path.is_absolute() + and path.is_dir() + and not any(p.is_symlink() for p in (path, *path.parents)), + "private_directory_required", + ) + info = path.stat() + require(info.st_uid == os.getuid() and not info.st_mode & 0o077, "private_directory_required") + + +def sync_directory(path): + fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY) + try: + os.fsync(fd) + finally: + os.close(fd) + + +def durable(path, raw): + fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) + with os.fdopen(fd, "wb") as stream: + stream.write(raw) + stream.flush() + os.fsync(stream.fileno()) + sync_directory(path.parent) + + +def read_json(path): + info = path.lstat() + require( + not path.is_symlink() + and path.is_file() + and info.st_uid == os.getuid() + and not info.st_mode & 0o077, + "private_regular_receipt_required", + ) + require(info.st_size <= MAX_MESSAGE, "receipt_size_outside_contract") + return json.loads(path.read_text()) + + +def completed_operation(root, value, status): + require(re.fullmatch(r"[a-f0-9]{64}", value), "operation_id_invalid") + directory = Path(root) / value + private_directory(directory) + plan = read_json(directory / "plan.json") + require(digest(plan) == value, "retained_plan_changed") + completed = read_json(directory / "completed.json") + require( + completed.get("plan_sha256") == value and completed.get("status") == status, + "completion_receipt_changed", + ) + return plan + + +def prepare_release(path, audit_root, source, review, *, now=None): + require( + isinstance(review, str) and 10 <= len(review.strip()) <= 500, + "explicit_release_review_required", + ) + private_directory(audit_root) + original = completed_operation(audit_root, source, "suppression_applied") + require( + original.get("schema") == SCHEMA and original.get("action") == "suppress", + "original_suppression_required", + ) + with closing(connect(path)) as db: + require( + suppression(db, original["binding"]["recipient"]) == original["after"], + "suppression_changed", + ) + return { + "schema": SCHEMA, + "action": "release", + "created_at": timestamp(now or datetime.now(UTC)), + "source_plan_sha256": source, + "binding": original["binding"], + "operator_review": review, + "policy_sha256": original["policy_sha256"], + "before": original["after"], + "after": None, + "scope": original["scope"], + } + + +@contextmanager +def exclusive(directory): + private_directory(directory) + fd = os.open(directory / "operation.lock", os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW, 0o600) + try: + try: + fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + raise Refused("operation_still_running") from None + yield + finally: + os.close(fd) + + +def apply(path, plan, raw, document, expected, audit_root): + require( + plan.get("schema") == SCHEMA + and plan.get("action") in {"suppress", "release"} + and expected == digest(plan), + "reviewed_plan_required", + ) + 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", + ) + else: + original = completed_operation( + audit_root, plan["source_plan_sha256"], "suppression_applied" + ) + require( + plan["binding"] == original["binding"] + and plan["before"] == original["after"] + and plan["after"] is None, + "release_delta_invalid", + ) + directory = Path(audit_root) / expected + directory.mkdir(mode=0o700) # A prior intent must be inspected, never replayed. + sync_directory(audit_root) + with exclusive(directory): + return commit(path, plan, raw, document, expected, directory) + + +def commit(path, plan, raw, document, expected, directory): + durable(directory / "plan.json", canonical(plan) + b"\n") + if plan["action"] == "suppress": + durable(directory / "message.eml", raw) + with closing(connect(path, writable=True)) as db: + try: + db.execute("BEGIN IMMEDIATE") + recipient = plan["binding"]["recipient"] + if plan["action"] == "suppress": + 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 + and plan["after"] == [recipient, "hard_bounce", plan["created_at"]], + "suppression_delta_invalid", + ) + require(suppression(db, recipient) == plan["before"], "suppression_changed") + durable( + directory / "intent.json", + canonical( + { + "plan_sha256": expected, + "status": "commit_unconfirmed", + "started_at": timestamp(datetime.now(UTC)), + } + ), + ) + if plan["action"] == "suppress": + db.execute( + "INSERT INTO suppressions (email,reason,created_at) VALUES (?,?,?)", + plan["after"], + ) + else: + db.execute("DELETE FROM suppressions WHERE email=?", (recipient,)) + db.commit() + except BaseException: + db.rollback() + raise + status = "suppression_applied" if plan["action"] == "suppress" else "suppression_released" + durable( + directory / "completed.json", + canonical( + { + "plan_sha256": expected, + "status": status, + "completed_at": timestamp(datetime.now(UTC)), + } + ), + ) + return {"status": status, "plan_sha256": expected, "scope": plan["scope"], "mail_sent": False} + + +def inspect(path, directory): + private_directory(directory) + plan = read_json(Path(directory) / "plan.json") + require(digest(plan) == Path(directory).name, "retained_plan_changed") + with closing(connect(path)) as db: + current = suppression(db, plan["binding"]["recipient"]) + state = ( + "planned_state_present" + if current == plan["after"] + else "original_state_present" + if current == plan["before"] + else "later_state_requires_review" + ) + return { + "plan_sha256": digest(plan), + "current_state": state, + "commit_receipt_present": (Path(directory) / "completed.json").is_file(), + "retry_automatically": False, + } + + +def reconcile(path, directory, expected): + """Verify a stopped attempt's actual state; never repeat its SQL mutation.""" + directory = Path(directory) + require(directory.name == expected, "reviewed_plan_required") + with exclusive(directory), closing(connect(path, writable=True)) as db: + plan = read_json(directory / "plan.json") + require(digest(plan) == expected, "retained_plan_changed") + require(not (directory / "completed.json").exists(), "already_completed") + db.execute("BEGIN IMMEDIATE") + try: + current = suppression(db, plan["binding"]["recipient"]) + if current == plan["after"]: + intent = read_json(directory / "intent.json") + require(intent.get("plan_sha256") == expected, "commit_intent_required") + status = ( + "suppression_applied" + if plan["action"] == "suppress" + else "suppression_released" + ) + elif current == plan["before"]: + status = "operation_not_applied" + else: + raise Refused("later_state_requires_review") + completed = { + "plan_sha256": expected, + "status": status, + "completed_at": timestamp(datetime.now(UTC)), + "reconciled_state_verified": True, + "sql_mutated": False, + } + durable(directory / "completed.json", canonical(completed)) + return completed + finally: + db.rollback() + + +def expire_raw(audit_root, *, now, days=30, execute=True): + """Expire only copied raw evidence; never SQL, decision metadata or source mail. + + Active suppressions retain their binding/review records. Their release is a + separate explicit operator decision. Ambiguous/incomplete operations stay held. + """ + require(days == 30, "reviewed_retention_required") + timestamp(now) + private_directory(audit_root) + cutoff = now - timedelta(days=days) + expired = [] + for directory in sorted(Path(audit_root).iterdir()): + private_directory(directory) + require(re.fullmatch(r"[a-f0-9]{64}", directory.name), "unknown_audit_entry") + if not (directory / "plan.json").exists(): + # A stopped attempt can die after mkdir and before its first plan. + # Hold that directory; it establishes no authority to remove files. + continue + plan = read_json(directory / "plan.json") + require(digest(plan) == directory.name, "retained_plan_changed") + raw = directory / "message.eml" + if not (directory / "completed.json").is_file() or not raw.exists(): + continue + completed = read_json(directory / "completed.json") + require( + completed.get("plan_sha256") == directory.name + and completed.get("status") in {"suppression_applied", "operation_not_applied"}, + "completion_receipt_changed", + ) + if datetime.fromisoformat(completed["completed_at"]) > cutoff: + continue + require(not raw.is_symlink() and raw.is_file(), "real_evidence_copy_required") + require( + hashlib.sha256(raw.read_bytes()).hexdigest() == plan["evidence_sha256"], + "retained_evidence_changed", + ) + if not execute: + expired.append(directory.name) + continue + # Durable intent precedes unlink; interruption is reconciled from that + # intent and file presence, without touching the original mailbox/file. + intent = directory / "expiry.json" + if not intent.exists(): + durable( + intent, + canonical( + { + "raw_sha256": plan["evidence_sha256"], + "expired_at": timestamp(now), + "retention_days": days, + } + ), + ) + else: + require( + read_json(intent)["raw_sha256"] == plan["evidence_sha256"], "expiry_intent_changed" + ) + raw.unlink() + fd = os.open(directory, os.O_RDONLY | os.O_DIRECTORY) + try: + os.fsync(fd) + finally: + os.close(fd) + expired.append(directory.name) + return { + "expired_raw_copies": expired, + "ledger_modified": False, + "decision_metadata_retained": True, + "source_mail_modified": False, + } + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "action", choices=("prepare", "release", "apply", "inspect", "reconcile", "expire-raw") + ) + parser.add_argument("--ledger", type=Path) + parser.add_argument("--caller-policy", type=Path) + parser.add_argument("--message", type=Path) + parser.add_argument("--recipient") + parser.add_argument("--review") + parser.add_argument("--invitation-payload", type=Path) + parser.add_argument("--packet", type=Path) + parser.add_argument("--audit-root", type=Path) + parser.add_argument("--operation") + parser.add_argument("--expect-digest") + parser.add_argument("--execute", action="store_true", help="Execute the reviewed raw expiry") + parser.add_argument("--as-of", help="Use the timestamp returned by an expiry preview") + args = parser.parse_args(argv) + require(__debug__, "optimized_python_refused") + if args.action == "expire-raw": + require(args.audit_root is not None, "audit_root_required") + now = datetime.fromisoformat(args.as_of) if args.as_of else datetime.now(UTC) + require( + now.tzinfo is not None and now <= datetime.now(UTC), "observed_expiry_time_required" + ) + preview = expire_raw(args.audit_root, now=now, execute=False) + expiry_digest = digest({"as_of": timestamp(now), "copies": preview["expired_raw_copies"]}) + if args.execute: + require(args.expect_digest == expiry_digest, "reviewed_expiry_required") + preview = expire_raw(args.audit_root, now=now) + print( + json.dumps( + { + **preview, + "as_of": timestamp(now), + "expiry_digest": expiry_digest, + "executed": args.execute, + } + ) + ) + return 0 + require(args.ledger is not None, "ledger_required") + if args.action in {"prepare", "release"}: + require(args.packet is not None, "private_new_packet_required") + private_directory(args.packet.parent) + raw = None + if args.action == "prepare": + require( + args.message is not None + and args.caller_policy is not None + and args.recipient is not None, + "selected_message_recipient_and_policy_required", + ) + require( + args.message.is_file() + and not args.message.is_symlink() + and args.message.stat().st_size <= MAX_MESSAGE, + "bounded_selected_file_required", + ) + raw = args.message.read_bytes() + document = json.loads(args.caller_policy.read_text()) + invitation = ( + json.loads(args.invitation_payload.read_text()) + if (args.invitation_payload is not None) + else None + ) + plan = prepare( + args.ledger, + raw, + args.recipient, + document, + args.review, + invitation_payload=invitation, + ) + else: + require( + args.audit_root is not None and args.operation is not None, + "source_suppression_operation_required", + ) + plan = prepare_release(args.ledger, args.audit_root, args.operation, args.review) + packet = { + "plan": plan, + "plan_sha256": digest(plan), + "source_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), + } + durable(args.packet, canonical(packet) + b"\n") + print( + json.dumps( + { + "status": "return_operation_prepared", + "plan_sha256": digest(plan), + "scope": plan["scope"], + "ledger_modified": False, + } + ) + ) + return 0 + require(args.audit_root is not None, "audit_root_required") + private_directory(args.audit_root) + if args.action in {"inspect", "reconcile"}: + require( + args.operation is not None and re.fullmatch(r"[a-f0-9]{64}", args.operation), + "operation_id_invalid", + ) + directory = args.audit_root / args.operation + if args.action == "inspect": + result = inspect(args.ledger, directory) + else: + require(args.expect_digest == args.operation, "reviewed_plan_required") + result = reconcile(args.ledger, directory, args.expect_digest) + else: + require(args.packet is not None, "private_packet_required") + private_directory(args.packet.parent) + packet = read_json(args.packet) + require( + packet["source_sha256"] == hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), + "reviewed_source_changed", + ) + require(packet["plan_sha256"] == args.expect_digest, "reviewed_plan_required") + plan = packet["plan"] + raw = None + if plan["action"] == "suppress": + require( + args.message is not None + and args.message.is_file() + and not args.message.is_symlink() + and args.message.stat().st_size <= MAX_MESSAGE, + "bounded_selected_file_required", + ) + raw = args.message.read_bytes() + require( + plan["action"] == "release" or args.caller_policy is not None, "caller_policy_required" + ) + document = json.loads(args.caller_policy.read_text()) if args.caller_policy else None + result = apply(args.ledger, plan, raw, document, args.expect_digest, args.audit_root) + print(json.dumps(result)) + return 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except Exception as error: + # Do not echo MIME, recipient values, payloads or SQL errors into logs. + reason = str(error) if isinstance(error, Refused) else type(error).__name__ + print( + json.dumps( + { + "status": "return_operation_requires_inspection", + "reason": reason, + "retry_automatically": False, + } + ) + ) + sys.exit(1) diff --git a/scripts/rehearse-mail-return-boundary.py b/scripts/rehearse-mail-return-boundary.py index 237279a..6d0cb63 100644 --- a/scripts/rehearse-mail-return-boundary.py +++ b/scripts/rehearse-mail-return-boundary.py @@ -17,6 +17,8 @@ from datetime import UTC, datetime from pathlib import Path from unittest.mock import patch +import mail_return_operation as return_operation + def main(): if not __debug__: @@ -90,7 +92,7 @@ def main(): max_payload_bytes=8192, ) - sender = policy("fixture-sender", {"send", "delivery-status"}) + sender = policy("user-engine", {"send", "delivery-status"}) operator = policy("fixture-operator", {"suppress", "unsuppress"}) other = policy("fixture-other", {"send"}, "tenant:other") @@ -191,6 +193,86 @@ def main(): assert invoke(app, payload, "/v1/suppressions/remove", operator)[0] == 200 assert not ledger.is_suppressed("recipient@example.test") assert len(captured) == 1 + # Exercise the actual selected-return operator against the real owner's + # ledger and scoped send path, without admitting an HTTP operator caller. + strict_dsn = ( + "MIME-Version: 1.0\r\nContent-Type: multipart/report; " + 'report-type=delivery-status; boundary="dsn"\r\n\r\n' + "--dsn\r\nContent-Type: text/plain\r\n\r\nFailure.\r\n" + "--dsn\r\nContent-Type: message/delivery-status\r\n\r\n" + "Reporting-MTA: dns; fixture.example.test\r\n" + f"Original-Message-ID: {reference}\r\n\r\n" + "Final-Recipient: rfc822; recipient@example.test\r\n" + "Action: failed\r\nStatus: 5.1.1\r\n\r\n--dsn--\r\n" + ).encode() + document = { + "schema_version": 1, + "callers": [ + { + "caller_id": sender.caller_id, + "source": sender.source, + "bindings": [list(v) for v in sender.bindings], + "templates": sorted(sender.templates), + } + ], + } + audit = root / "selected-return-audit" + audit.mkdir(mode=0o700) + plan = return_operation.prepare( + root / "transactional.sqlite", + strict_dsn, + "recipient@example.test", + document, + "Synthetic operator verified this controlled return", + invitation_payload=event("invitation-1"), + ) + + def table_rows(): + return { + table: ledger.db.execute('SELECT * FROM "' + table + '" ORDER BY 1').fetchall() + for (table,) in ledger.db.execute( + "SELECT name FROM sqlite_master WHERE type='table'" + ) + } + + before = table_rows() + return_operation.apply( + root / "transactional.sqlite", + plan, + strict_dsn, + document, + return_operation.digest(plan), + audit, + ) + after = table_rows() + assert {k: v for k, v in before.items() if k != "suppressions"} == { + k: v for k, v in after.items() if k != "suppressions" + } + assert invoke(app, event("operator-blocked")) == (400, {"error": "recipient_suppressed"}) + assert invoke(app, event("operator-other", other, "tenant:other"), caller=other) == ( + 400, + {"error": "recipient_suppressed"}, + ) + release = return_operation.prepare_release( + root / "transactional.sqlite", + audit, + return_operation.digest(plan), + "Synthetic operator verified the corrected recipient", + ) + before = table_rows() + return_operation.apply( + root / "transactional.sqlite", + release, + None, + None, + return_operation.digest(release), + audit, + ) + after = table_rows() + assert {k: v for k, v in before.items() if k != "suppressions"} == { + k: v for k, v in after.items() if k != "suppressions" + } + assert not ledger.is_suppressed("recipient@example.test") and len(captured) == 1 ledger.db.close() evidence.close() assert not root.exists() @@ -216,6 +298,12 @@ def main(): "synthetic_operator_suppression_blocks_send_and_survives_restart": True, "suppression_scope": "recipient-global across callers and tenants", "explicit_synthetic_operator_unsuppression_verified": True, + "selected_return_operator_source_sha256": hashlib.sha256( + Path(return_operation.__file__).read_bytes() + ).hexdigest(), + "selected_operator_correlates_actual_owner_payload_and_accepted_message_id": True, + "selected_operator_suppresses_across_callers_and_explicitly_releases": True, + "selected_operator_preserves_all_five_other_ledger_tables": True, "native_operator_admission_proved": False, "received_mail_authenticity_or_correlation_authority_proved": False, "python_network_operations_blocked": True, diff --git a/scripts/smtp-private-browser/xdg-open b/scripts/smtp-private-browser/xdg-open new file mode 100755 index 0000000..92b2780 --- /dev/null +++ b/scripts/smtp-private-browser/xdg-open @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Open only the OpenBao administrator's KeyCape request in Windows Edge InPrivate.""" + +import subprocess +import sys +from pathlib import Path +from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit + +EDGE = Path("/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe") +POWERSHELL = "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe" + + +def fresh_url(value): + url = urlsplit(value) + query = parse_qs(url.query, keep_blank_values=True) + if ( + url.scheme != "https" + or url.hostname != "kc.coulomb.social" + or url.port not in (None, 443) + or url.username + or url.password + or url.fragment + or url.path != "/authorize" + or any(c in value for c in "\r\n\x00") + or query.get("client_id") != ["openbao-admin"] + or query.get("redirect_uri") + not in (["http://localhost:8250/oidc/callback"], ["http://127.0.0.1:8250/oidc/callback"]) + ): + raise ValueError("unreviewed_browser_request") + query["prompt"] = ["login"] + return urlunsplit((url.scheme, url.netloc, url.path, urlencode(query, doseq=True), "")) + + +def main(): + if len(sys.argv) != 2 or not EDGE.is_file(): + return 1 + try: + url = fresh_url(sys.argv[1]) + result = subprocess.run( + [ + POWERSHELL, + "-NoProfile", + "-NonInteractive", + "-Command", + '$ErrorActionPreference="Stop"; $u=[Console]::In.ReadLine(); ' + "Start-Process -FilePath " + '"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe" ' + '-ArgumentList @("--inprivate",$u)', + ], + input=(url + "\n").encode(), + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + timeout=20, + check=False, + ) + return int(result.returncode != 0) + except Exception: + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_mail_return_native.py b/tests/test_mail_return_native.py new file mode 100644 index 0000000..8779052 --- /dev/null +++ b/tests/test_mail_return_native.py @@ -0,0 +1,496 @@ +"""Native adapter assembly with modeled Kubernetes and real SQLite child processes.""" + +import copy +import hashlib +import importlib.util +import json +import os +import subprocess +import sys +from pathlib import Path +from types import SimpleNamespace + +import pytest + +from tests.test_mail_return_operation import POLICY, RECIPIENT, dsn, rows +from tests.test_mail_return_operation import ledger as ledger + +SCRIPTS = Path(__file__).resolve().parents[1] / "scripts" +sys.path.insert(0, str(SCRIPTS)) +spec = importlib.util.spec_from_file_location( + "mail_return_native", SCRIPTS / "mail_return_native.py" +) +native = importlib.util.module_from_spec(spec) +spec.loader.exec_module(native) + + +class Cluster: + def __init__(self, ledger, directory, monkeypatch): + self.ledger, self.audit = ledger + self.child_calls = 0 + self.change_after_exec = False + self.lose_reply = False + self.uid = "receiver-uid" + self.pvc_uid = "pvc-uid" + self.cluster_uid = native.CLUSTER + self.extra_pod = False + self.source = directory / "site" + self.source.mkdir() + package = self.source / "email_connect" + package.mkdir() + (package / "__init__.py").write_text("") + for name, variable in (("transactional", "TRANSACTIONAL"), ("callers", "CALLERS")): + p = package / (name + ".py") + p.write_text("# Synthetic installed module identity fixture.\n") + monkeypatch.setattr(native, variable, hashlib.sha256(p.read_bytes()).hexdigest()) + self.policy = directory / "callers.json" + self.policy.write_text(json.dumps(POLICY)) + + def __call__(self, args, payload=None): + if args[0] == "get": + kind, name = args[1:3] + if kind == "namespace": + return { + "metadata": { + "uid": self.cluster_uid if name == "kube-system" else native.NAMESPACE + } + } + if kind == "deployment": + return { + "metadata": {"uid": native.DEPLOYMENT, "generation": 76}, + "spec": {"replicas": 1}, + "status": {"observedGeneration": 76, "readyReplicas": 1}, + } + if kind == "replicaset": + return { + "metadata": { + "uid": "rs-uid", + "ownerReferences": [ + { + "controller": True, + "uid": native.DEPLOYMENT, + } + ], + } + } + if kind == "pvc": + return {"metadata": {"uid": self.pvc_uid}, "status": {"phase": "Bound"}} + if kind == "pods": + pod = { + "metadata": { + "uid": self.uid, + "name": "receiver", + "ownerReferences": [ + { + "controller": True, + "kind": "ReplicaSet", + "name": "rs", + "uid": "rs-uid", + } + ], + }, + "spec": { + "containers": [ + { + "name": "email-connect", + "image": native.IMAGE, + "volumeMounts": [{"name": "data", "mountPath": "/data"}], + } + ], + "volumes": [ + { + "name": "data", + "persistentVolumeClaim": {"claimName": "email-connect-data"}, + } + ], + }, + "status": { + "phase": "Running", + "containerStatuses": [ + {"name": "email-connect", "ready": True, "imageID": native.IMAGE} + ], + }, + } + return {"items": [pod, copy.deepcopy(pod)] if self.extra_pod else [pod]} + raise AssertionError(args) + assert args[:3] == ["exec", "-i", "-n"] + self.child_calls += 1 + # Only these isolation substitutions differ from the actual pod program: + # fixture UID/paths and installed-module location. SQL/core code is real. + code = args[-1].replace("os.getuid()==10001", "os.getuid()==" + str(os.getuid())) + code = code.replace("/data/email-connect.db", str(self.ledger)) + code = code.replace("/data/mail-returns", str(self.audit)) + code = code.replace("/etc/email-connect-callers/callers.json", str(self.policy)) + prefix = ( + "import sys\nsys.path.insert(0," + repr(str(self.source)) + ")\n" + "def no_network(event,args):\n" + " if event in {'socket.connect','socket.bind','socket.getaddrinfo','socket.sendto'}:" + "raise RuntimeError('network forbidden in fixture')\n" + "sys.addaudithook(no_network)\n" + ) + result = subprocess.run( + [sys.executable, "-I", "-c", prefix + code], + input=payload, + capture_output=True, + timeout=20, + check=False, + env={ + **os.environ, + "EMAIL_CONNECT_CALLERS_FILE": str(self.policy), + "EMAIL_CONNECT_DATABASE_PATH": str(self.ledger), + }, + ) + if self.change_after_exec: + self.uid = "replacement-uid" + if self.lose_reply: + self.lose_reply = False + raise OSError("modeled lost exec reply") + if result.returncode: + raise native.operation.Refused("fixture_child_refused") + return json.loads(result.stdout) + + +@pytest.fixture +def cluster(ledger, tmp_path, monkeypatch): + return Cluster(ledger, tmp_path, monkeypatch) + + +def request(action, **kwargs): + return {"action": action, "sources": native.sources(), **kwargs} + + +def prepare(cluster): + import base64 + + return native.run_node( + request( + "prepare", + raw_base64=base64.b64encode(dsn()).decode(), + recipient=RECIPIENT, + review="Operator reviewed selected provider DSN", + ), + cluster, + ) + + +def test_native_observe_is_read_only_and_pins_actual_six_table_store(cluster): + before = rows(cluster.ledger) + response = native.run_node(request("observe"), cluster) + assert response["result"]["status"] == "native_return_observed" + assert set(response["result"]["table_counts"]) == set(before) + assert not response["result"]["ledger_modified"] and rows(cluster.ledger) == before + assert cluster.child_calls == 1 + + +def test_assembled_prepare_apply_release_uses_live_policy_and_preserves_other_tables(cluster): + import base64 + + before = rows(cluster.ledger) + packet = prepare(cluster) + plan = packet["result"] + assert rows(cluster.ledger) == before + result = native.run_node( + request( + "apply", + expected_context=packet["context"], + plan=plan, + raw_base64=base64.b64encode(dsn()).decode(), + ), + cluster, + ) + assert result["result"]["status"] == "suppression_applied" + after = rows(cluster.ledger) + assert {k: v for k, v in before.items() if k != "suppressions"} == { + k: v for k, v in after.items() if k != "suppressions" + } + release = native.run_node( + request( + "release", + source_operation=native.operation.digest(plan), + review="Operator verified recipient correction", + ), + cluster, + ) + native.run_node( + request("apply", expected_context=release["context"], plan=release["result"]), cluster + ) + assert rows(cluster.ledger) == before + + +@pytest.mark.parametrize("defect", ["cluster", "pod", "volume"]) +def test_changed_native_identity_refuses_before_receiver_exec(cluster, defect): + packet = prepare(cluster) + if defect == "cluster": + cluster.cluster_uid = "wrong-cluster" + elif defect == "pod": + cluster.extra_pod = True + else: + cluster.pvc_uid = "replaced-volume" + calls = cluster.child_calls + with pytest.raises(native.operation.Refused): + native.run_node( + request("apply", expected_context=packet["context"], plan=packet["result"]), cluster + ) + assert cluster.child_calls == calls + + +def test_changed_policy_or_installed_source_cannot_mutate_ledger(cluster): + import base64 + + packet = prepare(cluster) + before = rows(cluster.ledger) + cluster.policy.write_text(json.dumps({"changed": True})) + with pytest.raises(native.operation.Refused): + native.run_node( + request( + "apply", + expected_context=packet["context"], + plan=packet["result"], + raw_base64=base64.b64encode(dsn()).decode(), + ), + cluster, + ) + cluster.policy.write_text(json.dumps(POLICY)) + (cluster.source / "email_connect/transactional.py").write_text("# changed installed source\n") + with pytest.raises(native.operation.Refused): + native.run_node(request("observe"), cluster) + assert rows(cluster.ledger) == before + + +def test_lost_reply_and_replaced_receiver_can_be_inspected_without_replay(cluster): + import base64 + + packet = prepare(cluster) + cluster.lose_reply = True + with pytest.raises(OSError): + native.run_node( + request( + "apply", + expected_context=packet["context"], + plan=packet["result"], + raw_base64=base64.b64encode(dsn()).decode(), + ), + cluster, + ) + cluster.uid = "replacement-uid" + response = native.run_node( + request( + "inspect", + expected_context=packet["context"], + operation_id=native.operation.digest(packet["result"]), + ), + cluster, + ) + assert response["result"]["current_state"] == "planned_state_present" + assert response["result"]["commit_receipt_present"] + with pytest.raises(native.operation.Refused): + native.run_node( + request("apply", expected_context=packet["context"], plan=packet["result"]), cluster + ) + + +def test_receiver_replacement_during_commit_is_unconfirmed_and_remains_inspectable(cluster): + import base64 + + packet = prepare(cluster) + cluster.change_after_exec = True + with pytest.raises(native.operation.Refused, match="receiver_changed_during_operation"): + native.run_node( + request( + "apply", + expected_context=packet["context"], + plan=packet["result"], + raw_base64=base64.b64encode(dsn()).decode(), + ), + cluster, + ) + cluster.change_after_exec = False + response = native.run_node( + request( + "inspect", + expected_context=packet["context"], + operation_id=native.operation.digest(packet["result"]), + ), + cluster, + ) + assert response["result"]["current_state"] == "planned_state_present" + + +def test_remote_data_never_enters_shell_code_and_timeout_is_not_no_effect(monkeypatch): + def execute(args, **kwargs): + assert args[:2] == ["ssh", "-o"] + assert "$(touch" not in " ".join(args) + assert json.loads(kwargs["input"])["review"] == "$(touch /tmp/must-not-run)" + return SimpleNamespace(returncode=0, stdout=b'{"context":{},"result":{}}') + + monkeypatch.setattr(native.subprocess, "run", execute) + response = native.remote({"action": "observe", "review": "$(touch /tmp/must-not-run)"}) + assert response["context"] == {} and response["result"] == {} + assert set(response["source_sha256"]) == set(native.FILES) + + def timeout(*args, **kwargs): + raise subprocess.TimeoutExpired("ssh", 180) + + monkeypatch.setattr(native.subprocess, "run", timeout) + with pytest.raises(native.operation.Refused, match="outcome_unconfirmed"): + native.remote({"action": "apply", "reviewed_source_sha256": response["source_sha256"]}) + + +def test_operation_path_escape_refuses_before_native_read(cluster): + with pytest.raises(native.operation.Refused, match="operation_id_invalid"): + native.run_node(request("inspect", operation_id="../../unrelated"), cluster) + assert not cluster.child_calls + + +def test_reviewed_source_drift_refuses_before_ssh(monkeypatch): + calls = [] + monkeypatch.setattr(native.subprocess, "run", lambda *a, **kw: calls.append(a)) + with pytest.raises(native.operation.Refused, match="reviewed_source_changed"): + native.remote({"action": "apply", "reviewed_source_sha256": {"changed": "source"}}) + assert not calls + + +def test_native_expiry_lost_reply_is_inspectable_and_reconciles_only_selected_copies(cluster): + import base64 + from datetime import UTC, datetime, timedelta + + packet = prepare(cluster) + native.run_node( + request( + "apply", + expected_context=packet["context"], + plan=packet["result"], + raw_base64=base64.b64encode(dsn()).decode(), + ), + cluster, + ) + opid = native.operation.digest(packet["result"]) + directory = cluster.audit / opid + completed = directory / "completed.json" + value = json.loads(completed.read_text()) + value["completed_at"] = (datetime.now(UTC) - timedelta(days=31)).isoformat() + completed.write_text(json.dumps(value)) # Explicit synthetic retention clock fixture. + before = rows(cluster.ledger) + expiry = native.run_node( + request("prepare-expiry", as_of=datetime.now(UTC).isoformat()), cluster + ) + assert expiry["result"]["preview"]["expired_raw_copies"] == [opid] + cluster.lose_reply = True + with pytest.raises(OSError): + native.run_node( + request("apply-expiry", expected_context=expiry["context"], plan=expiry["result"]), + cluster, + ) + state = native.run_node( + request("inspect-expiry", expected_context=expiry["context"], plan=expiry["result"]), + cluster, + ) + assert state["result"]["selected_raw_states"] == {opid: "raw_expiry_verified"} + settled = native.run_node( + request("reconcile-expiry", expected_context=expiry["context"], plan=expiry["result"]), + cluster, + ) + assert settled["result"]["expired_raw_copies"] == [] and rows(cluster.ledger) == before + assert (directory / "plan.json").exists() and not (directory / "message.eml").exists() + + +def test_missing_raw_without_expiry_proof_is_not_success(cluster): + import base64 + from datetime import UTC, datetime + + packet = prepare(cluster) + native.run_node( + request( + "apply", + expected_context=packet["context"], + plan=packet["result"], + raw_base64=base64.b64encode(dsn()).decode(), + ), + cluster, + ) + opid = native.operation.digest(packet["result"]) + (cluster.audit / opid / "message.eml").unlink() + with pytest.raises(native.operation.Refused): + native.run_node( + request( + "inspect-expiry", + plan={ + "action": "expire-raw", + "as_of": datetime.now(UTC).isoformat(), + "preview": {"expired_raw_copies": [opid]}, + }, + ), + cluster, + ) + + +def test_native_cli_saves_private_plan_and_rechecks_digest_before_mutation( + cluster, tmp_path, monkeypatch, capsys +): + calls = [] + + def transport(req): + calls.append(req) + return { + **native.run_node({**req, "sources": native.sources()}, cluster), + "source_sha256": { + n: hashlib.sha256(v.encode()).hexdigest() for n, v in native.sources().items() + }, + } + + monkeypatch.setattr(native, "remote", transport) + private = tmp_path / "operator" + private.mkdir(mode=0o700) + message = private / "selected.eml" + message.write_bytes(dsn()) + packet_path = private / "packet.json" + before = rows(cluster.ledger) + assert ( + native.main( + [ + "prepare", + "--packet", + str(packet_path), + "--message", + str(message), + "--recipient", + RECIPIENT, + "--review", + "Operator reviewed provider DSN", + ] + ) + == 0 + ) + prepared = json.loads(capsys.readouterr().out) + assert packet_path.stat().st_mode & 0o777 == 0o600 + assert "raw_base64" not in packet_path.read_text() and rows(cluster.ledger) == before + with pytest.raises(native.operation.Refused, match="reviewed_packet_required"): + native.main( + [ + "apply", + "--packet", + str(packet_path), + "--message", + str(message), + "--expect-digest", + "wrong", + ] + ) + assert len(calls) == 1 + assert ( + native.main( + [ + "apply", + "--packet", + str(packet_path), + "--message", + str(message), + "--expect-digest", + prepared["packet_sha256"], + ] + ) + == 0 + ) + result = json.loads(capsys.readouterr().out) + assert result["result"]["status"] == "suppression_applied" + assert RECIPIENT not in json.dumps(result) diff --git a/tests/test_mail_return_operation.py b/tests/test_mail_return_operation.py new file mode 100644 index 0000000..b6545ec --- /dev/null +++ b/tests/test_mail_return_operation.py @@ -0,0 +1,529 @@ +"""Actual SQLite and abrupt process exits at the selected-return boundary.""" + +import copy +import importlib.util +import json +import sqlite3 +import subprocess +import sys +from datetime import UTC, datetime, timedelta +from pathlib import Path + +import pytest + +SOURCE = Path(__file__).resolve().parents[1] / "scripts/mail_return_operation.py" +spec = importlib.util.spec_from_file_location("mail_return_operation", SOURCE) +operation = importlib.util.module_from_spec(spec) +spec.loader.exec_module(operation) +NOW = datetime(2026, 11, 1, tzinfo=UTC) +RECIPIENT = "recipient@example.test" +REFERENCE = "" +POLICY = { + "schema_version": 1, + "callers": [ + { + "caller_id": "user-engine", + "source": "user-engine", + "bindings": [["tenant:one", "client:one"]], + "templates": ["family_member.invited"], + "adopt_legacy_verifications": True, + "adopt_legacy_invitations": True, + } + ], +} + + +def dsn(reference=REFERENCE, recipient=RECIPIENT, status="5.1.1", action="failed"): + return ( + "From: postmaster@example.test\r\nTo: platform@example.test\r\n" + "MIME-Version: 1.0\r\nContent-Type: multipart/report; " + 'report-type=delivery-status; boundary="report"\r\n\r\n' + "--report\r\nContent-Type: text/plain\r\n\r\nDelivery failed.\r\n" + "--report\r\nContent-Type: message/delivery-status\r\n\r\n" + f"Reporting-MTA: dns; mx.example.test\r\nOriginal-Message-ID: {reference}\r\n\r\n" + f"Final-Recipient: rfc822; {recipient}\r\nAction: {action}\r\nStatus: {status}\r\n" + "\r\n--report--\r\n" + ).encode() + + +@pytest.fixture +def ledger(tmp_path): + path = tmp_path / "transactional.db" + with sqlite3.connect(path) as db: + db.executescript(""" + CREATE TABLE verifications (request_id TEXT PRIMARY KEY, email TEXT, + tenant TEXT, client_id TEXT, caller_id TEXT, handle_hash TEXT, + expires_at TEXT, consumed_at TEXT, canceled_at TEXT); + CREATE TABLE verification_attempts (request_id TEXT PRIMARY KEY, state TEXT, + provider_ref TEXT); + CREATE TABLE deliveries (event_id TEXT PRIMARY KEY, provider_ref TEXT); + CREATE TABLE send_attempts (event_id TEXT PRIMARY KEY, fingerprint TEXT, state TEXT); + CREATE TABLE suppressions (email TEXT PRIMARY KEY, reason TEXT, created_at TEXT); + CREATE TABLE caller_buckets (caller_id TEXT PRIMARY KEY, tokens REAL, updated_at REAL); + """) + db.execute( + "INSERT INTO verifications VALUES (?,?,?,?,?,?,?,?,?)", + ( + "accepted", + RECIPIENT, + "tenant:one", + "client:one", + "user-engine", + "retained-handle", + "2030-01-01T00:00:00Z", + None, + None, + ), + ) + db.execute( + "INSERT INTO verification_attempts VALUES (?,?,?)", ("accepted", "accepted", REFERENCE) + ) + db.execute( + "INSERT INTO send_attempts VALUES (?,?,?)", ("uncertain", "original", "in_flight") + ) + db.execute("INSERT INTO caller_buckets VALUES (?,?,?)", ("user-engine", 3, 123)) + db.execute( + "INSERT INTO suppressions VALUES (?,?,?)", + ("other@example.test", "manual", "2026-01-01"), + ) + audit = tmp_path / "audit" + audit.mkdir(mode=0o700) + return path, audit + + +def prepare(ledger, **kwargs): + return operation.prepare( + ledger[0], + dsn(), + RECIPIENT, + POLICY, + "Operator verified the selected provider return", + now=NOW, + **kwargs, + ) + + +def rows(path): + with sqlite3.connect(path) as db: + return { + name: db.execute(f'SELECT * FROM "{name}" ORDER BY 1').fetchall() + for (name,) in db.execute("SELECT name FROM sqlite_master WHERE type='table'") + } + + +def apply(ledger, plan): + return operation.apply(ledger[0], plan, dsn(), POLICY, operation.digest(plan), ledger[1]) + + +def test_correlation_dry_run_is_read_only_and_never_trusts_mail_alone(ledger): + before = rows(ledger[0]) + plan = prepare(ledger) + assert plan["binding"] == { + "kind": "verification", + "id": "accepted", + "caller_id": "user-engine", + "tenant": "tenant:one", + "client_id": "client:one", + "recipient": RECIPIENT, + "provider_ref": REFERENCE, + } + assert rows(ledger[0]) == before and not list(ledger[1].iterdir()) + with pytest.raises(operation.Refused, match="provenance_review"): + operation.prepare(ledger[0], dsn(), RECIPIENT, POLICY, "") + + +@pytest.mark.parametrize( + "raw", + [ + dsn(status="4.2.2", action="delayed"), + dsn(action="delivered"), + dsn(status="2.0.0"), + dsn(recipient="unrelated@example.test"), + dsn(reference=""), + b"Subject: failure\n\nOriginal-Message-ID: \nStatus: 5.1.1", + dsn().replace(b"Status: 5.1.1", b"Status: 5.1.1\r\nStatus: 5.2.2"), + dsn().replace( + b"--report--", + b"--report\r\nContent-Type: message/delivery-status\r\n\r\n" + b"Reporting-MTA: dns; another.example.test\r\n\r\n--report--", + ), + dsn().replace( + b"--report--", + b"--report\r\nContent-Type: message/rfc822\r\n\r\n" + b"Message-ID: \r\n\r\nbody\r\n--report--", + ), + ], +) +def test_unmatched_ambiguous_temporary_and_unstructured_mail_cannot_suppress(ledger, raw): + before = rows(ledger[0]) + with pytest.raises(operation.Refused): + operation.prepare(ledger[0], raw, RECIPIENT, POLICY, "Manually selected trusted return") + assert rows(ledger[0]) == before and not list(ledger[1].iterdir()) + + +@pytest.mark.parametrize("change", ["caller", "tenant", "unaccepted", "duplicate", "recipient"]) +def test_message_id_match_does_not_override_authoritative_binding(ledger, change): + with sqlite3.connect(ledger[0]) as db: + if change == "caller": + db.execute("UPDATE verifications SET caller_id='other-caller'") + elif change == "tenant": + db.execute("UPDATE verifications SET tenant='tenant:other'") + elif change == "unaccepted": + db.execute("UPDATE verification_attempts SET state='in_flight'") + elif change == "duplicate": + db.execute("INSERT INTO deliveries VALUES (?,?)", ("duplicate", REFERENCE)) + db.execute("INSERT INTO send_attempts VALUES ('duplicate','different','in_flight')") + else: + db.execute("UPDATE verifications SET email='other@example.test'") + with pytest.raises(operation.Refused): + prepare(ledger) + + +def test_legacy_caller_adoption_must_be_explicitly_admitted(ledger): + with sqlite3.connect(ledger[0]) as db: + db.execute("UPDATE verifications SET caller_id=''") + assert prepare(ledger)["binding"]["caller_id"] == "user-engine" + denied = copy.deepcopy(POLICY) + denied["callers"][0]["adopt_legacy_verifications"] = False + with pytest.raises(operation.Refused, match="caller_mismatch"): + operation.prepare(ledger[0], dsn(), RECIPIENT, denied, "Reviewed provider DSN provenance") + + +def test_invitation_requires_exact_original_payload_fingerprint(ledger): + payload = { + "id": "event", + "source": "user-engine", + "type": "family_member.invited", + "tenant": "tenant:one", + "client_id": "client:one", + "data": { + "primary_email": RECIPIENT, + "invitation_id": "fixture-invitation", + }, + } + with sqlite3.connect(ledger[0]) as db: + db.execute("UPDATE verification_attempts SET provider_ref=''") + db.execute( + "INSERT INTO send_attempts VALUES (?,?,?)", + ("event", operation.digest(payload), "in_flight"), + ) + db.execute("INSERT INTO deliveries VALUES (?,?)", ("event", REFERENCE)) + with pytest.raises(operation.Refused, match="original_invitation_payload"): + prepare(ledger) + plan = prepare(ledger, invitation_payload=payload) + assert plan["binding"]["kind"] == "invitation" + forged = copy.deepcopy(payload) + forged["data"]["invitation_id"] = "different" + with pytest.raises(operation.Refused, match="fingerprint_mismatch"): + prepare(ledger, invitation_payload=forged) + assert apply(ledger, plan)["status"] == "suppression_applied" + + +def test_suppression_and_explicit_release_preserve_all_unrelated_records(ledger): + before = rows(ledger[0]) + plan = prepare(ledger) + result = apply(ledger, plan) + assert result["scope"] == "recipient-global across callers and tenants" + after = rows(ledger[0]) + assert {k: v for k, v in after.items() if k != "suppressions"} == { + k: v for k, v in before.items() if k != "suppressions" + } + assert len(after["suppressions"]) == len(before["suppressions"]) + 1 + directory = ledger[1] / operation.digest(plan) + assert operation.inspect(ledger[0], directory)["current_state"] == "planned_state_present" + with pytest.raises(FileExistsError): + apply(ledger, plan) + release = operation.prepare_release( + ledger[0], + ledger[1], + operation.digest(plan), + "Operator verified the address correction", + now=NOW, + ) + assert ( + operation.apply(ledger[0], release, None, None, operation.digest(release), ledger[1])[ + "status" + ] + == "suppression_released" + ) + assert rows(ledger[0]) == before + for path in ledger[1].rglob("*"): + assert path.stat().st_mode & 0o077 == 0 + + +def test_stale_policy_evidence_or_suppression_never_applies(ledger): + plan = prepare(ledger) + for raw, policy in ((dsn() + b"changed", POLICY), (dsn(), {"changed": True})): + with pytest.raises(operation.Refused): + operation.apply(ledger[0], plan, raw, policy, operation.digest(plan), ledger[1]) + with sqlite3.connect(ledger[0]) as db: + db.execute("INSERT INTO suppressions VALUES (?,?,?)", (RECIPIENT, "manual", "later")) + before = rows(ledger[0]) + with pytest.raises(operation.Refused, match="suppression_changed"): + apply(ledger, plan) + assert rows(ledger[0]) == before + + +@pytest.mark.parametrize("boundary,committed", [("intent.json", False), ("completed.json", True)]) +def test_real_process_death_retains_inspectable_evidence_without_replay( + ledger, boundary, committed +): + plan = prepare(ledger) + request = { + "source": str(SOURCE), + "db": str(ledger[0]), + "audit": str(ledger[1]), + "plan": plan, + "raw": dsn().decode(), + "policy": POLICY, + "boundary": boundary, + } + code = r""" +import importlib.util,json,os,pathlib,sys +r=json.load(sys.stdin) +s=importlib.util.spec_from_file_location('operation',r['source']) +m=importlib.util.module_from_spec(s);s.loader.exec_module(m) +original=m.durable +def crash(path,raw): + if path.name==r['boundary'] and path.name=='completed.json':os._exit(79) + original(path,raw) + if path.name==r['boundary']:os._exit(79) +m.durable=crash +m.apply(r['db'],r['plan'],r['raw'].encode(),r['policy'],m.digest(r['plan']),pathlib.Path(r['audit'])) +""" + child = subprocess.run( + [sys.executable, "-I", "-c", code], + input=json.dumps(request).encode(), + capture_output=True, + timeout=20, + check=False, + ) + assert child.returncode == 79, child.stderr.decode() + state = operation.inspect(ledger[0], ledger[1] / operation.digest(plan)) + assert state["current_state"] == ( + "planned_state_present" if committed else "original_state_present" + ) + assert not state["commit_receipt_present"] and not state["retry_automatically"] + with pytest.raises(FileExistsError): + apply(ledger, plan) + before = rows(ledger[0]) + completed = operation.reconcile( + ledger[0], ledger[1] / operation.digest(plan), operation.digest(plan) + ) + assert completed["status"] == ("suppression_applied" if committed else "operation_not_applied") + assert not completed["sql_mutated"] and completed["reconciled_state_verified"] + assert rows(ledger[0]) == before + + +def test_evidence_expiry_preserves_ledger_decisions_and_original_mail(ledger, tmp_path): + plan = prepare(ledger) + apply(ledger, plan) + original = tmp_path / "original.eml" + original.write_bytes(dsn()) + before = rows(ledger[0]) + directory = ledger[1] / operation.digest(plan) + completed_at = datetime.fromisoformat( + json.loads((directory / "completed.json").read_text())["completed_at"] + ) + assert not operation.expire_raw(ledger[1], now=completed_at + timedelta(days=29))[ + "expired_raw_copies" + ] + result = operation.expire_raw(ledger[1], now=completed_at + timedelta(days=31)) + assert result["expired_raw_copies"] == [operation.digest(plan)] + assert not (directory / "message.eml").exists() + assert (directory / "plan.json").exists() and original.read_bytes() == dsn() + assert rows(ledger[0]) == before + assert not operation.expire_raw(ledger[1], now=completed_at + timedelta(days=32))[ + "expired_raw_copies" + ] + assert ( + operation.prepare_release( + ledger[0], ledger[1], operation.digest(plan), "Operator confirms recipient is corrected" + )["action"] + == "release" + ) + + +def test_expiry_holds_incomplete_operations_and_refuses_changed_copies(ledger): + plan = prepare(ledger) + apply(ledger, plan) + directory = ledger[1] / operation.digest(plan) + raw = directory / "message.eml" + completed = directory / "completed.json" + saved = completed.read_bytes() + completed.unlink() + assert not operation.expire_raw(ledger[1], now=NOW + timedelta(days=400))["expired_raw_copies"] + operation.durable(completed, saved) + raw.write_bytes(b"changed") + with pytest.raises(operation.Refused, match="retained_evidence_changed"): + operation.expire_raw(ledger[1], now=NOW + timedelta(days=400)) + assert raw.exists() + + +def test_private_path_refusals_precede_ledger_or_audit_mutation(ledger, tmp_path): + plan = prepare(ledger) + ledger[1].chmod(0o755) + before = rows(ledger[0]) + with pytest.raises(operation.Refused, match="private_directory"): + apply(ledger, plan) + link = tmp_path / "linked-ledger" + link.symlink_to(ledger[0]) + with pytest.raises(operation.Refused, match="real_existing_ledger"): + operation.prepare(link, dsn(), RECIPIENT, POLICY, "Selected authenticated provider return") + assert rows(ledger[0]) == before + + +def test_running_operation_lock_refuses_reconciliation_without_writes(ledger): + plan = prepare(ledger) + apply(ledger, plan) + directory = ledger[1] / operation.digest(plan) + completed = directory / "completed.json" + saved = completed.read_bytes() + completed.unlink() + before = rows(ledger[0]) + with operation.exclusive(directory): + with pytest.raises(operation.Refused, match="still_running"): + operation.reconcile(ledger[0], directory, operation.digest(plan)) + assert rows(ledger[0]) == before and not completed.exists() + operation.durable(completed, saved) + + +def test_operator_cli_roundtrip_requires_exact_source_plan_and_selected_message( + ledger, tmp_path, capsys +): + directory = tmp_path / "operator" + directory.mkdir(mode=0o700) + policy = directory / "callers.json" + policy.write_text(json.dumps(POLICY)) + message = directory / "selected.eml" + message.write_bytes(dsn()) + packet = directory / "packet.json" + baseline = rows(ledger[0]) + assert ( + operation.main( + [ + "prepare", + "--ledger", + str(ledger[0]), + "--caller-policy", + str(policy), + "--message", + str(message), + "--recipient", + RECIPIENT, + "--review", + "Selected controlled provider DSN reviewed", + "--packet", + str(packet), + ] + ) + == 0 + ) + result = json.loads(capsys.readouterr().out) + assert not result["ledger_modified"] and rows(ledger[0]) == baseline + assert "raw_base64" not in packet.read_text() + args = [ + "apply", + "--ledger", + str(ledger[0]), + "--caller-policy", + str(policy), + "--packet", + str(packet), + "--audit-root", + str(ledger[1]), + "--expect-digest", + result["plan_sha256"], + "--message", + str(message), + ] + assert operation.main(args) == 0 + applied = json.loads(capsys.readouterr().out) + assert applied["status"] == "suppression_applied" and RECIPIENT not in json.dumps(applied) + release = directory / "release.json" + assert ( + operation.main( + [ + "release", + "--ledger", + str(ledger[0]), + "--audit-root", + str(ledger[1]), + "--operation", + result["plan_sha256"], + "--review", + "Verified address correction", + "--packet", + str(release), + ] + ) + == 0 + ) + release_result = json.loads(capsys.readouterr().out) + assert ( + operation.main( + [ + "apply", + "--ledger", + str(ledger[0]), + "--audit-root", + str(ledger[1]), + "--packet", + str(release), + "--expect-digest", + release_result["plan_sha256"], + ] + ) + == 0 + ) + capsys.readouterr() + assert rows(ledger[0]) == baseline + + +def test_changed_local_operation_source_cannot_apply(ledger): + packet = ledger[1] / "packet.json" + plan = prepare(ledger) + operation.durable( + packet, + operation.canonical( + { + "plan": plan, + "plan_sha256": operation.digest(plan), + "source_sha256": "stale", + } + ), + ) + before = rows(ledger[0]) + with pytest.raises(operation.Refused, match="reviewed_source_changed"): + operation.main( + [ + "apply", + "--ledger", + str(ledger[0]), + "--audit-root", + str(ledger[1]), + "--packet", + str(packet), + "--expect-digest", + operation.digest(plan), + ] + ) + assert rows(ledger[0]) == before + + +def test_new_audit_directory_is_durable_before_sql_or_commit_intent(ledger, monkeypatch): + plan = prepare(ledger) + before = rows(ledger[0]) + + def fail_parent_sync(path): + assert Path(path) == ledger[1] + raise OSError("synthetic audit-directory fsync failure") + + monkeypatch.setattr(operation, "sync_directory", fail_parent_sync) + with pytest.raises(OSError, match="fsync failure"): + apply(ledger, plan) + assert rows(ledger[0]) == before + assert not list(ledger[1].rglob("intent.json")) + assert not operation.expire_raw(ledger[1], now=NOW + timedelta(days=400))[ + "expired_raw_copies" + ] diff --git a/tests/test_smtp_admission_launcher.py b/tests/test_smtp_admission_launcher.py new file mode 100644 index 0000000..ff31601 --- /dev/null +++ b/tests/test_smtp_admission_launcher.py @@ -0,0 +1,258 @@ +"""The founder launcher never approves or starts Warden without explicit consent.""" + +import importlib.util +import io +import json +from importlib.machinery import SourceFileLoader +from pathlib import Path +from types import SimpleNamespace +from unittest.mock import Mock + +import pytest + +SOURCE = Path(__file__).resolve().parents[1] / "scripts/admit-platform-smtp.py" +spec = importlib.util.spec_from_file_location("smtp_admission_launcher", SOURCE) +launcher = importlib.util.module_from_spec(spec) +spec.loader.exec_module(launcher) + + +@pytest.fixture +def attended(tmp_path, monkeypatch): + ccr = {"status": "proposed", "review": {"comments": []}} + validator = Mock() + intake = Mock() + intake.request_decision.return_value = {"id": "fixed-test-decision"} + modules = { + "attended_ccr": validator, + "attended_intake": intake, + "attended_retry_ccr": validator, + } + foundation = SimpleNamespace( + admission=SimpleNamespace(CCR=tmp_path / "ccr.yaml", module=lambda n, _: modules[n]), + Native=Mock(), + protected_application=Mock(return_value="protected-fingerprint"), + ) + preflight = Mock(return_value=(ccr, {"foundation": {}}, "protected-fingerprint")) + monkeypatch.setattr(launcher, "owner", lambda: foundation) + monkeypatch.setattr(launcher, "preflight", preflight) + monkeypatch.setattr(launcher.Path, "home", lambda: tmp_path) + monkeypatch.setattr(launcher.sys, "stdin", SimpleNamespace(isatty=lambda: True)) + monkeypatch.setattr("builtins.input", lambda _: launcher.PHRASE) + requests = [] + + class Response(io.BytesIO): + def __init__(self): + super().__init__(b'{"status":"resolved"}') + + def resolve(request, **kwargs): + requests.append(json.loads(request.data)) + return Response() + + monkeypatch.setattr(launcher.urllib.request, "urlopen", resolve) + + def warden(args, **kwargs): + assert args[:4] == ["warden", "access", "openbao-platform-admin-login", "--exec"] + assert validator.sync_state_hub_decision.call_count == 1 + assert requests[0]["decided_by"] == "Bernd Worsch" + receipt = Path(args[-1]) + receipt.write_text( + json.dumps( + { + "status": "foundation_admitted_projection_ready", + "proposal_digest": launcher.EXPECTED, + } + ) + + "\n" + ) + receipt.chmod(0o600) + return SimpleNamespace(returncode=0) + + run = Mock(side_effect=warden) + monkeypatch.setattr(launcher.subprocess, "run", run) + return SimpleNamespace( + root=tmp_path, + ccr=ccr, + validator=validator, + intake=intake, + foundation=foundation, + preflight=preflight, + run=run, + requests=requests, + ) + + +def test_default_checks_state_without_approval_or_login(attended): + assert launcher.main([]) == 0 + attended.validator.dump_yaml.assert_not_called() + attended.run.assert_not_called() + assert not list(attended.root.rglob("approval.json")) + + +@pytest.mark.parametrize("consent", ["", "yes", "ADMIT PLATFORM SMTP ".lower()]) +def test_inexact_approval_cannot_create_state_or_approve(attended, monkeypatch, consent): + monkeypatch.setattr("builtins.input", lambda _: consent) + with pytest.raises(RuntimeError, match="Approval not given"): + launcher.main(["--execute"]) + attended.validator.dump_yaml.assert_not_called() + attended.run.assert_not_called() + assert not list(attended.root.rglob("approval.json")) + + +def test_agent_pipe_cannot_resolve_founder_decision(attended, monkeypatch): + monkeypatch.setattr(launcher.sys, "stdin", SimpleNamespace(isatty=lambda: False)) + with pytest.raises(RuntimeError, match="own terminal"): + launcher.main(["--execute"]) + attended.validator.dump_yaml.assert_not_called() + attended.run.assert_not_called() + + +def test_changed_native_state_after_confirmation_stops_before_approval(attended): + attended.preflight.side_effect = [ + (attended.ccr, {"foundation": {}}, "before"), + (attended.ccr, {"foundation": {}}, "changed"), + ] + with pytest.raises(RuntimeError, match="Application changed"): + launcher.main(["--execute"]) + attended.validator.dump_yaml.assert_not_called() + attended.run.assert_not_called() + + +def test_attended_scope_is_recorded_before_login_and_result_is_private(attended): + assert launcher.main(["--execute"]) == 0 + approval = next(attended.root.rglob("approval.json")) + outcome = approval.with_name("outcome.json") + assert approval.stat().st_mode & 0o777 == 0o600 + assert approval.parent.stat().st_mode & 0o777 == 0o700 + assert outcome.stat().st_mode & 0o777 == 0o600 + assert json.loads(outcome.read_text())["warden_exit_code"] == 0 + assert "whole exact record" in attended.requests[0]["rationale"] + assert attended.foundation.protected_application.call_count == 1 + with pytest.raises(FileExistsError): + launcher.main(["--execute"]) + assert attended.run.call_count == 1 + + +def test_unconfirmed_session_revocation_cannot_be_success(attended): + attended.run.side_effect = lambda *a, **kw: SimpleNamespace(returncode=13) + with pytest.raises(RuntimeError, match="Warden outcome requires inspection"): + launcher.main(["--execute"]) + outcome = next(attended.root.rglob("outcome.json")) + assert json.loads(outcome.read_text())["warden_exit_code"] == 13 + attended.foundation.protected_application.assert_not_called() + + +def test_preexisting_denied_or_resolved_decision_is_not_overwritten(attended): + attended.ccr["state_hub"] = {"decision_id": "existing"} + attended.validator.state_hub_get_json.return_value = {"status": "resolved"} + with pytest.raises(RuntimeError, match="Existing founder decision"): + launcher.main(["--execute"]) + assert not attended.requests + attended.run.assert_not_called() + + +def test_changed_review_digest_stops_before_native_reads(): + native = Mock() + foundation = SimpleNamespace(prepare=lambda: ({}, {}, "changed"), Native=native) + with pytest.raises(RuntimeError, match="Proposal changed"): + launcher.preflight(foundation) + native.assert_not_called() + + +def failed_login(attended): + attended.run.side_effect = lambda *a, **kw: SimpleNamespace(returncode=10) + with pytest.raises(RuntimeError, match="Warden outcome"): + launcher.main(["--execute"]) + attended.ccr["status"] = "approved" + attended.ccr["state_hub"] = {"decision_id": "fixed-test-decision"} + attended.validator.state_hub_get_json.return_value = { + "status": "resolved", + "decided_by": "Bernd Worsch", + "rationale": "APPROVE: exact " + launcher.EXPECTED, + } + return next(attended.root.rglob("outcome.json")).parent + + +def test_retry_uses_saved_approval_without_resolving_again(attended, monkeypatch): + root = failed_login(attended) + + def warden(args, **kwargs): + assert "smtp-private-browser" in kwargs["env"]["PATH"].split(":")[0] + receipt = Path(args[-1]) + receipt.write_text( + json.dumps( + { + "status": "foundation_admitted_projection_ready", + "proposal_digest": launcher.EXPECTED, + } + ) + + "\n" + ) + return SimpleNamespace(returncode=0) + + attended.run.side_effect = warden + monkeypatch.setattr("builtins.input", lambda _: pytest.fail("Approval requested twice")) + assert launcher.main(["--retry-login"]) == 0 + assert json.loads((root / "outcome.json").read_text())["warden_exit_code"] == 10 + assert json.loads((root / "login-retry-01/outcome.json").read_text())["warden_exit_code"] == 0 + assert len(attended.requests) == 1 + assert attended.validator.sync_state_hub_decision.call_count == 1 + with pytest.raises(FileExistsError): + launcher.main(["--retry-login"]) + + +@pytest.mark.parametrize("defect", ["child_started", "not_login_failure", "changed_approval"]) +def test_retry_refuses_partial_controller_or_changed_approval(attended, defect): + root = failed_login(attended) + if defect == "child_started": + (root / "foundation.jsonl").write_text("{}\n") + elif defect == "not_login_failure": + path = root / "outcome.json" + value = json.loads(path.read_text()) + value["warden_exit_code"] = 11 + path.write_text(json.dumps(value)) + else: + attended.validator.state_hub_get_json.return_value["rationale"] = "DENY: changed" + with pytest.raises(RuntimeError): + launcher.main(["--retry-login"]) + assert attended.run.call_count == 1 and not (root / "login-retry-01").exists() + + +def browser_module(): + path = SOURCE.parent / "smtp-private-browser/xdg-open" + spec = importlib.util.spec_from_loader( + "smtp_private_browser", SourceFileLoader("smtp_private_browser", str(path)) + ) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def test_private_browser_keeps_pkce_state_and_forces_fresh_login(): + from urllib.parse import parse_qs, urlencode, urlsplit + + module = browser_module() + query = { + "client_id": "openbao-admin", + "redirect_uri": "http://localhost:8250/oidc/callback", + "state": "synthetic-state", + "code_challenge": "synthetic-pkce", + "prompt": "none", + } + fresh = parse_qs( + urlsplit(module.fresh_url("https://kc.coulomb.social/authorize?" + urlencode(query))).query + ) + assert fresh == {**{k: [v] for k, v in query.items()}, "prompt": ["login"]} + + +@pytest.mark.parametrize( + "url", + [ + "https://unrelated.example/authorize?client_id=openbao-admin", + "https://kc.coulomb.social/authorize?client_id=other", + "https://kc.coulomb.social/unrelated?client_id=openbao-admin", + "http://kc.coulomb.social/authorize?client_id=openbao-admin", + ], +) +def test_private_browser_refuses_other_authorization_surfaces(url): + with pytest.raises(ValueError): + browser_module().fresh_url(url) diff --git a/workplans/CSOC-WP-0015-communication-runtime-acceptance.md b/workplans/CSOC-WP-0015-communication-runtime-acceptance.md index 080ca27..d22f679 100644 --- a/workplans/CSOC-WP-0015-communication-runtime-acceptance.md +++ b/workplans/CSOC-WP-0015-communication-runtime-acceptance.md @@ -8,7 +8,7 @@ status: active owner: codex topic_slug: coulomb-social created: "2026-10-08" -updated: "2026-10-31" +updated: "2026-11-01" origin: residual origin_ref: CSOC-WP-0010 state_hub_workstream_id: "38208f72-c285-56fa-be44-de9b59a4e031" @@ -18,6 +18,19 @@ Residual acceptance from CSOC-WP-0010, split at the user's request. Existing imp ## Current execution map β€” 2026-10-31 +2026-11-01 T02 follow-through: the user selected T02 for completion with minimal +human involvement and builder-stage efficiency. Fresh native generation 76 and +current-source checks supersede the old admission packet. All foundation targets +are still absent and all five sender server dry-runs pass. The exact refreshed +foundation and one-command founder approval/login are in +[current admission review](../docs/release/platform-mail-admission-review-2026-11-01.md). +The explicit selected-return operator, release and copied-evidence expiry now +have source/native-adapter implementation and actual owner-source rehearsal; +live read-only observation and the 744-case full gate pass. Builder policy is +approved in decision 112e14bb-4b5d-46d7-b365-84628f72f115; native acceptance remains +open. No credential admission, sender change, mail +submission, inbox access, suppression or expiry occurred during this preparation. + 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 @@ -54,6 +67,8 @@ flow verdicts remain T04. See docs/release/acceptance-audit-2026-10-27.md. ```task id: CSOC-WP-0015-T02 status: progress +needs_human: true +intervention_note: "Exact founder approval is recorded in decision 66c13f9d-73b8-4976-8c44-65a9f5ee04da. First Warden login timed out before controller handoff; native targets remain absent. Run the reviewed scripts/admit-platform-smtp.py --retry-login and sign in as platform-root in its fresh private browser. Return final JSON; no renewed admission approval is needed." depends_on: [RPF-WP-0068-T04, EMAIL-WP-0010-T04] priority: high state_hub_task_id: "0604cb15-72c7-5d15-bb42-0335d4ec80df" @@ -148,6 +163,83 @@ T03/T04. Evidence: This consumer task stays progress because sender/inbound/bounce and productive acceptance obligations are still open. +2026-11-01 builder follow-through: verified generation 76, unchanged image/sender/ +caller policy, absent foundation objects/target Secret and five successful server +dry-runs. Updated the platform foundation pin and sender adapter's exact foundation +digests. All 27 owner admission/controller cases and 270 email-connect cases pass; +the 21 existing optional owner harness skips remain. Current proposal is +sha256:d9f8785060db902f969e5d8d0917494858b2114e8f93ae812dda3a059c090844. +The new founder launcher records exact scope/owner review and canonical decision +sync in the founder terminal, invokes the existing contained Warden/controller, +retains private outcome evidence and refuses stale/replayed/uncertain attempts. +Ten launcher checks and its actual read-only native preflight pass. The initial +full make check passes 693 tests including required browsers. + +Independent return-handling work now provides a selected structured-DSN operator +with accepted Message-ID/recipient/caller/tenant/client correlation, invitation +fingerprint verification, one-row global suppression, explicit linked release, +durable intent, stopped-state reconciliation and previewed raw-copy expiry. +Twenty-seven isolated cases pass, including two real process deaths; the actual +owner-source rehearsal proves suppression across callers and release while all +five other ledger tables remain unchanged. No new HTTP caller or background mail +worker is needed for the proposed builder operating path. Review: +[selected return operator](../docs/release/mail-return-operator-review-2026-11-01.md). +Raw-copy 30-day retention and builder-phase metadata hold remain proposals. Native +transport/operation, sender rollout, delivered-message authentication, selected +inbox/reply/bounce evidence and policy/retention agreement remain required. T02 +stays progress; no closure or native grant is inferred from these preparations. + +Attended follow-through: founder decision 66c13f9d is resolved and CCR-2026-0056 is +approved. Warden's 21:32:04Z return is login_failed/exit 10, callback_timeout with no +helper, and revocation unconfirmed. Foundation journal is absent and renewed +native readback confirms every target absent and the exact application unchanged. +The user observed platform-root followed by tegwick OTP; issuer metadata records +an MFA failure. Synthetic Windows-to-WSL port 8250 checks pass for both loopback +names, so no callback transport change is justified. The reviewed --retry-login +path reuses the original exact approval, requires the proven pre-controller +failure, and opens a scoped Edge InPrivate/prompt=login request. It never repeats +the decision or an uncertain admission attempt. Nineteen launcher and 27 operator +checks pass. The operator-stage full gate passes 720 tests with required browsers; +an unrelated /tmp/.git artifact required private test roots under /var/tmp to +preserve the existing output privacy guard. Native identity/custody execution +remains open; no mailbox grant, sender change or mail submission is inferred. + +2026-11-01 native operator follow-through: scripts/mail_return_native.py now +connects the selected-return core to the existing email-connect receiver with +source-bound SSH/stdin transport, exact cluster/namespace/deployment/ReplicaSet/ +image/PVC/mount checks, installed-source verification and current caller-policy +binding. It supports read-only preparation, explicit one-row apply/release, +inspection and stopped-state reconciliation, plus exact-preview raw expiry and +lost-expiry-result reconciliation. No HTTP operator caller, service, standing +credential or mailbox scanner is added. Fourteen native-adapter assembly cases +and 28 core cases pass, including actual SQLite child processes. A parent-directory +fsync correction makes audit-directory creation durable before SQL intent, and +expiry holds attempts that stopped before their plan was written. + +The actual native read-only observation verifies generation 76, the pinned +image/source, mounted Bound PVC, sole Ready receiver and all six tables; 127 +verification records, two historical delivery receipts and one suppression are +present. There is no audit directory or invitation attempt fingerprint, so the +historical deliveries cannot authorize correlated invitation suppression. No +credential, mail, mailbox, SQL or operator-audit mutation occurred. Latest proof: +docs/release/native-mail-return-observation-2026-11-01-r1.json. Full gate: +744 passed, zero failures/errors/skips, including required browsers. Review: +[native return operator](../docs/release/native-mail-return-review-2026-11-01.md). +The builder operating-policy proposal is presented for explicit owner agreement; +the founder private-browser login retry remains outstanding. Native custody, +sender rollout and actual selected provider/inbox/return acceptance remain open. + +User operating-policy return: explicit "Approve this builder policy" is recorded +as resolved decision 112e14bb-4b5d-46d7-b365-84628f72f115. Bernd/platform-root owns +selected replies/bounces; reviewed correlated permanent bounces may suppress a +recipient across all callers/tenants, with explicit release. Copied raw returns +expire after 30 days; provider inbox and minimal decision metadata remain in +Bernd's custody through the builder phase, with disposition reviewed before +production. No automatic inbox scan/deletion is admitted. Do not ask for this +policy approval again. This operating agreement creates no credential reader or +HTTP caller. Native identity/custody, sender and actual provider/inbox/return +proof remain open; the private-browser login retry is still needed. + ## T03 β€” Complete independent ledger recovery and copy custody ```task