Humans
Argon2id-hashed passwords screened against Have I Been Pwned; TOTP MFA with single-use recovery codes.
Turning MFA on signs out every session established without it, including the current one. Sign in with Microsoft Entra ID (OIDC).
Named mechanisms, the tests that pin them, and a plain list of what is not built. No SOC 2 yet — we say so.
Every workspace has a per-workspace SHA-256 audit chain. Each row's hash covers the JCS-canonical row, its
sequence number and the previous row's hash; row 1 is anchored to a genesis secret held in Key Vault. The
hash and the sequence are computed inside the insert transaction by a serialised stored
procedure — application code never computes either — so concurrent writers cannot fork the chain
(Sixteen_concurrent_appends_chain_contiguously_without_forking).
The application's database identity is denied UPDATE and DELETE on the audit table — appends only
(AuditLogAppendOnlyGrantTests). That makes the log tamper-evident by
construction, not immutable: a database owner could still rewrite rows, and the daily verifier would then see
the break. The chain is re-verified on a daily schedule; a detected break or hash mismatch freezes the
workspace's write path in the same transaction and raises an integrity alert event
(AuditChainFreezeOnBreakTests).
Weekly ECDSA P-256 signed seals of each workspace's chain head, chained to the prior week's seal, with the
verification keys published at /.well-known/postmq-signing-keys. Public per-workspace
publication of the log is opt-in, toggled from the dashboard’s workspace transparency page.
Every session-state lifecycle event — sessions started and ended, decisions appended and corrected, backlog
filed, triaged, assigned and reprioritised, lessons and rules created and changed — is written into the same
tamper-evident chain as messaging: 38 of 123 event types. High-frequency touches such as pings and usage
reports are deliberately not audited. A workspace can browse its own chain in the dashboard — the Audit log page,
newest first, filtered by activity or by message — and read it over REST and MCP
(GET /v1/audit-log, browse_audit_log), keyset-paged over the sequence, with the
operator-significant families withheld. What that record is for — and what it deliberately is not — is on
the AI coding governance page.
Argon2id-hashed passwords screened against Have I Been Pwned; TOTP MFA with single-use recovery codes.
Turning MFA on signs out every session established without it, including the current one. Sign in with Microsoft Entra ID (OIDC).
Scoped, once-disclosed credentials per AI account: 19 issuable scopes, operational ≤ 365 days, administrative ≤ 90 days.
Rotate with an overlap window; revoking an account revokes its credentials, and the authenticator re-reads the account on every call.
Gated by what the action is, not uniformly: an operator decision a human makes — a notice, a sanctions call, a break-glass request or approval — needs a designated operator, a live session and a fresh MFA step-up.
A message-level override instead presents the DPoP-bound token two operators minted, and carries no
session of its own. Break-glass close needs a designated operator but no step-up; operator reads need
the operator scope, which the API will not put on a newly issued credential.
Passwords are Argon2id-hashed (64 MiB, t=3, p=4, per-password salt) and screened against Have I Been Pwned; API credentials and tokens are hashed with a Key Vault-held pepper. Passwords are not peppered. Sign in with Microsoft Entra ID (OIDC); Google and GitHub sign-in are scaffolded but not yet enabled. Sensitive self-service operations — publishing a policy, rectifying or erasing your own account — require a fresh MFA step-up, and the step-up filter runs before idempotency replay so a cached response can never be returned past a missing assertion.
Every agent is an AI account with its own scoped, once-disclosed credential: the plaintext is shown once at
issue and stored as a peppered HMAC; a client that loses it rotates. Every mutating route and MCP tool is
scope-gated, and the operator scope cannot be issued over the API.
Operator surfaces are REST and dashboard only — there are no operator MCP tools. A stolen bearer
credential cannot mutate anything as an operator: every operator mutation needs either a live human
session carrying the operator designation — a bearer has no human_id and is refused — or
the DPoP-bound break-glass token, which a bearer cannot present either. Operator reads need the
operator scope, which the API will not put on a newly issued credential. Publish a workspace policy and no agent can send until it has
acknowledged it; the acknowledgment is on the audit chain. Every operator decision writes an
operator-decision ledger row, and message overrides, hold releases and — where the decision resolves
to a workspace — notice and sanctions decisions also append a chain row in the same transaction as the
change it records, so a rollback drops both. The message overrides carry their ledger row's
decision_id in the chain row, so the two are cross-referable. Those workspace-visible
override rows deliberately carry an operator actor with no human id — the affected workspace learns
that an operator acted on its own message, never which operator, and full attribution is retained in
the restricted operator-decision ledger. One honest line: the break-glass request, approve and close
lifecycle also appends a chain row per affected workspace in the same transaction as its operator-table
write, but those rows are operator-significant and withheld from a workspace's own audit
view — a workspace does not learn from its own log that break-glass was opened over it
(the record lives on the chain and the restricted operator tables).
Notice-and-action; a takedown disables access on all three delivery paths, reversibly, so put-back restores it.
Every send is screened against OFAC's SDN list, fail-closed, with a human review queue and an operator decision.
Self-service export and erasure; erasure needs a fresh MFA step-up and freezes the workspace and crypto-shreds the standing-id salt in one transaction.
On every send, the sender and recipient workspaces are screened against OFAC's SDN list — Double Metaphone plus Levenshtein name matching, plus a country block — fail-closed: a screen that cannot run refuses the send, and hits go to a human review queue with an operator decision. Notice-and-action covers DSA Article 16 intake and DMCA counter-notice; a takedown disables access on all three delivery paths by a reversible timestamp, which is what makes put-back restorable. Erasure requires a fresh MFA step-up.
Outbound webhooks are HMAC-signed (X-PostMQ-Signature: t=,v1=,kid=) and SSRF-guarded:
destinations are checked at configuration and IP-pinned at every connect; private ranges, cloud metadata
endpoints, DNS rebinds and redirects are refused (HttpWebhookDeliverySenderTests). The outbox,
the retry schedule and the read-only webhook surface are on the handoffs page.
Secrets live in Azure Key Vault and are fetched with the app's managed identity; the core secrets — the
credential peppers, session signing keys, the anchor key and the audit genesis secret — are force-resolved
at boot, so a missing or malformed value fails the deployment at startup rather than on the first request
(FailFastSecretsBootTests). Hosted in Microsoft Azure, United States, single region — no EU
option and no multi-region. OpenTelemetry to Application Insights on the API and dashboard hosts, not the
MCP host. More than 4,000 automated tests; integration tests run against real SQL Server containers, not
mocks.
Every row is an entry in the site's claims register; the footnote is its id.
| what holds | how | scope |
|---|---|---|
| Every audit row is hashed into a per-workspace SHA-256 chain inside the insert transaction.C32 | A serialised stored procedure reads the previous hash, assigns the sequence and computes the row hash; application code never computes either, so concurrent writers cannot fork the chain. · Sixteen_concurrent_appends_chain_contiguously_without_forking | shipped |
| The application identity can only append to the audit table.C34 | DENY UPDATE and DENY DELETE on audit_log for the data-plane SQL user, checked three ways by the grant script. Tamper-evident by construction — a database owner could still rewrite rows, and the verifier would then see the break. · AuditLogAppendOnlyGrantTests | shipped |
| The chain is re-verified daily; a break freezes the workspace.C33 | A daily sweep re-verifies every active workspace end to end; a break or hash mismatch freezes the write path in the same transaction and raises an integrity alert event (a Critical structured log line). The alert rule that would route it to a person is not provisioned. · AuditChainFreezeOnBreakTests | shipped |
| Weekly ECDSA P-256 seals of each workspace’s chain head; verification keys published.C35 | Each complete ISO week’s chain head is sealed and signed with a dedicated ECDSA P-256 key, chained to the prior seal; keys are served at /.well-known/postmq-signing-keys. Public per-workspace publication is opt-in; the toggle is in the dashboard, under the workspace’s transparency settings. · TransparencyDeployConfigTests | shipped |
| Session-state lifecycle events are on the same chain as messaging.C19 | 38 of 123 audit event types are session-state lifecycle events — sessions started and ended, decisions appended and corrected, backlog filed and triaged, lessons and rules changed. Pings, usage reports and retrieval-count bumps are deliberately not audited. · AuditEventTypes.cs | shipped |
| Scoped, once-disclosed credentials per AI account.C36 | 19 issuable scopes across an operational tier (lifetime ≤ 365 days) and an administrative tier (≤ 90 days); the plaintext is shown once and stored as a peppered HMAC; rotate with an overlap window; revoking an account cascades to its credentials, and the authenticator re-reads the account on every call. · CredentialAccountLifecycleRaceTests | shipped |
| A workspace policy blocks send until the agent has acknowledged it.C55 | Publish a policy and every agent’s send returns POSTMQ_POLICY_NOT_ACKNOWLEDGED until it has retrieved and acknowledged the current version; the acknowledgment is a row on the audit chain. · PoliciesEndpoint.cs · AcknowledgePolicyService.cs | shipped |
| Passwords are Argon2id-hashed and screened against Have I Been Pwned.C37 | 64 MiB, t=3, p=4, per-password salt, PHC-serialised so the work factor can be raised; new passwords are checked over the HIBP k-anonymity range API. Passwords are not peppered — API credentials and tokens are, under a Key Vault-held pepper. · Argon2HumanPasswordHasher.cs · HibpPasswordBreachCheck.cs | shipped |
| TOTP MFA with single-use recovery codes; enabling MFA signs out every session established without it.C38 | Ten single-use recovery codes shown once and stored as a peppered HMAC; turning MFA on revokes every session that was established without the second factor, including the current one. · AccountSecurityMfaInteractionTests | shipped |
| Sign in with Microsoft Entra ID (OIDC).C39 | Configuration-gated; the deployed template carries the Entra authority and client id. Google and GitHub sign-in are scaffolded but not yet enabled. No SAML, no SCIM. · DashboardSsoLoginTests | shipped |
| Operator access is gated by what the action is, not uniformly.C40 | An operator decision a human makes — a DSA or DMCA notice, a sanctions call, a break-glass request or approval — needs a designated operator, a live human session and a fresh MFA step-up (and break-glass request and approval must be different operators). A message-level override instead presents the DPoP-bound token they minted (RFC 9449) and carries no session of its own; break-glass close needs a designated operator but no step-up; operator reads need the operator scope, which the API will not put on a newly issued credential. Operator surfaces are REST and dashboard only — no operator MCP tools. Every operator decision writes an operator-decision ledger row; message overrides, hold releases and — where the decision resolves to a workspace — notice and sanctions decisions also append a chain row in the same transaction as the change it records, so a rollback drops both, and the message overrides carry their ledger row’s decision_id so the two are cross-referable. The workspace-visible override rows carry an operator actor with no human id, so the affected workspace sees that an operator acted but not which one; full attribution stays in the restricted ledger. The break-glass request, approve and close lifecycle also appends a chain row per affected workspace in the same transaction as its operator-table write, but those rows are operator-significant and withheld from the workspace’s own audit view — a workspace does not learn from its own log that break-glass was opened over it. · RequireStepUpFilter.cs · OperatorDpopVerifier.cs | shipped |
| Outbound webhooks are HMAC-signed and SSRF-guarded.C29 | X-PostMQ-Signature: t=,v1=,kid=; destinations are checked at configuration and IP-pinned at every connect; private ranges, cloud metadata endpoints, DNS rebinds and redirects are refused. · HttpWebhookDeliverySenderTests | shipped |
| DSA / DMCA notice-and-action, with reversible takedown and put-back.C41 | A takedown disables access on all three delivery paths (pull, webhook, read) by a reversible timestamp gating the statement that hands the bytes over; a build guard fails if a new path forgets the gate. · TakedownDisablesDeliveryTests | shipped |
| Every send is screened against OFAC’s SDN list, fail-closed.C42 | Double Metaphone plus Levenshtein name matching plus a country block, on sender and recipient workspaces; a screen that cannot run refuses the send; hits go to a human review queue with an operator decision. · SanctionsSendGateTests | shipped |
| Self-service export and erasure.C43 | GET /v1/me/export is synchronous; erasure requires a fresh MFA step-up and, in one transaction, soft-deletes the human, freezes the workspace and crypto-shreds the standing-id salt. · HumanErasureEndpointTests | shipped |
| Secrets live in Azure Key Vault and are read with managed identity; core secrets fail fast at boot.C44 | The credential peppers, session signing keys, the anchor key and the audit genesis secret are force-resolved at startup, so a missing or malformed value fails the deployment then, not on the first request. · FailFastSecretsBootTests | shipped |
| Hosted in Microsoft Azure, United States, single region.C45 | Container Apps, Azure SQL, Key Vault, Storage, Log Analytics; no EU option, no multi-region, no zone redundancy. · infra/main.bicep | shipped |
| OpenTelemetry to Application Insights on the API and dashboard hosts.C53 | Traces, metrics and logs from the API and dashboard; the MCP host has no telemetry wiring yet, and no alert rule routes any event to a person. · Api/Program.cs · Web/Program.cs | shipped |
| More than 4,000 automated tests; integration tests run on real SQL Server containers.C46 | One shared container builder; concurrency and lock-ordering tests ask SQL Server’s own wait DMVs rather than timing heuristics. · SqlTestcontainerWiringTests · LeaseRaceTests | shipped |
The address, what to expect, the safe-harbour terms and the scope — on a page you can read without an account.
Email [email protected]. Include enough detail to reproduce: the affected host or endpoint, the request that demonstrates the issue, and what you observed versus what you expected. If a proof of concept touches data, describe it rather than attaching it. Please do not open a public issue for a security report.
What to expect.
There is no bug bounty. We cannot pay for reports today. Saying so plainly is fairer than leaving it ambiguous.
Safe harbour.
If you make a good-faith effort to comply with this policy while researching, we will not pursue or support legal action against you for that research, and we will treat it as authorised under the Computer Fraud and Abuse Act and equivalent laws. If a third party brings action against you for research conducted under this policy, we will make that authorisation clear. Good faith means:
In scope: api.postmq.com (the REST API), app.postmq.com (the
dashboard), mcp.postmq.com (the MCP server), postmq.com (this site), and this
product's source code.
Out of scope, because a report about them cannot lead to a fix here:
Stated first, in our own words, and not softened.
No SOC 2 yet, no SLA, no penetration test on record, and no written incident-response procedure. The security contact is published: the reporting address, the disclosure process and the safe-harbour terms are on this page, and Q52 below answers it in full. What an organisation must accept alongside this list — people added one at a time with no directory sync, one retention window for everybody, and audit-log reads that return the verifier's verdict rather than the hashes — is on the enterprise page. In full:
Version 1.2. Every Yes and every Partial names the files, tests or pull requests that make it true; a gap is stated first. Paths are repository-relative.
as of commit 88f9ead20718 (main, 2026-09-04) · 35 Yes · 16 Partial · 9 No · 1 Not yet
Yes No Compromise AI, LLC, a Delaware limited liability company doing business as PostMQ. The contracting documents that name it are drafts (Q59).
Evidence: docs/legal/terms-of-service.md, docs/marketing/SITE-PLAN.md
No PostMQ is in developer preview. There is no availability commitment, no uptime figure and no status page (Q39); the paid tiers, when they exist, sell a support response and not an SLA.
Evidence: docs/marketing/SITE-PLAN.md
Yes Microsoft Azure, United States, single region. Container Apps, Key Vault, Storage, Log Analytics and Application Insights deploy into the resource group's region (East US in the deployed environment); Azure SQL is parameterised to Central US. Data does not leave the United States. No EU or multi-region option exists (Q40).
Evidence: infra/main.bicep (location, sqlLocation), docs/legal/transfer-impact-assessment.md
Partial The list is maintained here and is not yet published on a page of its own. At this commit the subprocessors are: Microsoft Azure (compute, SQL, Key Vault, Storage, monitoring, and Azure Communication Services for transactional e-mail — data location UnitedStates); Cloudflare (DNS for the postmq zones, the edge for the postmq.com marketing site and for the transparency-log worker); Have I Been Pwned (the Pwned Passwords range API — only the first five hex characters of a SHA-1 hash leave the server, never the password); the U.S. Treasury OFAC sanctions list service (a download of the public SDN list; no customer data is sent); and, only when an operator configures an API key, Anthropic (the LLM enrichment and detection sweeps ship with no key configured and are inert without one).
Evidence: infra/main.bicep (acs, emailService), site/REDIRECTS.md, src/PostMQ.Infrastructure/Authentication/HibpPasswordBreachCheck.cs, src/PostMQ.Infrastructure/Sanctions/HttpSanctionsListSource.cs, src/PostMQ.Infrastructure/SessionState/Llm/SessionStateLlmOptions.cs, SweepDefaultEnablementTests
No PostMQ is offered as a hosted service only. The MCP server can be run over stdio for a co-located deployment, but it still needs a connection to the hosted database.
Evidence: docs/marketing/SITE-PLAN.md
Yes Every Yes and Partial cites a file, a test, a template resource or a pull request; a guard test in the required build fails if a cited path or test disappears, and the commit reference at the top is checked against the checkout. Source access for a review is arranged through the security contact.
Evidence: tests/PostMQ.Tooling.Tests/SecurityQuestionnaireTests.cs, .github/rulesets/main-required-checks.json
Yes Argon2id with a per-password random salt, 64 MiB memory, 3 iterations, 4 lanes (the OWASP baseline), serialised as a PHC string that carries its own parameters so the work factor can be raised later. Passwords are not peppered; API credentials, refresh tokens, reset tokens and recovery codes are (Q17).
Evidence: src/PostMQ.Infrastructure/Authentication/Argon2HumanPasswordHasher.cs
Yes New passwords are checked against Have I Been Pwned's Pwned Passwords over the k-anonymity range API; the deployed environment sets the mode to Hibp, and whether a lookup failure fails open or closed is a configuration choice (FailClosed).
Evidence: src/PostMQ.Infrastructure/Authentication/HibpPasswordBreachCheck.cs, infra/main.bicep (passwordBreachCheckMode)
Yes TOTP; the enrolment secret is wrapped with AES-256-GCM under a dedicated MFA master key (sourced from Key Vault at deploy time as a container secret, separate from every other master); ten single-use recovery codes of sixteen Crockford characters (about 80 bits) are shown once and stored as a peppered HMAC. Turning MFA on revokes every session that was established without the second factor — including the one that turned it on — so nobody stays signed in on the strength of a password alone.
Evidence: src/PostMQ.Infrastructure/Authentication/MfaService.cs, src/PostMQ.Infrastructure/Authentication/MfaRecoveryCodeService.cs, src/PostMQ.Infrastructure/Authentication/ConfiguredMfaSecretProtector.cs, DashboardMfaTests, AccountSecurityMfaInteractionTests, #467, #444
No MFA is enrolled per human. There is no workspace-level policy that forces enrolment. The sensitive self-service operations, and the two break-glass steps that authorise an operator override, do require a fresh MFA step-up regardless (Q14).
Partial Sign in with Microsoft Entra ID (OIDC) is built and configuration-gated; the deployed template carries the Entra authority and client id. Google and GitHub sign-in are scaffolded but not yet enabled — the provider list enumerates them, the redirect builds, and the callback fails closed. No SAML. No SCIM. There is no per-workspace SSO configuration; the identity provider is set per environment.
Evidence: src/PostMQ.Infrastructure/Authentication/EntraOidcExternalIdpVerifier.cs, src/PostMQ.Infrastructure/Authentication/DeferredExternalIdpVerifier.cs, src/PostMQ.Infrastructure/Authentication/ConfiguredSsoAvailability.cs, infra/main.bicep (ssoEntraAuthority, ssoEntraClientId), DashboardSsoLoginTests
Yes Sessions are signed tokens (kid-selected signing keys held in Key Vault; two keys are configured so a rotation verifies old tokens while issuing under the new key), with refresh tokens hashed like credentials. Revocation is written to the database and picked up by every replica by a poll, not by a restart; logout-all revokes every session for the human.
Evidence: src/PostMQ.Infrastructure/Authentication/SessionTokenService.cs, src/PostMQ.Infrastructure/Authentication/RefreshTokenService.cs, src/PostMQ.Infrastructure/Authentication/KeyVaultSessionSigningKeyProvider.cs, src/PostMQ.Api/Authentication/SessionRevocationPoll.cs, #482, #485
Partial There is a per-IP authentication request limiter, a per-account login backoff, an MFA verification lockout and a signup-creation limiter. All four are in-process — each replica keeps its own counters — so the protection is per replica, not cluster-wide.
Evidence: src/PostMQ.Infrastructure/RateLimiting/InProcessAuthRequestRateLimiter.cs, src/PostMQ.Infrastructure/Authentication/InProcessPerAccountLoginBackoff.cs, src/PostMQ.Infrastructure/Authentication/InProcessMfaLockout.cs, src/PostMQ.Infrastructure/Authentication/InProcessSignupCreationLimiter.cs, DashboardAuthThrottleTests, DashboardPerAccountBackoffTests, DashboardSignupCreationLimitTests
Partial Publishing a workspace policy, rectifying or erasing your own account, and the two break-glass steps that authorise an operator override (§13.1 request, §13.2 approval) require an X-PostMQ-Step-Up-Assertion header, as do the DSA/DMCA notice and sanctions review decisions. Break-glass close, the message-level overrides and the operator reads do NOT — see Q15 for what gates each. The assertion is: an RFC 9470-shaped assertion, RS256-signed, acr=mfa, a 300-second lifetime, bound to the calling session so an API bearer can never satisfy it. The step-up filter runs before idempotency replay, so a cached response cannot be returned past a missing assertion.
Evidence: src/PostMQ.Api/Authorization/RequireStepUpFilter.cs, src/PostMQ.Infrastructure/Authentication/StepUpAssertionService.cs, src/PostMQ.Api/Policies/PoliciesEndpoint.cs, src/PostMQ.Api/Dsr/HumanErasureEndpoint.cs
Partial The operator surface is gated by what the action is, not uniformly, so "all operator access needs X" is false whichever X you pick. An operator decision a human makes — a DSA or DMCA notice decision, a sanctions review decision, a §13.1 break-glass request, a §13.2 approval — runs RequireAuthorization -> RequireStepUp -> RequireOperator: a designated operator (an Entra security group in the deployed environment, or a configured list), a live human dashboard session, and a fresh MFA step-up; break-glass request and approval must additionally be different operators. Break-glass CLOSE needs a designated operator on a live session but deliberately no step-up. Operator READS (queue depth, the preservation-hold list, the notice and sanctions queues) need only a bearer carrying the operator scope, which is not issuable over the API. Message-level overrides (force-ack, force-nack, force-expire, force-revisible, bulk cancel, dead-letter replay, hold release) are reached differently and it is worth being exact: those seven endpoints carry no session authorization at all — the DPoP-bound break-glass token (RFC 9449, ES256, jti replay table, with a quota) IS the credential. The three checks are enforced on the two steps that MINT that token: the §13.1 break-glass request and the §13.2 approval each run RequireAuthorization -> RequireStepUp -> RequireOperator, and they must be different operators. So two step-up-authenticated designated operators stand behind every override — but the override request itself presents a token, and its verifier does not re-check that the minting session is still live. Operator surfaces are REST and dashboard only — there are no operator MCP tools — so a stolen bearer credential cannot mutate anything as an operator: every operator mutation requires either a live human session carrying the operator designation — RequireOperator refuses a bearer, which has no human_id — or the DPoP-bound break-glass token, which a bearer cannot present either. The operator reads require the operator scope, which ScopeCatalog.NotIssuableViaApi withholds from credential ISSUANCE over the API. This now holds for rotation too, not just issuance: POST /v1/credentials/{id}/rotate runs the successor's scopes through the same ScopeCatalog.IsIssuableViaApi check and refuses (403 POSTMQ_SCOPE_NOT_ISSUABLE) an operator-scoped credential rather than minting a fresh operator plaintext over the API (backlog 01a018a4), so the scope genuinely never comes out of the API — an operator credential is re-provisioned out of band, never rotated over it. Hold-release decisions are rows on the audit chain, as are notice and sanctions decisions where the decision resolves to a workspace — a DSA decision with no affected workspace, and a sanctions decision on an ai_account, recipient or notifier subject, append none (Q29). The five single-envelope operator message overrides — force-ack, force-nack, force-expire, force-revisible and dead-letter replay — now ALSO emit a message.*_by_operator audit-chain row in the same transaction as their operator-decision row (a rollback drops both), so they are on the tamper-evident chain and visible to the affected workspace — which learns THAT an operator acted on its own message, but not the acting operator's identity (that stays in the restricted operator-decision ledger). Bulk cancel now also emits chain rows — one message.cancelled_by_operator per cancelled envelope on that envelope's own workspace chain, plus a bulk_cancel.executed batch summary, atomic with each batch's transitions. The break-glass request, approve and close lifecycle now writes to the audit chain — one operator-significant, WITHHELD chain row per affected workspace, in the same transaction as its operator-table write; that emission spans the request's set of affected workspaces, each row anchored to its own per-workspace chain. Because the rows are withheld, a workspace does not learn from its own audit that break-glass was opened over it.
Evidence: src/PostMQ.Api/Authorization/RequireOperatorFilter.cs, src/PostMQ.Infrastructure/Operator/EntraGroupOperatorDirectory.cs, src/PostMQ.Infrastructure/Operator/BreakGlassApproveService.cs, src/PostMQ.Infrastructure/Operator/OperatorDpopVerifier.cs, src/PostMQ.Api/Operator/, src/PostMQ.Infrastructure/Operator/OperatorMessageOverride.cs, OperatorDesignationDeployConfigTests
Yes Signup is geofenced against the OFAC comprehensive-sanctions jurisdictions (CU, IR, KP, SY), and the deployed template names a blocked country code explicitly. The check is on the signup path only; it is not a sanctions screen of message traffic (Q55 covers that).
Evidence: src/PostMQ.Infrastructure/Sanctions/SanctionsCountryPolicy.cs, src/PostMQ.Application/Authentication/IGeofencePolicy.cs, infra/main.bicep (blockedCountryCode)
Yes Every agent is an AI account inside a workspace and holds its own bearer credential, disclosed once at issue and stored as a peppered HMAC-SHA256 under a Key Vault-held pepper. Credentials are scoped: nineteen issuable scopes across an operational tier (lifetime at most 365 days) and an administrative tier (at most 90 days); the operator scope exists in the catalogue but cannot be issued over the API. Every mutating route and MCP tool is scope-gated; there is no anonymous write path.
Evidence: src/PostMQ.Infrastructure/Accounts/CredentialIssueService.cs, src/PostMQ.Application/Authentication/ScopeCatalog.cs, src/PostMQ.Infrastructure/Authentication/CredentialFactory.cs, src/PostMQ.Infrastructure/Authentication/KeyVaultPepperProvider.cs, src/PostMQ.Api/Accounts/CredentialsEndpoint.cs
Yes Rotation mints a successor and schedules the predecessor's revocation after an overlap window (zero overlap when the predecessor is flagged as requiring rotation). Revocation is immediate on the row; revoking an AI account cascades to its credentials, and the authenticator re-reads the parent account on every call so a revoked account's credentials stop working even if a cascade lagged. Revocation is signalled to every replica by a poll on a 30-second interval, so cross-replica lag is <= 30 seconds by design (TRD §6) — now stated publicly on /docs/auth. What is not built is per-revocation CONFIRMATION: nothing acknowledges that a given replica has applied a given revocation, so the bound is a design property rather than an observed one. The bound applies to CREDENTIAL revocation only; revoking the parent AI account, freezing it, or deleting the workspace takes effect on the very next request, because the account and workspace are re-read from SQL on every call.
Evidence: src/PostMQ.Infrastructure/Accounts/CredentialLifecycleService.cs, src/PostMQ.Infrastructure/Accounts/AiAccountRevokeService.cs, src/PostMQ.Infrastructure/Authentication/CredentialAuthenticator.cs, src/PostMQ.Hosting/Sweeps/CredentialRevocationPoll.cs, CredentialAccountLifecycleRaceTests
Yes Token buckets per credential (per minute: send 100, get_pending 60, validate 50) and per workspace (1000), with 2× burst capacity, surfaced as X-RateLimit-* and Retry-After headers; identical over REST and MCP; replicas reconcile their buckets every 60 seconds through a shared table.
Evidence: src/PostMQ.Application/RateLimiting/RateLimitOptions.cs, src/PostMQ.Infrastructure/RateLimiting/TokenBucket.cs, src/PostMQ.Infrastructure/RateLimiting/RateLimitBucketSyncSweep.cs, src/PostMQ.Api/RateLimiting/RateLimitEndpointFilter.cs, src/PostMQ.Mcp/Middleware/McpRateLimitGuard.cs
Yes Every mutating endpoint and tool takes a mandatory idempotency key, retained 24 hours; the request is fingerprinted over its JCS-canonical body, a replay returns the original response bytes, and a key reused with a different body is refused rather than replayed. REST and MCP share one store.
Evidence: src/PostMQ.Infrastructure/Idempotency/IdempotencyStore.cs, src/PostMQ.Api/Idempotency/IdempotencyEndpointFilter.cs, src/PostMQ.Mcp/Middleware/McpIdempotencyCoordinator.cs, IdempotencyFingerprintCompositionGuardTests
Yes Public ingress is HTTPS on Azure Container Apps with managed certificates (SNI) bound to app.postmq.com and mcp.postmq.com; the template does not enable insecure ingress. Azure SQL is configured with minimalTlsVersion: '1.2'; the storage account requires TLS 1.2 and HTTPS-only. Cloudflare holds DNS for the postmq zones and fronts the static marketing site. The template does not pin a minimum TLS version on the Container Apps ingress itself — that is the platform default (TLS 1.2), not a setting we assert.
Evidence: infra/main.bicep (apiApp, webApp, mcpApp, sql, sa), site/REDIRECTS.md
Yes by Azure platform default, service-managed keys. Azure SQL Database enables transparent data encryption on every new database (the template neither sets nor disables it); Azure Storage encrypts all blobs service-side; Key Vault stores its secrets encrypted at rest (standard tier). No customer-managed keys and no private endpoints are offered. No repository test asserts the SQL encryption state — it is a platform property, verifiable in the portal or from sys.dm_database_encryption_keys.
Evidence: infra/main.bicep (sqlDb, sa, kv); https://learn.microsoft.com/azure/azure-sql/database/transparent-data-encryption-tde-overview
Yes Every high-value secret (credential pepper, session signing keys, the messaging anchor key, the audit genesis secret, the standing-id salt, the transparency signing key, the SSO client secret, the e-mail connection string) lives in Azure Key Vault (enableRbacAuthorization: true, soft-delete 7 days, audit logs to Log Analytics) and is read by each app's system-assigned managed identity under the Key Vault Secrets User role (the MFA and webhook-signing masters are supplied from the same vault at deploy time as container secrets). The core secrets are force-resolved at boot, so a missing or malformed value fails the deployment at startup rather than on the first request. Repository guards and a diff-scoped secret scan keep secrets out of source. Two honest limits: Key Vault public network access is enabled (firewall, no private endpoint), and purge protection is not set.
Evidence: infra/main.bicep (kv, kvDiagnostics), src/PostMQ.Hosting/SecretBootValidation.cs, FailFastSecretsBootTests, KeyVaultIdentityScopeTests, NoHardcodedSecretsWiringTests, RazorHardcodedSecretTests, GitleaksConfigTests
Partial The credential pepper is versioned — version 2 is active and version 1 stays configured so existing rows verify (rotation stage 1, #480), with a runbook and a re-hash sweep; the session signing key is kid-selected with k2 active and k1 verifying (#482, #485). The audit genesis secret is deliberately never rotated (it anchors the chain). Rotation of the transparency signing key and the anchor key has not been exercised, and no rotation schedule is committed.
Evidence: infra/main.bicep, docs/runbooks/pepper-rotation.md, src/PostMQ.Infrastructure/Authentication/PepperRotationSweep.cs, PepperRotationReadinessTests, src/PostMQ.Infrastructure/Audit/KeyVaultWorkspaceGenesisSecretProvider.cs
Partial Message payloads are purged 30 days after acceptance (daily sweep); a message's own TTL defaults to 7 days and is capped at 30; idempotency records live 24 hours; Log Analytics keeps telemetry 30 days. Audit rows are retained indefinitely at present — the specified 7-year / 30-day split and the cold-tier archive are not built, and there is no audit-row deletion path either. Erasure of a human account is self-service (Q57).
Evidence: src/PostMQ.Hosting/Sweeps/PeriodicSweepOptions.cs, src/PostMQ.Infrastructure/Messaging/SendMessageService.cs, src/PostMQ.Infrastructure/Idempotency/IdempotencyStore.cs, infra/main.bicep (law)
Partial Azure SQL Database takes automated backups with point-in-time restore for the last seven days by platform default; the template sets no retention period, no backup redundancy and no long-term retention, so the defaults apply. No restore has been rehearsed, no recovery-time or recovery-point objective is stated, and there is no disaster-recovery plan (Q40).
Evidence: infra/main.bicep (sqlDb); https://learn.microsoft.com/azure/azure-sql/database/automated-backups-overview
Yes JSON is canonicalised with JCS (RFC 8785) using the vendored reference implementation; the idempotency fingerprint, the stored payload_jcs_bytes and every audit row's hash input are JCS bytes.
Evidence: src/PostMQ.Canonicalization.Jcs/, src/PostMQ.Infrastructure/Audit/AuditRowCanonicalizer.cs, src/PostMQ.Api/Idempotency/IdempotencyEndpointFilter.cs
Yes Deliveries carry an HMAC signature header (t=, v1=, kid=); destinations are checked at configuration and re-resolved and IP-pinned at every connect; private ranges, cloud metadata endpoints, DNS rebinds and redirects are refused. Delivery is at-least-once through a transactional outbox with five jittered attempts.
Evidence: src/PostMQ.Infrastructure/Webhooks/HttpWebhookDeliverySender.cs, src/PostMQ.Infrastructure/Http/PinnedIpConnector.cs, src/PostMQ.Infrastructure/Webhooks/WebhookDestinationAllowlist.cs, HttpWebhookDeliverySenderTests, Send_rollback_does_not_persist_the_webhook_outbox_row
Yes Every workspace has a SHA-256 hash chain: each row's hash covers the JCS-canonical row, its sequence number and the previous row's hash, and row 1 is anchored to SHA-256(workspace_id ‖ genesis secret) with the secret in Key Vault. The hash and the sequence are computed inside the insert transaction by a serialised stored procedure — application code never computes either — so concurrent writers cannot fork the chain. It is tamper-evident, not immutable (Q30).
Evidence: src/PostMQ.Infrastructure/Audit/AuditChainWriter.cs, src/PostMQ.Data/Migrations/20260702193243_AuditChainSchema.cs, src/PostMQ.Infrastructure/Audit/KeyVaultWorkspaceGenesisSecretProvider.cs, Sixteen_concurrent_appends_chain_contiguously_without_forking, Stored_row_hash_matches_a_csharp_rederivation_over_the_stored_row
Partial The application's database identity is denied UPDATE and DELETE on the audit table — a per-user DENY, checked three ways by the grant script (table-level rows present, no column-level grant overriding them, no DDL-capable privilege that would allow TRUNCATE) — so the data plane can only append. The chain-head and support tables are still writable by the data plane, and a db_owner maintenance session is not itself logged; a privileged human with database ownership could rewrite rows, and the daily verifier would then detect the break. That is why the wording is tamper-evident and not "immutable".
Evidence: tools/grant-app-sql-users.sql, AuditLogAppendOnlyGrantTests
Yes A daily sweep re-verifies every active workspace's chain end to end. A break or hash mismatch freezes the workspace's write path in the same transaction that records the failed verification and raises an integrity alert event — a Critical structured log line under a stable event id (6501). That event is routed to a person by the Critical-log alert rule, which carries the audit-chain freeze as one of its named Alarm dimension values (Q41).
Evidence: src/PostMQ.Infrastructure/Audit/AuditChainVerifier.cs, src/PostMQ.Infrastructure/Audit/AuditChainVerifierSweep.cs, src/PostMQ.Hosting/Sweeps/PeriodicSweepOptions.cs, src/PostMQ.Infrastructure/Audit/LoggingAuditIntegrityAlertRaiser.cs, AuditChainFreezeOnBreakTests, AuditChainVerifierSweepTests, Concurrent_appends_do_not_cause_a_false_chain_break
Partial Each complete ISO week's chain head per workspace is sealed and ECDSA P-256 signed with a dedicated Key Vault key (separate from session signing) and chained to the prior week's seal; the verification keys are served at /.well-known/postmq-signing-keys. Public publication of a workspace's seals is opt-in per workspace, and there is not yet a user-facing toggle for it. The blob store is configuration-gated, the public host is not verified live, and the storage containers carry no immutability or legal-hold policy — so this is "signed and verifiable", not "immutable storage".
Evidence: src/PostMQ.Infrastructure/Transparency/TransparencySealSweep.cs, src/PostMQ.Infrastructure/Transparency/KeyVaultTransparencyLogSigner.cs, src/PostMQ.Infrastructure/Transparency/TransparencyPublicVisibility.cs, src/PostMQ.Api/WellKnown/SigningKeysEndpoint.cs, TransparencyDeployConfigTests
Yes A workspace browses its own chain in the dashboard (/audit-log, newest first, filtered by activity or by message; #498) and reads it over REST (GET /v1/audit-log, keyset over the sequence; #484) and MCP (browse_audit_log, the same query service; #496). All three are gated by one fail-closed family classifier, so a workspace cannot read the operator-significant rows that share its chain.
Evidence: src/PostMQ.Web/Components/Pages/AuditLog.razor, src/PostMQ.Api/Audit/AuditLogReadEndpoint.cs, src/PostMQ.Mcp/Tools/AuditLogTools.cs, src/PostMQ.Application/Audit/AuditLogVisibility.cs, AuditLogReadApiTests, AuditLogQueryServiceTests, #484, #496, #498
Partial Over one hundred event types: message lifecycle, account and credential lifecycle, human authentication and MFA, workspace lifecycle including closure at end of service, policy publication and acknowledgement, notice, sanctions and hold decisions, DSR actions, webhook outbox outcomes and webhook configuration changes (update, delete, signing-key rotation), plan-allowance events, and over thirty session-state lifecycle events (sessions, decisions, backlog, lessons, rules). The exact counts are derived from the catalogue and published on postmq.com rather than typed here, so this answer stays true as the catalogue grows. High-frequency touches such as pings and usage reports are deliberately not audited. The five single-envelope operator message overrides (force-ack/nack/expire/revisible, dead-letter replay) are now on the chain as message.*_by_operator events, emitted atomically with their operator-decision row. Bulk cancel is now on the chain too (per-envelope message.cancelled_by_operator + a bulk_cancel.executed batch summary). The break-glass request/approve/close lifecycle is on the chain too — one operator-significant, WITHHELD row per affected workspace, in the same transaction as its operator-table write, so a workspace does not learn from its own audit that break-glass was opened over it. Of the three steps, only approve also writes an operator-decision row (Q15).
Evidence: src/PostMQ.Application/Audit/AuditEventTypes.cs, src/PostMQ.Infrastructure/Operator/OperatorForceAckService.cs, src/PostMQ.Infrastructure/Operator/BreakGlassRequestService.cs
Yes a single shared database with strict workspace scoping. Every tenant row carries workspace_id; the large tables are hash-partitioned into sixteen partitions on a persisted partition_key derived from it, and bulk writes are partition-aligned. Every query is workspace-predicated by discipline and review; a build-time analyzer for that predicate is not built.
Evidence: src/PostMQ.Infrastructure/Partitioning/WorkspacePartitionKeys.cs, PartitionAlignedBulkWriteTests
Yes Every lock family has a cross-key race test: hold a row in workspace A, mutate workspace B, assert B does not block — which fails if a locking read ever scans instead of seeking. Lock parameters are typed (CHAR(26)), never interpolated, because an interpolated parameter once scanned other tenants' keys under a lock hint (measured, then fixed and pinned).
Evidence: AiAccountWorkspaceFreezeRaceTests, A_freeze_on_one_workspace_does_not_block_a_create_in_another, CredentialAccountLifecycleRaceTests, src/PostMQ.Infrastructure/Accounts/AiAccountLock.cs
No Recipients are AI accounts in the sender's own workspace; there is no cross-workspace messaging and no human recipient — humans see messages in the dashboard, they do not receive them.
Evidence: src/PostMQ.Infrastructure/Messaging/SendMessageService.cs
Yes A workspace freeze stops all agent traffic at the write-path authentication gate (403 POSTMQ_FROZEN). Freezes are applied by an operator, by owner erasure, and automatically when the audit chain fails verification.
Evidence: AuditChainFreezeOnBreakTests, src/PostMQ.Infrastructure/Dsr/HumanErasureService.cs, AiAccountWorkspaceFreezeRaceTests
No There is no availability SLA, no uptime figure and no status page. The service-level exhibit in the legal set is an unsigned draft with placeholder terms and does not bind anyone.
Evidence: docs/legal/sla-exhibit-a.md
No Single region; the database is zoneRedundant: false on a serverless General Purpose tier with auto-pause; storage is locally redundant (Standard_LRS). No multi-region, no zone redundancy, no geo-failover, no disaster-recovery plan.
Evidence: infra/main.bicep (sqlDb, sa)
Partial The API and dashboard hosts export OpenTelemetry traces, metrics and logs to Application Insights; the MCP host does not (no telemetry wiring). Key Vault audit logs flow to Log Analytics. Integrity failures and exhausted webhook deliveries raise alert events (Critical log lines with stable ids). Those events now reach a person: a scheduled-query alert rule fires on any Critical log from any host and routes to an enabled action group, splitting on an Alarm dimension so a named alarm arrives identified and anything new arrives as other_critical. It evaluates every five minutes over a fifteen-minute window, so a late-ingested row still lands in an evaluation. Verified live rather than inferred from the template: tools/check-critical-audit-alert.sh reports the deployed rule byte-identical to infra/main.bicep. It remains Partial, and the gaps are the honest part — the action group has a single email receiver, there is no on-call rota, no escalation and no acknowledgement tracking, and this is the postmq-dev environment, which is the only one that exists.
Evidence: src/PostMQ.Api/Program.cs, src/PostMQ.Web/Program.cs, infra/main.bicep (appi, law, kvDiagnostics, criticalAuditAlert), src/PostMQ.Infrastructure/Audit/LoggingAuditIntegrityAlertRaiser.cs, tools/check-critical-audit-alert.sh
Yes Continuous delivery runs from an Azure DevOps pipeline that stamps every revision with its commit and applies schema migrations; a scheduled monitor asks every three hours whether the deployed revision has fallen behind main (binary and schema), and a gate script answers "is commit X live?" on demand — with a shallow or inconclusive checkout reported as "cannot determine", never as "not live". A parity test pins the stamp names the pipeline writes to the names both scripts read.
Evidence: tools/check-dev-drift.sh, tools/is-commit-deployed.sh, .github/workflows/dev-deploy-drift.yml, CheckDevDriftTests, IsCommitDeployedTests, BuildStampParityTests, MigrationDeployStepTests
Yes The one sweep that destroys content irreversibly (the takedown purge) ships disabled and runs only where an environment sets it on; a test pins the exact membership of the default-disabled set in both directions and another pins the configuration key that enables it. Envelope rows and the audit chain survive a purge; only the bytes go.
Evidence: src/PostMQ.Infrastructure/Notices/DsaTakedownPurgeSweep.cs, SweepDefaultEnablementTests, TakedownPurgeEnableWiringTests, #470
Yes A ruleset requires four status checks on every merge — change-scope classification, build and test (format verification, Release build with warnings as errors and analyzers, the full test run), a diff-scoped gitleaks secret scan, and a public-copy hygiene check — with no force-push and no branch deletion. All CI runs on self-hosted runners; a guard workflow refuses hosted ones.
Evidence: .github/rulesets/main-required-checks.json, .github/workflows/ci.yml, .github/workflows/enforce-self-hosted-runners.yml, tools/check-public-copy-clean.sh
Yes Warnings are errors; thirteen first-party Roslyn analyzers (PMQ0001 through PMQ0020 in src/PostMQ.Analyzers) plus two .editorconfig-configured CA rules run at build-breaking severity, covering among others no hard-coded secrets, no bare catch blocks, no direct clock or random access, no untyped parameter under a lock hint, explicit string comparison and mandatory doc comments; a layering test enforces project dependencies; the analyzer set's own documentation is tied to the assembly by a guard.
Evidence: src/PostMQ.Analyzers/, LayerDependencyTests, AnalyzerGateTests, AnalyzerSeverityWiringTests, AnalyzerDocClaimTests
Yes 4,287 test cases at this commit across twelve test projects; integration tests run against real SQL Server containers, not mocks, from one shared container builder; concurrency and lock-ordering tests probe SQL Server's own wait DMVs rather than timing heuristics; whole-solution runs are known to under-report, so the discipline is to run the changed project alone.
Evidence: tests/Shared/Sql/PostMqSqlContainer.cs, SqlTestcontainerWiringTests, LeaseRaceTests, CredentialAccountLifecycleRaceTests
Yes A diff-scoped gitleaks scan is a required check, its configuration pinned by a test; a first-party analyzer rejects hard-coded secret literals in C#; separate guards cover Razor markup and the wiring of every secret-bearing option; the deployment scripts were fixed to never echo the secrets they are passed.
Evidence: .gitleaks.toml, GitleaksConfigTests, NoHardcodedSecretsWiringTests, RazorHardcodedSecretTests, #476
Partial This is a single-maintainer project: the ruleset requires zero approving reviews and auto-merge proceeds on green required checks. Every pull request is reviewed locally by an independent LLM code reviewer before it is opened, and its findings are triaged and fixed first; that review is not a CI check and does not count as an approval. Dependency vulnerability scanning and an SBOM are not in place (Q49).
Evidence: .github/rulesets/main-required-checks.json, CLAUDE.md
Not yet There is no automated dependency-vulnerability scan and no published SBOM. Package versions are pinned in the project files; the JCS reference implementation is vendored as a pinned submodule with an integrity check on its tarball.
Evidence: .github/workflows/ci.yml, src/PostMQ.Canonicalization.Jcs/
Yes Every commit on main carries a build-session trailer linking it to a recorded session; decisions go into an append-only decision log; deferred work is filed with a priority tier and the launch plan is generated from that ledger and fails closed on any open item without a tier.
Evidence: tools/generate-launch-plan.py, docs/LAUNCH-PLAN.md, CLAUDE.md
No No penetration test, no bug bounty and no third-party security assessment is on record.
Yes with one gap named. [email protected] is a provisioned shared mailbox, and SECURITY.md at the repository root publishes the disclosure process, an explicit safe-harbour statement, and in-scope / out-of-scope lists. Acknowledgement target is 3 business days — deliberately modest, because there is no 24/7 security on-call and a commitment that cannot be kept is worse than a slower one. There is no bug bounty, stated plainly rather than left ambiguous. Q53's procedure is now written but not rehearsed.
Evidence: SECURITY.md, docs/03-technical-requirements-document.md
Partial Written, not rehearsed. docs/runbooks/incident-response.md carries a three-level severity scheme, a triage window bounded at 24 hours from awareness, and the notification clocks side by side — the draft DPA's 48 hours from confirmation, GDPR Art. 33's 72 hours from awareness where PostMQ is controller, and Art. 34. It states which of those the confirmation instant starts and forbids extending triage to defer the clock, which is the DPA §5.1 term it implements. Containment levers, evidence handling and the Art. 33(5) register are covered. Three things are deliberately NOT claimed: it has never been exercised, in a drill or in anger; detection is its weakest part — security-incident alerting is email to a single mailbox and fires on Critical logs only (a second scheduled-query rule exists but is an informational business signal, not a security page), so nothing detects an unauthorised read that stays inside normal traffic; and there is no US state-law routing matrix, no settled EU supervisory-authority routing for a Delaware LLC with no EEA establishment, and no second responder — all of which the runbook names as open. Its final section lists every gap, and this answer is written to match that list rather than to imply the gaps are closed.
Evidence: docs/runbooks/incident-response.md, docs/legal/dpa.md, infra/main.bicep
Yes DSA Article 16 notice intake, review, decision with reasons and reclassification; DMCA counter-notice, court-action recording and operator clear; a put-back sweep. A takedown disables access on all three delivery paths (pull, webhook, read) by a reversible timestamp gating the exact statement that hands the bytes over — which is what makes §512(g) put-back restorable — and a build guard fails if a new delivery path forgets the gate. Notice acknowledgements and decisions are queued on a transactional outbox and sent by e-mail. There is no captcha on the public notice form.
Evidence: src/PostMQ.Infrastructure/Notices/, src/PostMQ.Api/Notices/, TakedownDisablesDeliveryTests, DmcaPutBackSweepTests, src/PostMQ.Infrastructure/Notices/DisabledCaptchaVerifier.cs, #353, #390, #473
Yes On every send, the sender and recipient workspaces are screened against OFAC's SDN list (Double Metaphone plus Levenshtein name matching, plus a country block), fail-closed — a screen that cannot run refuses the send — with a human review queue and operator decision. The deployed environment fetches the list from the Treasury list service; development and CI use a fixture.
Evidence: src/PostMQ.Infrastructure/Sanctions/SanctionsSendGate.cs, src/PostMQ.Infrastructure/Sanctions/HttpSanctionsListSource.cs, src/PostMQ.Infrastructure/Sanctions/SanctionsReviewDecisionService.cs, SanctionsSendGateTests, infra/main.bicep (sanctionsListSourceMode)
Yes A preservation hold set by a notice or an operator pins an envelope past every retention and purge path until an operator releases it; a reconcile sweep keeps the hold mirror consistent, and hold release is an operator override (Q15).
Evidence: src/PostMQ.Infrastructure/Operator/OperatorPreservationHoldReleaseService.cs, src/PostMQ.Infrastructure/Legal/LegalHoldReleasedSweep.cs, src/PostMQ.Infrastructure/Legal/PreservationHoldMirrorReconcileSweep.cs
Yes GET /v1/me (access) and GET /v1/me/export (portability) are self-service; rectification and erasure require a fresh MFA step-up. Erasure soft-deletes the human, freezes the workspace and crypto-shreds the standing-id salt in one transaction, then an anonymisation sweep completes the teardown. Export is synchronous — there are no asynchronous DSR jobs or signed download URLs.
Evidence: src/PostMQ.Infrastructure/Dsr/HumanDataAccessService.cs, src/PostMQ.Infrastructure/Dsr/HumanDataPortabilityService.cs, src/PostMQ.Infrastructure/Dsr/HumanErasureService.cs, src/PostMQ.Infrastructure/Standing/StandingIdSaltNonceShred.cs, HumanErasureEndpointTests, HumanExportEndpointTests
No There is no SOC 2 report, no ISO 27001 certificate, no HIPAA attestation, no PCI DSS attestation and no FedRAMP authorisation. The system is built to be audit-ready — the mechanisms in sections E through H are the evidence a future audit would examine — but nothing has been audited or certified.
Evidence: docs/legal/dpa.md
Partial Drafts of all three exist, together with a service-level exhibit, an acceptable-use policy, a DMCA policy, DSA contact pages and a transfer-impact assessment. They are AI-generated drafts, not counsel-signed, and carry open fact markers; none can be executed or relied on as binding until reviewed by counsel.
Evidence: docs/legal/README.md, docs/legal/dpa.md, docs/legal/terms-of-service.md, docs/legal/privacy-notice.md
Partial Built: Article 15 access, Article 16 rectification, Article 17 erasure with crypto-shred, Article 20 portability (Q57), and audit-chained terms acceptance. Not in place: no Data Privacy Framework certification, no EU representative, no EU hosting option (data is in the United States, Q3), no DMCA designated-agent registration, no NCMEC registration; the repeat-infringer policy is enforced by a sweep on a code default (three findings in 180 days) but has not been published as policy.
Evidence: src/PostMQ.Infrastructure/Dsr/, src/PostMQ.Application/Authentication/IPublishedTermsRegistry.cs, src/PostMQ.Application/Standing/RepeatOffenderOptions.cs, src/PostMQ.Infrastructure/Standing/RepeatOffenderEnforcementSweep.cs, docs/legal/dmca-designated-agent-filing.md, docs/legal/ncmec-cybertipline-registration.md
No None of the following has a committed date, and none is claimed as built. Not built: SCIM provisioning. Not built: SAML sign-in. Not enabled: Google and GitHub sign-in are scaffolded but not yet enabled (Q11). Not built: per-workspace SSO configuration, or a workspace-level MFA requirement (Q10). Not offered: an availability SLA. Not run: a status page (Q39). Not deployed: a second region (Q40). Not provisioned: alert rules and an on-call rota (Q41). Not built: a user-facing transparency-publication toggle (Q32). Not in place: dependency scanning and an SBOM (Q49). Not published: a security contact and disclosure policy (Q52). Not reviewed: the legal documents (Q59). Not obtained: a SOC 2 report (Q58).
Create a workspace, connect an agent over MCP, and read the chain your agents write.