email-connect/docs/caller-policy-draft-2026-10-08.md
tegwick e9b9d1c708
Some checks failed
Transactional mail acceptance / mail (push) Failing after 16s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Activate scoped user-engine mail and finish EMAIL-WP-0008
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a11fd9-caf6-7a63-b578-9daaaa134ff2
2026-10-09 20:23:43 +02:00

11 KiB

User-engine caller policy draft — 2026-10-08

This is the historical draft review. The compatible rollout preparation below updates its scope and resolves the source compatibility gap; activation remains open.

This file proposes the only caller that exists today. It is not mounted, EMAIL_CONNECT_CALLERS_FILE is not set, and no token file was created. EMAIL-WP-0008-T03 stays waiting until the bindings, quotas and token custody are approved.

Source: config/callers.user-engine.draft.json.

What the live caller does

user-engine sends invitation mail to /v1/send with source user-engine and templates family_member.invited or family_invitation.resent. It reads /v1/status and /v1/delivery-status. Registration uses /v1/registration-verifications, consume and cancel. The live registration allow-list is tenant tenant:coulomb and client coulomb-social (USER_ENGINE_REGISTRATION_TENANTS / USER_ENGINE_REGISTRATION_CLIENTS in net-kingdom/sso-mfa/k8s/user-engine/runtime.yaml). Mail and verification share the current user-engine-delivery mail-token. Suppression is not a user-engine operation and is omitted.

Proposed limits

rate_per_minute 10 matches the live registration limit of 10 requests per 60 seconds. Burst 5 and an 8 KiB payload cap are below the receiver ceilings (6000/minute, 600 burst, 128 KiB). They are proposals, not an approved quota. adopt_legacy_verifications is proposed so this original caller can still consume verification rows written before caller ids existed.

Gap that blocks activation

Current invitation envelopes carry tenant and do not carry client_id. Scoped send accepts a binding only when both fields are present and admitted, or when both are absent and legacy_invitation_binding is set. A tenant without a client id is denied. Family invitations also use tenants other than tenant:coulomb. This draft therefore does not claim to carry current invitation traffic. Activation needs a user-engine change that sends an admitted tenant/client pair, or a separate reviewed contract change.

The token path in the draft does not exist. Selecting this file on the running receiver would fail startup before serving traffic. Do not copy the shared ingest token into that path as part of this draft.

Compatible rollout preparation — 2026-10-09

User selected the proposed limits and requested glob or regex support. The draft now admits exact tenant:coulomb/coulomb-social plus the proposed case-sensitive tenant:-family and tenant:family- globs with literal client coulomb-social. Only * and ? glob operators are supported; all patterns and actual identifiers are bounded to 128 characters, with at most 128 total exact/pattern pairs. These family naming patterns are proposals rather than a complete tenant inventory. They apply to verification creation and handle consumption/cancellation as well as invitation sends. Suppression remains excluded.

User-engine source accepts opt-in USER_ENGINE_MAIL_CLIENT_BINDINGS, a JSON list matching config/mail-client-bindings.user-engine.draft.json. It resolves one literal client for the actual tenant; missing or ambiguous matches refuse before any HTTP delivery. Only the mail transport gains client_id: audit envelopes and stored outbox payloads retain their original shape. Default unset configuration retains current live behavior. The reviewed patch is retained in deploy/caller-rollout/user-engine-mail-bindings.patch.

adopt_legacy_invitations explicitly reserves original invitation ids for this user-engine caller. A matching old fingerprint can tolerate the added transport client_id, preserving accepted evidence and terminal unknown/permanent outcomes. Other immutable changes still refuse; admission checks precede legacy matching. New events bind the full payload. Other callers cannot opt into this adoption.

The immutable ConfigMap and strategic deployment patch in deploy/caller-rollout pass server dry-run; packaged-evidence-2026-10-09.json binds the local candidate image to the exact caller module under UID10001 and disabled networking. Before activation, deliver a caller-specific token to the separately admitted Secret email-connect-caller-user-engine (key token) and corresponding user-engine custody lane. Do not copy the legacy shared ingest token into that projection. Publish/admit the new receiver image, retain the current ledger/recovery receipt, fence outbox dispatch during coordinated token/policy transition, and set both the receiver policy and user-engine binding configuration. Pending outbox tenants must match the admitted scope before traffic resumes; narrow or add explicit bindings when their names do not match these proposed globs.

The live receiver remains eb3e7b68 in single-caller mode. Capture and rehearsal must be repinned together only when a new receiver image is actually deployed. Policy narrowing/removal preserves ledger evidence but can deny existing links; review rollback with the compatible image and both caller credentials before releasing traffic. Source preparation does not issue a token or select this policy.

Published closure candidates — 2026-10-09

The immutable receiver candidate is forgejo.coulomb.social/coulomb/email-connect@sha256:5d79fd1fec15d3a279383820f170d263de3a55f5308df01a0a34732ebaea6d3d. The coordinated user-engine candidate is forgejo.coulomb.social/coulomb/user-engine@sha256:312dcb062b237c518148e1b4f09798a4e6ad08bc60d4add6834aab3f246f2002. It applies only the retained binding patch to the verified live source baseline 0d0751d, avoiding unrelated owner-repository changes. Baseline regression passes 302 tests with 20 baseline optional skips; the six packaged binding tests pass under UID10001/network-none. A read-only live query found no historical family invitation outbox rows. This does not replace scope checks at release time.

Six delivery objects and both immutable-image deployment patches in deploy/closure pass actual cluster server dry-run. Caller custody is a separate OpenBao leaf, platform/workloads/email-connect/callers/user-engine, field TOKEN, initial CAS0. The two dedicated namespace/ServiceAccount readers have exact leaf-only policies; no legacy ingest token is copied. scripts/admit-scoped-caller.py previews by default and silently installs exact policies/roles, creates or verifies the leaf with a five-minute bounded writer, and self-revokes that writer before its receipt. It refuses changed artifacts, differing existing roles/policies and unreviewed plan decisions. It does not apply workloads or create Kubernetes Secrets.

The reviewed plan hash is 8e529e2ffdb6ee3d53a768d65b2d025b95e788734c48a29f2f8b975825dbaed7. For an attended execution, bind a resolved User decision to this exact plan and use a fresh receipt under a mode-0700 parent:

BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 \
  warden access openbao-platform-admin-login --exec -- \
  python3 /home/worsch/email-connect/scripts/admit-scoped-caller.py --execute \
    --reviewed-sha256 8e529e2ffdb6ee3d53a768d65b2d025b95e788734c48a29f2f8b975825dbaed7 \
    --decision-id RESOLVED_USER_DECISION_UUID --receipt PRIVATE_RECEIPT_PATH

The environment orientation §5 requires the founder to run this in their own terminal because OIDC/MFA needs a browser and TTY: /home/worsch/the-custodian/docs/agent-environment-orientation.md. The child captures all secret output. Operator login and session revocation must be verified separately; Warden's exit code alone is insufficient.

After custody/reader proof, project both bounded secrets, freeze user-engine dispatch, capture the ledger, deploy the coordinated scoped pair, and prove health, denials/quotas and legacy handle continuity without sending mail. Repin backup capture/rehearsal together only after actual receiver activation and refresh the recovery kit. No new credential, image rollout or policy activation has occurred. T03 remains progress. Full email regression: 193 passed, zero skips.

Scoped activation complete — 2026-10-09

EMAIL-WP-0008 now runs the published scoped receiver and compatible user-engine images, with one Ready replica each. Decision 8edbff8c-f62d-4357-8465-8eb37300f311 admitted continuation of the reviewed package. The attended child installed both exact policies/roles and created/read back caller KV version 1, then failed because its writer self-revocation CLI command was invalid. User confirmed parent login revocation. Corrected to bao token revoke -self; a real CLI test against a synthetic local HTTP endpoint verifies that call. Failure receipts now retain only allow-listed diagnostic codes. No second administrator session was used.

Both approved ESO readers deliver equal credentials. Exact policy/900-second bounds and own-leaf read pass; sibling data, metadata, parent list, administrative policy read and a non-mutating CAS0 write to the existing leaf deny. Wrong bound identity, account and audience logins deny. Both proof tokens self-revoke and subsequent lookup denies. User-engine's invitation/registration credentials match its own projection and its binding JSON matches the reviewed exact/glob pairs.

Quiesced pre-release offsite capture version 1791569299633609 precedes coordinated activation. A probe initially rejected a newline in the retired legacy bearer; Python rendered that bearer in its error. Automatic rollback restored baseline, then dispatch was fenced and scoped mode restored immediately. The corrected contained probe strips/rejects invalid header characters. The live receiver now returns 401 for the exposed legacy bearer. New caller/provider credentials were not exposed. Platform-root notice: b78dfaf0-953e-410c-b9fb-0309af4c42ae. Never restore the retired bearer or the old single-caller rollback spec. Its custody record was not deleted; retirement is enforced by the active scoped policy and canonical receiver manifests. Productive provider traffic remains separately owned.

Live no-send probes verify forbidden suppression, foreign tenant, 8192-byte cap, burst five and preservation of both existing accepted deliveries. Non-quota ledger tables remain unchanged; SMTP authentication/database diagnostics pass. The synthetic recovery/package tests retain unknown-outcome, consumed/cancelled handle and legacy adoption coverage; those live state tables were empty.

Canonical receiver kustomization now retains the scoped image, immutable policy and exact receiver-side ESO projection and passes server dry-run. The user-engine activation patch remains deploy/closure/user-engine.patch.json. Capture/rehearsal pins are changed together in source and installed runtime to the actual scoped image. Fresh offsite version 1791569930973785 and isolated UID10001/network-none recovery preserve all six tables, including the populated caller quota bucket. A refreshed private kit bundles the image/tools/age ciphertext/public receipts and excludes the private age identity. It remains local, not independent escrow.

Public proofs: docs/scoped-caller-live-2026-10-09.json and docs/ledger-scoped-image-recovery-2026-10-09.json. Focused backup/notification/ custody regressions: 41 passed; focused syntax checks pass. Earlier full email regression remains 193 passed, zero skips. EMAIL-WP-0008-T03 is done. Independent Scaleway custody, generation retention and recurring backup activation remain EMAIL-WP-0007; backup timers stay disabled. Productive/browser sender/reply/bounce acceptance remains CSOC-WP-0015.