Skip to content

NFR and evaluation release gates

Duckbill™ is not releasable until the service-quality gates in 03_NFR §16 have evidence. This page is the Stream B (MAIL-51 / MAIL-109 / MAIL-167) checklist. It is not the full MVP exit matrix (MAIL-49 / MAIL-62) and it is not the live-mailbox E2E (MAIL-50 / MAIL-103).

Recorded: 2026-09-26T19:52:01.969Z. Environment: local worktree feature/MAIL-109-nfr from origin/preview @ b886b6b, Node 26, ephemeral PGlite, 24 timed samples unless noted. ENABLE_SEND and ENABLE_DELETE stayed false. AI evaluation used the fixture classifier only — it never called a mail adapter.

Owners: agent = this Stream B change. host = Jason / human approval, live mailbox, or production promotion.

Checklist

GateResultOwnerEvidence
Replay / idempotency / out-of-order / cursorpassagenttests/application/initial-sync.test.ts, message-identity.test.ts, incremental-sync.test.ts, cursor-recovery.test.ts, tests/domain/ownership.test.ts, tests/persistence/operations.test.ts, tests/application/command-service.test.ts, tests/application/webhook-ingress.test.ts
Reconciliationpassagenttests/application/reconciliation.test.ts (3 tests)
Write-back convergencepassagenttests/application/writeback-convergence.test.ts, tests/application/command-service.test.ts
Privacy / logging (redaction + scan)passagenttests/privacy/redaction.test.ts, tests/privacy/token-secret-redaction.test.ts, tests/nfr/privacy-log-scan.test.ts
Restore exercisepassagenttests/ops/backup-restore.test.ts, fixtures/ops/restore-rehearsal-checklist.json
AI golden-setpassagentnpm run ai:eval, tests/ai/golden-set.test.ts
Performance p95 measured (local synthetic)passagenttests/nfr/performance-sample.test.ts vs 03_NFR §4
No known data-loss defect in automated suitespassagentReplay, identity, reconciliation, and restore suites created no duplicate or wiped canonical rows
Live-provider p95 under a real mailboxfailhostNot measured in this stream. Follow-up MAIL-217. Blocked by MAIL-103.
Free-text log fields (lastError) vs ciphertextpassagentMAIL-218: redactString now strips the project ciphertext prefix from free text. tests/privacy/redaction.test.ts, tests/privacy/token-secret-redaction.test.ts, tests/nfr/privacy-log-scan.test.ts.
Full release-docs matrixnot in scopehostMAIL-49 / MAIL-62. Cite this page; do not treat it as the matrix.

Failures become linked Jira issues. There are no silent waivers.

Replay, idempotency, cursor, reconciliation, write-back

Local Vitest on 2026-09-26: all listed files passed.

AI never calls adapters in these suites.

Privacy and logging

Redaction is always on. The composite scan in tests/nfr/privacy-log-scan.test.ts feeds a synthetic body, OAuth token, token ciphertext, client secret, and JWT through sync, provider, write-back, and ops loggers plus redactHealthDocument and buildModelPrompt. Named sensitive fields are [REDACTED]. The sink did not contain those needles.

Safe metadata (message id, folder, subject) may remain. Production mailbox content is forbidden in fixtures/.

MAIL-218: redactString treats a bare project ciphertext prefix (v1.aes256gcm. / v1.aesgcm.) as a secret when it appears in a non-sensitive field such as lastError. Named-field redaction is unchanged.

Restore exercise

fixtures/ops/restore-rehearsal-checklist.json is the disposable PGlite rehearsal artifact from MAIL-56 / MAIL-112. The suite re-ran the export/restore path on 2026-09-26:

This is not a production-mail restore.

AI golden-set

npm run ai:eval ran scripts/run-ai-eval.ts against tests/ai/golden/corpus.json with createFixtureClassifier: 12/12 passed, 0 schema-invalid, 0 critical regressions, 0 high-impact false positives, fixture cost $0. The Vitest self-test (tests/ai/golden-set.test.ts, 10 tests) passed. This is the 01_AI §11 / §14 gate, not a live-model latency test.

Performance sample vs 03_NFR §4

Isolated sample 2026-09-26T19:52:01.969Z; contended re-run during full npm test 2026-09-26T19:52:32.385Z (numbers in parentheses). Inclusive p95 via @inbox-agent/observability percentile. Do not read local pass as live-mailbox pass.

NFR targetp95 measuredResultEnvironment
API metadata read < 500 ms1.51 ms (5.78 ms) n=24passPGlite getById
Interactive list/search < 1.5 s4.67 ms (16.91 ms) n=48passPGlite listByAccount + in-memory triage home/search (80 conversations)
UI state-change ack < 1 s0.42 ms (9.51 ms) n=24passin-memory submitCommand (MarkRead; send/delete off)
Background event pickup < 30 s1.23 ms (2.72 ms) n=24passPGlite outbox insert + publishOutboxBatch
New-mail canonical availability < 2 min16.15 ms (88.15 ms) n=24pass (synthetic only)mock adapter + PGlite runIncrementalSync
AI enrichment < 5 min0–1 ms n=12 casespass (fixture only)fixture classifier case latencies

Live Microsoft Graph discovery-to-canonical p95 and live-model enrichment p95 were not measured. That row stays fail for representative production conditions.

Observability

Critical API and sync metrics already exist (MAIL-75): api.duration_ms p95, sync lag, queue depth/age, DLQ, reconciliation drift. Health documents are redacted before they leave the process. See operational health and privacy and logging.

Kill switches and flags

ENABLE_SEND and ENABLE_DELETE remain false. ENABLE_MICROSOFT_WRITES is only true inside write-back unit tests. PAUSE_AUTOMATION and PAUSE_AI stay default-off configuration keys.

Follow-up

Opened from this gate run:

File another linked issue when a later gate fails. Do not mark the live-provider p95 row pass because the local sample passed.