docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519) - #526

Merged
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr
Aug 6, 2026
Merged

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519)#526
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr

Conversation

@AndresL230

@AndresL230AndresL230 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Closes#519.

Option 2 from the issue: newsletter_emails.email stays plaintext, deliberately —
the UNIQUE constraint, lookup index, and both upserts (subscribe + admin allowlist)
key on the email value, which AES-GCM's per-call nonce breaks. ADR 0026 records the
decision and the rejected HMAC alternative; CLAUDE.md names the exception so the gap
is not re-filed. Also carries the approved design spec + implementation plan for the #522 stack.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Documented that newsletter email addresses remain plaintext to support uniqueness and reliable lookups.
    • Added an architectural decision record explaining the rationale, alternatives, and consequences of this exception.
    • Added an implementation plan and approved design for expanding encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.
    • Documented compatibility with existing plaintext data, staged backfills, testing, and administrative access requirements.

AndresL230and others added 3 commits August 5, 2026 15:35
Approved design for the four-PR stack: #519 plaintext-by-decision ADR,
#520 feedback encryption + admin read surface, #521 quiz JSON columns,
#518 derived content (flashcards, study guides, room summaries).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging064e44dCommit Preview URL

Branch Preview URL
Aug 05 2026, 10:55 PM

@supabase

supabaseBot commented Aug 5, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change documents newsletter_emails.email as an intentional plaintext exception and adds a design and implementation plan for encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.

Changes

Encryption coverage documentation

Layer / File(s)Summary
Newsletter plaintext exception
CLAUDE.md, docs/decisions/0026-newsletter-email-plaintext.md, docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation records the plaintext decision and its uniqueness and lookup constraints.
Feedback encryption and admin surfaces
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted feedback and issue-report fields, admin read endpoints, frontend rendering, tests, seeds, and backfills.
Quiz JSON encryption compatibility
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted quiz JSON writes, legacy plaintext reads, decryption boundaries, backfills, and validation.
Derived content encryption
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encryption and decryption for flashcards, study guides, and room summaries, including cache and deduplication behavior.
Rollout and completion runbook
docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation defines shared testing, ciphertext manifests, staged backfills, stacked-PR operations, and closeout criteria.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • Issue 522 — The change documents the newsletter plaintext exception described by this issue.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check nameStatusExplanationResolution
Description check⚠️ WarningThe description explains the decision and links #519, but it omits most required template sections, including Changes Made, Testing, and Notes for Reviewers.Add the required headings and complete the change summary, testing status, screenshots applicability, and reviewer notes.
Out of Scope Changes check⚠️ WarningThe #522 design specification and 1,446-line implementation plan extend beyond the directly linked #519 decision and acceptance criteria.Move the #522 specification and implementation plan to a separate PR, or link the relevant #522 issue and justify their inclusion here.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly identifies the newsletter plaintext decision ADR and the related encryption-gaps specification.
Linked Issues check✅ PassedThe PR selects option 2, documents its rationale in ADR 0026, preserves uniqueness and lookup behavior, and records the intentional exception in CLAUDE.md [#519].
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/522-a-519-newsletter-adr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md`:
- Line 1393: Update the expected ciphertext-column count in the oracle
validation text near the “journeys green; oracles exit 0” statement from 13 to
11, matching the documented additions and the count referenced later in the
plan.
- Around line 63-64: Update the pre-PR read-path statement near the
UNIQUE(email) discussion to clarify that, before PR B, no filters or search
operations read these columns; do not claim the application never read the
tables, since the new admin endpoints add application reads.
- Around line 857-862: Update the context decryption flow around
decrypt_json_column to stop converting every exception into an empty context
object. Remove the broad exception handler, or restrict it to documented
compatibility errors while emitting the required telemetry; preserve the
existing fallback for absent or validly empty context_json.
- Around line 494-500: Update the reports rendering in the reports.map block to
implement the screenshot-link contract: validate each r.screenshot_urls entry
and render links to the private screenshot URLs instead of displaying only the
count. If count-only behavior is intended, update the design specification and
response contract consistently instead.
- Around line 99-101: Update the fenced block containing the “Deliberate
exception” text to specify the text language tag, changing the untyped fence to
a text fence while preserving its contents.
- Around line 1380-1389: Update the locked E2E shell command around the make
e2e-up, Playwright, and e2e_oracles steps to guarantee make e2e-down runs on any
failure despite set -e. Install an EXIT trap before make e2e-up that performs
teardown, while preserving the original command failure status as the script’s
exit status.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 373b84fa-2ed1-425a-a53e-b549009534fc

📥 Commits

Reviewing files that changed from the base of the PR and between ec34bf1 and 064e44d.

📒 Files selected for processing (4)
  • CLAUDE.md
  • docs/decisions/0026-newsletter-email-plaintext.md
  • docs/superpowers/plans/2026-08-05-encryption-gaps.md
  • docs/superpowers/specs/2026-08-05-encryption-gaps-design.md

Comment on lines +63 to +64
breaks this table's `UNIQUE(email)` constraint, its lookup index, and both
upserts' conflict detection, silently converting dedupe into duplicate rows.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Qualify the pre-PR read-path statement.

Lines 51-60 add application reads through the new admin endpoints, but Line 63 says the app never reads these tables. Change this to “Before PR B, no filters or search read these columns” or equivalent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 63 - 64,
Update the pre-PR read-path statement near the UNIQUE(email) discussion to
clarify that, before PR B, no filters or search operations read these columns;
do not claim the application never read the tables, since the new admin
endpoints add application reads.

Comment on lines +99 to +101
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced block.

The block at Line 99 contains plain text. Use a text fence so markdownlint MD040 passes.

Proposed fix
-```+```text
Deliberate exception: `newsletter_emails.email` stays plaintext ...
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 99-99: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 99 - 101,
Update the fenced block containing the “Deliberate exception” text to specify
the text language tag, changing the untyped fence to a text fence while
preserving its contents.

Source: Linters/SAST tools

Comment on lines +494 to +500
{reports.map((r) => (
<div key={r.id} style={{ padding: "12px 16px", borderBottom: "1px solid var(--border)" }} data-testid="adminfb-issue-row">
<div style={{ display: "flex", gap: 12, color: "var(--text-dim)", fontSize: 13 }}>
<span>{when(r.created_at)}</span>
<span style={{ color: "var(--text)" }}>{r.user_name || r.user_id}</span>
<span>{r.topic}</span>
{r.screenshot_urls.length > 0 && <span>{r.screenshot_urls.length} screenshot(s)</span>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Implement the screenshot-link contract.

The proposed component displays only the screenshot count at Line 500. The design specification requires links to the private screenshot URLs. Render validated links, or change the specification and response contract to require count-only behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 494 - 500,
Update the reports rendering in the reports.map block to implement the
screenshot-link contract: validate each r.screenshot_urls entry and render links
to the private screenshot URLs instead of displaying only the count. If
count-only behavior is intended, update the design specification and response
contract consistently instead.

Comment on lines +857 to +862
from services.encryption import decrypt_json_column
cj = ctx.get("context_json") or {}
try:
cj = decrypt_json_column(cj) or {}
except Exception:
cj = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not hide all decryption failures as empty context.

The broad except Exception converts key mismatches, corrupted ciphertext, and programming errors into {}. Users then lose quiz context while the request appears successful. decrypt_json_column already handles the documented legacy shapes, so remove this catch or catch only documented compatibility errors and emit telemetry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 857 - 862,
Update the context decryption flow around decrypt_json_column to stop converting
every exception into an empty context object. Remove the broad exception
handler, or restrict it to documented compatibility errors while emitting the
required telemetry; preserve the existing fallback for absent or validly empty
context_json.

Comment on lines +1380 to +1389
flock /tmp/claude-1000/sapling-e2e-stack.lock bash -c '
set -e
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
status=$?
make e2e-down
exit $status

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guarantee teardown when a test command fails.

With set -e, a Playwright or oracle failure exits before status=$? and before make e2e-down. The local stack can remain running and poison later runs. Install an EXIT trap before make e2e-up, or disable errexit while capturing both command statuses.

Proposed fix
 set -e
+ trap 'rc=$?; make e2e-down || true; exit "$rc"' EXIT
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
- status=$?- make e2e-down- exit $status
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 1380 -
1389, Update the locked E2E shell command around the make e2e-up, Playwright,
and e2e_oracles steps to guarantee make e2e-down runs on any failure despite set
-e. Install an EXIT trap before make e2e-up that performs teardown, while
preserving the original command failure status as the script’s exit status.

'
```

Expected: journeys green; oracles exit 0 — the ciphertext oracle now proves all 13 new columns encrypted at rest in a really-running stack. Testing the tip covers B and C's changes too; if the tip needs a fix, re-run after fixing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the ciphertext-column count.

The listed additions total 11 columns: four feedback/issue columns, three quiz columns, and four derived-content columns. Line 1393 says 13, while Line 1437 says 11. Align the count so operators do not validate the oracle against the wrong coverage target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` at line 1393, Update
the expected ciphertext-column count in the oracle validation text near the
“journeys green; oracles exit 0” statement from 13 to 11, matching the
documented additions and the count referenced later in the plan.

@AndresL230
AndresL230 merged commit 104b573 into mainAug 6, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/522-a-519-newsletter-adr branch August 6, 2026 01:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

newsletter_emails.email is plaintext while users.email is encrypted — needs a decision, not a swap

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519) - #526

Merged
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr
Aug 6, 2026
Merged

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519)#526
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr

Conversation

@AndresL230

@AndresL230AndresL230 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Closes#519.

Option 2 from the issue: newsletter_emails.email stays plaintext, deliberately —
the UNIQUE constraint, lookup index, and both upserts (subscribe + admin allowlist)
key on the email value, which AES-GCM's per-call nonce breaks. ADR 0026 records the
decision and the rejected HMAC alternative; CLAUDE.md names the exception so the gap
is not re-filed. Also carries the approved design spec + implementation plan for the #522 stack.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Documented that newsletter email addresses remain plaintext to support uniqueness and reliable lookups.
    • Added an architectural decision record explaining the rationale, alternatives, and consequences of this exception.
    • Added an implementation plan and approved design for expanding encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.
    • Documented compatibility with existing plaintext data, staged backfills, testing, and administrative access requirements.

AndresL230and others added 3 commits August 5, 2026 15:35
Approved design for the four-PR stack: #519 plaintext-by-decision ADR,
#520 feedback encryption + admin read surface, #521 quiz JSON columns,
#518 derived content (flashcards, study guides, room summaries).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging064e44dCommit Preview URL

Branch Preview URL
Aug 05 2026, 10:55 PM

@supabase

supabaseBot commented Aug 5, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change documents newsletter_emails.email as an intentional plaintext exception and adds a design and implementation plan for encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.

Changes

Encryption coverage documentation

Layer / File(s)Summary
Newsletter plaintext exception
CLAUDE.md, docs/decisions/0026-newsletter-email-plaintext.md, docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation records the plaintext decision and its uniqueness and lookup constraints.
Feedback encryption and admin surfaces
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted feedback and issue-report fields, admin read endpoints, frontend rendering, tests, seeds, and backfills.
Quiz JSON encryption compatibility
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted quiz JSON writes, legacy plaintext reads, decryption boundaries, backfills, and validation.
Derived content encryption
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encryption and decryption for flashcards, study guides, and room summaries, including cache and deduplication behavior.
Rollout and completion runbook
docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation defines shared testing, ciphertext manifests, staged backfills, stacked-PR operations, and closeout criteria.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • Issue 522 — The change documents the newsletter plaintext exception described by this issue.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check nameStatusExplanationResolution
Description check⚠️ WarningThe description explains the decision and links #519, but it omits most required template sections, including Changes Made, Testing, and Notes for Reviewers.Add the required headings and complete the change summary, testing status, screenshots applicability, and reviewer notes.
Out of Scope Changes check⚠️ WarningThe #522 design specification and 1,446-line implementation plan extend beyond the directly linked #519 decision and acceptance criteria.Move the #522 specification and implementation plan to a separate PR, or link the relevant #522 issue and justify their inclusion here.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly identifies the newsletter plaintext decision ADR and the related encryption-gaps specification.
Linked Issues check✅ PassedThe PR selects option 2, documents its rationale in ADR 0026, preserves uniqueness and lookup behavior, and records the intentional exception in CLAUDE.md [#519].
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/522-a-519-newsletter-adr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md`:
- Line 1393: Update the expected ciphertext-column count in the oracle
validation text near the “journeys green; oracles exit 0” statement from 13 to
11, matching the documented additions and the count referenced later in the
plan.
- Around line 63-64: Update the pre-PR read-path statement near the
UNIQUE(email) discussion to clarify that, before PR B, no filters or search
operations read these columns; do not claim the application never read the
tables, since the new admin endpoints add application reads.
- Around line 857-862: Update the context decryption flow around
decrypt_json_column to stop converting every exception into an empty context
object. Remove the broad exception handler, or restrict it to documented
compatibility errors while emitting the required telemetry; preserve the
existing fallback for absent or validly empty context_json.
- Around line 494-500: Update the reports rendering in the reports.map block to
implement the screenshot-link contract: validate each r.screenshot_urls entry
and render links to the private screenshot URLs instead of displaying only the
count. If count-only behavior is intended, update the design specification and
response contract consistently instead.
- Around line 99-101: Update the fenced block containing the “Deliberate
exception” text to specify the text language tag, changing the untyped fence to
a text fence while preserving its contents.
- Around line 1380-1389: Update the locked E2E shell command around the make
e2e-up, Playwright, and e2e_oracles steps to guarantee make e2e-down runs on any
failure despite set -e. Install an EXIT trap before make e2e-up that performs
teardown, while preserving the original command failure status as the script’s
exit status.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 373b84fa-2ed1-425a-a53e-b549009534fc

📥 Commits

Reviewing files that changed from the base of the PR and between ec34bf1 and 064e44d.

📒 Files selected for processing (4)
  • CLAUDE.md
  • docs/decisions/0026-newsletter-email-plaintext.md
  • docs/superpowers/plans/2026-08-05-encryption-gaps.md
  • docs/superpowers/specs/2026-08-05-encryption-gaps-design.md

Comment on lines +63 to +64
breaks this table's `UNIQUE(email)` constraint, its lookup index, and both
upserts' conflict detection, silently converting dedupe into duplicate rows.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Qualify the pre-PR read-path statement.

Lines 51-60 add application reads through the new admin endpoints, but Line 63 says the app never reads these tables. Change this to “Before PR B, no filters or search read these columns” or equivalent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 63 - 64,
Update the pre-PR read-path statement near the UNIQUE(email) discussion to
clarify that, before PR B, no filters or search operations read these columns;
do not claim the application never read the tables, since the new admin
endpoints add application reads.

Comment on lines +99 to +101
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced block.

The block at Line 99 contains plain text. Use a text fence so markdownlint MD040 passes.

Proposed fix
-```+```text
Deliberate exception: `newsletter_emails.email` stays plaintext ...
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 99-99: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 99 - 101,
Update the fenced block containing the “Deliberate exception” text to specify
the text language tag, changing the untyped fence to a text fence while
preserving its contents.

Source: Linters/SAST tools

Comment on lines +494 to +500
{reports.map((r) => (
<div key={r.id} style={{ padding: "12px 16px", borderBottom: "1px solid var(--border)" }} data-testid="adminfb-issue-row">
<div style={{ display: "flex", gap: 12, color: "var(--text-dim)", fontSize: 13 }}>
<span>{when(r.created_at)}</span>
<span style={{ color: "var(--text)" }}>{r.user_name || r.user_id}</span>
<span>{r.topic}</span>
{r.screenshot_urls.length > 0 && <span>{r.screenshot_urls.length} screenshot(s)</span>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Implement the screenshot-link contract.

The proposed component displays only the screenshot count at Line 500. The design specification requires links to the private screenshot URLs. Render validated links, or change the specification and response contract to require count-only behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 494 - 500,
Update the reports rendering in the reports.map block to implement the
screenshot-link contract: validate each r.screenshot_urls entry and render links
to the private screenshot URLs instead of displaying only the count. If
count-only behavior is intended, update the design specification and response
contract consistently instead.

Comment on lines +857 to +862
from services.encryption import decrypt_json_column
cj = ctx.get("context_json") or {}
try:
cj = decrypt_json_column(cj) or {}
except Exception:
cj = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not hide all decryption failures as empty context.

The broad except Exception converts key mismatches, corrupted ciphertext, and programming errors into {}. Users then lose quiz context while the request appears successful. decrypt_json_column already handles the documented legacy shapes, so remove this catch or catch only documented compatibility errors and emit telemetry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 857 - 862,
Update the context decryption flow around decrypt_json_column to stop converting
every exception into an empty context object. Remove the broad exception
handler, or restrict it to documented compatibility errors while emitting the
required telemetry; preserve the existing fallback for absent or validly empty
context_json.

Comment on lines +1380 to +1389
flock /tmp/claude-1000/sapling-e2e-stack.lock bash -c '
set -e
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
status=$?
make e2e-down
exit $status

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guarantee teardown when a test command fails.

With set -e, a Playwright or oracle failure exits before status=$? and before make e2e-down. The local stack can remain running and poison later runs. Install an EXIT trap before make e2e-up, or disable errexit while capturing both command statuses.

Proposed fix
 set -e
+ trap 'rc=$?; make e2e-down || true; exit "$rc"' EXIT
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
- status=$?- make e2e-down- exit $status
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 1380 -
1389, Update the locked E2E shell command around the make e2e-up, Playwright,
and e2e_oracles steps to guarantee make e2e-down runs on any failure despite set
-e. Install an EXIT trap before make e2e-up that performs teardown, while
preserving the original command failure status as the script’s exit status.

'
```

Expected: journeys green; oracles exit 0 — the ciphertext oracle now proves all 13 new columns encrypted at rest in a really-running stack. Testing the tip covers B and C's changes too; if the tip needs a fix, re-run after fixing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the ciphertext-column count.

The listed additions total 11 columns: four feedback/issue columns, three quiz columns, and four derived-content columns. Line 1393 says 13, while Line 1437 says 11. Align the count so operators do not validate the oracle against the wrong coverage target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` at line 1393, Update
the expected ciphertext-column count in the oracle validation text near the
“journeys green; oracles exit 0” statement from 13 to 11, matching the
documented additions and the count referenced later in the plan.

@AndresL230
AndresL230 merged commit 104b573 into mainAug 6, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/522-a-519-newsletter-adr branch August 6, 2026 01:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

newsletter_emails.email is plaintext while users.email is encrypted — needs a decision, not a swap

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519) - #526

Merged
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr
Aug 6, 2026
Merged

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519)#526
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr

Conversation

@AndresL230

@AndresL230AndresL230 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Closes#519.

Option 2 from the issue: newsletter_emails.email stays plaintext, deliberately —
the UNIQUE constraint, lookup index, and both upserts (subscribe + admin allowlist)
key on the email value, which AES-GCM's per-call nonce breaks. ADR 0026 records the
decision and the rejected HMAC alternative; CLAUDE.md names the exception so the gap
is not re-filed. Also carries the approved design spec + implementation plan for the #522 stack.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Documented that newsletter email addresses remain plaintext to support uniqueness and reliable lookups.
    • Added an architectural decision record explaining the rationale, alternatives, and consequences of this exception.
    • Added an implementation plan and approved design for expanding encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.
    • Documented compatibility with existing plaintext data, staged backfills, testing, and administrative access requirements.

AndresL230and others added 3 commits August 5, 2026 15:35
Approved design for the four-PR stack: #519 plaintext-by-decision ADR,
#520 feedback encryption + admin read surface, #521 quiz JSON columns,
#518 derived content (flashcards, study guides, room summaries).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging064e44dCommit Preview URL

Branch Preview URL
Aug 05 2026, 10:55 PM

@supabase

supabaseBot commented Aug 5, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change documents newsletter_emails.email as an intentional plaintext exception and adds a design and implementation plan for encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.

Changes

Encryption coverage documentation

Layer / File(s)Summary
Newsletter plaintext exception
CLAUDE.md, docs/decisions/0026-newsletter-email-plaintext.md, docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation records the plaintext decision and its uniqueness and lookup constraints.
Feedback encryption and admin surfaces
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted feedback and issue-report fields, admin read endpoints, frontend rendering, tests, seeds, and backfills.
Quiz JSON encryption compatibility
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted quiz JSON writes, legacy plaintext reads, decryption boundaries, backfills, and validation.
Derived content encryption
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encryption and decryption for flashcards, study guides, and room summaries, including cache and deduplication behavior.
Rollout and completion runbook
docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation defines shared testing, ciphertext manifests, staged backfills, stacked-PR operations, and closeout criteria.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • Issue 522 — The change documents the newsletter plaintext exception described by this issue.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check nameStatusExplanationResolution
Description check⚠️ WarningThe description explains the decision and links #519, but it omits most required template sections, including Changes Made, Testing, and Notes for Reviewers.Add the required headings and complete the change summary, testing status, screenshots applicability, and reviewer notes.
Out of Scope Changes check⚠️ WarningThe #522 design specification and 1,446-line implementation plan extend beyond the directly linked #519 decision and acceptance criteria.Move the #522 specification and implementation plan to a separate PR, or link the relevant #522 issue and justify their inclusion here.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly identifies the newsletter plaintext decision ADR and the related encryption-gaps specification.
Linked Issues check✅ PassedThe PR selects option 2, documents its rationale in ADR 0026, preserves uniqueness and lookup behavior, and records the intentional exception in CLAUDE.md [#519].
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/522-a-519-newsletter-adr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md`:
- Line 1393: Update the expected ciphertext-column count in the oracle
validation text near the “journeys green; oracles exit 0” statement from 13 to
11, matching the documented additions and the count referenced later in the
plan.
- Around line 63-64: Update the pre-PR read-path statement near the
UNIQUE(email) discussion to clarify that, before PR B, no filters or search
operations read these columns; do not claim the application never read the
tables, since the new admin endpoints add application reads.
- Around line 857-862: Update the context decryption flow around
decrypt_json_column to stop converting every exception into an empty context
object. Remove the broad exception handler, or restrict it to documented
compatibility errors while emitting the required telemetry; preserve the
existing fallback for absent or validly empty context_json.
- Around line 494-500: Update the reports rendering in the reports.map block to
implement the screenshot-link contract: validate each r.screenshot_urls entry
and render links to the private screenshot URLs instead of displaying only the
count. If count-only behavior is intended, update the design specification and
response contract consistently instead.
- Around line 99-101: Update the fenced block containing the “Deliberate
exception” text to specify the text language tag, changing the untyped fence to
a text fence while preserving its contents.
- Around line 1380-1389: Update the locked E2E shell command around the make
e2e-up, Playwright, and e2e_oracles steps to guarantee make e2e-down runs on any
failure despite set -e. Install an EXIT trap before make e2e-up that performs
teardown, while preserving the original command failure status as the script’s
exit status.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 373b84fa-2ed1-425a-a53e-b549009534fc

📥 Commits

Reviewing files that changed from the base of the PR and between ec34bf1 and 064e44d.

📒 Files selected for processing (4)
  • CLAUDE.md
  • docs/decisions/0026-newsletter-email-plaintext.md
  • docs/superpowers/plans/2026-08-05-encryption-gaps.md
  • docs/superpowers/specs/2026-08-05-encryption-gaps-design.md

Comment on lines +63 to +64
breaks this table's `UNIQUE(email)` constraint, its lookup index, and both
upserts' conflict detection, silently converting dedupe into duplicate rows.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Qualify the pre-PR read-path statement.

Lines 51-60 add application reads through the new admin endpoints, but Line 63 says the app never reads these tables. Change this to “Before PR B, no filters or search read these columns” or equivalent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 63 - 64,
Update the pre-PR read-path statement near the UNIQUE(email) discussion to
clarify that, before PR B, no filters or search operations read these columns;
do not claim the application never read the tables, since the new admin
endpoints add application reads.

Comment on lines +99 to +101
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced block.

The block at Line 99 contains plain text. Use a text fence so markdownlint MD040 passes.

Proposed fix
-```+```text
Deliberate exception: `newsletter_emails.email` stays plaintext ...
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 99-99: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 99 - 101,
Update the fenced block containing the “Deliberate exception” text to specify
the text language tag, changing the untyped fence to a text fence while
preserving its contents.

Source: Linters/SAST tools

Comment on lines +494 to +500
{reports.map((r) => (
<div key={r.id} style={{ padding: "12px 16px", borderBottom: "1px solid var(--border)" }} data-testid="adminfb-issue-row">
<div style={{ display: "flex", gap: 12, color: "var(--text-dim)", fontSize: 13 }}>
<span>{when(r.created_at)}</span>
<span style={{ color: "var(--text)" }}>{r.user_name || r.user_id}</span>
<span>{r.topic}</span>
{r.screenshot_urls.length > 0 && <span>{r.screenshot_urls.length} screenshot(s)</span>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Implement the screenshot-link contract.

The proposed component displays only the screenshot count at Line 500. The design specification requires links to the private screenshot URLs. Render validated links, or change the specification and response contract to require count-only behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 494 - 500,
Update the reports rendering in the reports.map block to implement the
screenshot-link contract: validate each r.screenshot_urls entry and render links
to the private screenshot URLs instead of displaying only the count. If
count-only behavior is intended, update the design specification and response
contract consistently instead.

Comment on lines +857 to +862
from services.encryption import decrypt_json_column
cj = ctx.get("context_json") or {}
try:
cj = decrypt_json_column(cj) or {}
except Exception:
cj = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not hide all decryption failures as empty context.

The broad except Exception converts key mismatches, corrupted ciphertext, and programming errors into {}. Users then lose quiz context while the request appears successful. decrypt_json_column already handles the documented legacy shapes, so remove this catch or catch only documented compatibility errors and emit telemetry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 857 - 862,
Update the context decryption flow around decrypt_json_column to stop converting
every exception into an empty context object. Remove the broad exception
handler, or restrict it to documented compatibility errors while emitting the
required telemetry; preserve the existing fallback for absent or validly empty
context_json.

Comment on lines +1380 to +1389
flock /tmp/claude-1000/sapling-e2e-stack.lock bash -c '
set -e
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
status=$?
make e2e-down
exit $status

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guarantee teardown when a test command fails.

With set -e, a Playwright or oracle failure exits before status=$? and before make e2e-down. The local stack can remain running and poison later runs. Install an EXIT trap before make e2e-up, or disable errexit while capturing both command statuses.

Proposed fix
 set -e
+ trap 'rc=$?; make e2e-down || true; exit "$rc"' EXIT
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
- status=$?- make e2e-down- exit $status
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 1380 -
1389, Update the locked E2E shell command around the make e2e-up, Playwright,
and e2e_oracles steps to guarantee make e2e-down runs on any failure despite set
-e. Install an EXIT trap before make e2e-up that performs teardown, while
preserving the original command failure status as the script’s exit status.

'
```

Expected: journeys green; oracles exit 0 — the ciphertext oracle now proves all 13 new columns encrypted at rest in a really-running stack. Testing the tip covers B and C's changes too; if the tip needs a fix, re-run after fixing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the ciphertext-column count.

The listed additions total 11 columns: four feedback/issue columns, three quiz columns, and four derived-content columns. Line 1393 says 13, while Line 1437 says 11. Align the count so operators do not validate the oracle against the wrong coverage target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` at line 1393, Update
the expected ciphertext-column count in the oracle validation text near the
“journeys green; oracles exit 0” statement from 13 to 11, matching the
documented additions and the count referenced later in the plan.

@AndresL230
AndresL230 merged commit 104b573 into mainAug 6, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/522-a-519-newsletter-adr branch August 6, 2026 01:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

newsletter_emails.email is plaintext while users.email is encrypted — needs a decision, not a swap

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519) - #526

Merged
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr
Aug 6, 2026
Merged

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519)#526
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr

Conversation

@AndresL230

@AndresL230AndresL230 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Closes#519.

Option 2 from the issue: newsletter_emails.email stays plaintext, deliberately —
the UNIQUE constraint, lookup index, and both upserts (subscribe + admin allowlist)
key on the email value, which AES-GCM's per-call nonce breaks. ADR 0026 records the
decision and the rejected HMAC alternative; CLAUDE.md names the exception so the gap
is not re-filed. Also carries the approved design spec + implementation plan for the #522 stack.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Documented that newsletter email addresses remain plaintext to support uniqueness and reliable lookups.
    • Added an architectural decision record explaining the rationale, alternatives, and consequences of this exception.
    • Added an implementation plan and approved design for expanding encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.
    • Documented compatibility with existing plaintext data, staged backfills, testing, and administrative access requirements.

AndresL230and others added 3 commits August 5, 2026 15:35
Approved design for the four-PR stack: #519 plaintext-by-decision ADR,
#520 feedback encryption + admin read surface, #521 quiz JSON columns,
#518 derived content (flashcards, study guides, room summaries).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging064e44dCommit Preview URL

Branch Preview URL
Aug 05 2026, 10:55 PM

@supabase

supabaseBot commented Aug 5, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change documents newsletter_emails.email as an intentional plaintext exception and adds a design and implementation plan for encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.

Changes

Encryption coverage documentation

Layer / File(s)Summary
Newsletter plaintext exception
CLAUDE.md, docs/decisions/0026-newsletter-email-plaintext.md, docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation records the plaintext decision and its uniqueness and lookup constraints.
Feedback encryption and admin surfaces
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted feedback and issue-report fields, admin read endpoints, frontend rendering, tests, seeds, and backfills.
Quiz JSON encryption compatibility
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted quiz JSON writes, legacy plaintext reads, decryption boundaries, backfills, and validation.
Derived content encryption
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encryption and decryption for flashcards, study guides, and room summaries, including cache and deduplication behavior.
Rollout and completion runbook
docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation defines shared testing, ciphertext manifests, staged backfills, stacked-PR operations, and closeout criteria.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • Issue 522 — The change documents the newsletter plaintext exception described by this issue.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check nameStatusExplanationResolution
Description check⚠️ WarningThe description explains the decision and links #519, but it omits most required template sections, including Changes Made, Testing, and Notes for Reviewers.Add the required headings and complete the change summary, testing status, screenshots applicability, and reviewer notes.
Out of Scope Changes check⚠️ WarningThe #522 design specification and 1,446-line implementation plan extend beyond the directly linked #519 decision and acceptance criteria.Move the #522 specification and implementation plan to a separate PR, or link the relevant #522 issue and justify their inclusion here.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly identifies the newsletter plaintext decision ADR and the related encryption-gaps specification.
Linked Issues check✅ PassedThe PR selects option 2, documents its rationale in ADR 0026, preserves uniqueness and lookup behavior, and records the intentional exception in CLAUDE.md [#519].
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/522-a-519-newsletter-adr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md`:
- Line 1393: Update the expected ciphertext-column count in the oracle
validation text near the “journeys green; oracles exit 0” statement from 13 to
11, matching the documented additions and the count referenced later in the
plan.
- Around line 63-64: Update the pre-PR read-path statement near the
UNIQUE(email) discussion to clarify that, before PR B, no filters or search
operations read these columns; do not claim the application never read the
tables, since the new admin endpoints add application reads.
- Around line 857-862: Update the context decryption flow around
decrypt_json_column to stop converting every exception into an empty context
object. Remove the broad exception handler, or restrict it to documented
compatibility errors while emitting the required telemetry; preserve the
existing fallback for absent or validly empty context_json.
- Around line 494-500: Update the reports rendering in the reports.map block to
implement the screenshot-link contract: validate each r.screenshot_urls entry
and render links to the private screenshot URLs instead of displaying only the
count. If count-only behavior is intended, update the design specification and
response contract consistently instead.
- Around line 99-101: Update the fenced block containing the “Deliberate
exception” text to specify the text language tag, changing the untyped fence to
a text fence while preserving its contents.
- Around line 1380-1389: Update the locked E2E shell command around the make
e2e-up, Playwright, and e2e_oracles steps to guarantee make e2e-down runs on any
failure despite set -e. Install an EXIT trap before make e2e-up that performs
teardown, while preserving the original command failure status as the script’s
exit status.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 373b84fa-2ed1-425a-a53e-b549009534fc

📥 Commits

Reviewing files that changed from the base of the PR and between ec34bf1 and 064e44d.

📒 Files selected for processing (4)
  • CLAUDE.md
  • docs/decisions/0026-newsletter-email-plaintext.md
  • docs/superpowers/plans/2026-08-05-encryption-gaps.md
  • docs/superpowers/specs/2026-08-05-encryption-gaps-design.md

Comment on lines +63 to +64
breaks this table's `UNIQUE(email)` constraint, its lookup index, and both
upserts' conflict detection, silently converting dedupe into duplicate rows.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Qualify the pre-PR read-path statement.

Lines 51-60 add application reads through the new admin endpoints, but Line 63 says the app never reads these tables. Change this to “Before PR B, no filters or search read these columns” or equivalent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 63 - 64,
Update the pre-PR read-path statement near the UNIQUE(email) discussion to
clarify that, before PR B, no filters or search operations read these columns;
do not claim the application never read the tables, since the new admin
endpoints add application reads.

Comment on lines +99 to +101
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced block.

The block at Line 99 contains plain text. Use a text fence so markdownlint MD040 passes.

Proposed fix
-```+```text
Deliberate exception: `newsletter_emails.email` stays plaintext ...
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 99-99: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 99 - 101,
Update the fenced block containing the “Deliberate exception” text to specify
the text language tag, changing the untyped fence to a text fence while
preserving its contents.

Source: Linters/SAST tools

Comment on lines +494 to +500
{reports.map((r) => (
<div key={r.id} style={{ padding: "12px 16px", borderBottom: "1px solid var(--border)" }} data-testid="adminfb-issue-row">
<div style={{ display: "flex", gap: 12, color: "var(--text-dim)", fontSize: 13 }}>
<span>{when(r.created_at)}</span>
<span style={{ color: "var(--text)" }}>{r.user_name || r.user_id}</span>
<span>{r.topic}</span>
{r.screenshot_urls.length > 0 && <span>{r.screenshot_urls.length} screenshot(s)</span>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Implement the screenshot-link contract.

The proposed component displays only the screenshot count at Line 500. The design specification requires links to the private screenshot URLs. Render validated links, or change the specification and response contract to require count-only behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 494 - 500,
Update the reports rendering in the reports.map block to implement the
screenshot-link contract: validate each r.screenshot_urls entry and render links
to the private screenshot URLs instead of displaying only the count. If
count-only behavior is intended, update the design specification and response
contract consistently instead.

Comment on lines +857 to +862
from services.encryption import decrypt_json_column
cj = ctx.get("context_json") or {}
try:
cj = decrypt_json_column(cj) or {}
except Exception:
cj = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not hide all decryption failures as empty context.

The broad except Exception converts key mismatches, corrupted ciphertext, and programming errors into {}. Users then lose quiz context while the request appears successful. decrypt_json_column already handles the documented legacy shapes, so remove this catch or catch only documented compatibility errors and emit telemetry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 857 - 862,
Update the context decryption flow around decrypt_json_column to stop converting
every exception into an empty context object. Remove the broad exception
handler, or restrict it to documented compatibility errors while emitting the
required telemetry; preserve the existing fallback for absent or validly empty
context_json.

Comment on lines +1380 to +1389
flock /tmp/claude-1000/sapling-e2e-stack.lock bash -c '
set -e
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
status=$?
make e2e-down
exit $status

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guarantee teardown when a test command fails.

With set -e, a Playwright or oracle failure exits before status=$? and before make e2e-down. The local stack can remain running and poison later runs. Install an EXIT trap before make e2e-up, or disable errexit while capturing both command statuses.

Proposed fix
 set -e
+ trap 'rc=$?; make e2e-down || true; exit "$rc"' EXIT
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
- status=$?- make e2e-down- exit $status
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 1380 -
1389, Update the locked E2E shell command around the make e2e-up, Playwright,
and e2e_oracles steps to guarantee make e2e-down runs on any failure despite set
-e. Install an EXIT trap before make e2e-up that performs teardown, while
preserving the original command failure status as the script’s exit status.

'
```

Expected: journeys green; oracles exit 0 — the ciphertext oracle now proves all 13 new columns encrypted at rest in a really-running stack. Testing the tip covers B and C's changes too; if the tip needs a fix, re-run after fixing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the ciphertext-column count.

The listed additions total 11 columns: four feedback/issue columns, three quiz columns, and four derived-content columns. Line 1393 says 13, while Line 1437 says 11. Align the count so operators do not validate the oracle against the wrong coverage target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` at line 1393, Update
the expected ciphertext-column count in the oracle validation text near the
“journeys green; oracles exit 0” statement from 13 to 11, matching the
documented additions and the count referenced later in the plan.

@AndresL230
AndresL230 merged commit 104b573 into mainAug 6, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/522-a-519-newsletter-adr branch August 6, 2026 01:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

newsletter_emails.email is plaintext while users.email is encrypted — needs a decision, not a swap

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519) - #526

Merged
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr
Aug 6, 2026
Merged

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519)#526
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr

Conversation

@AndresL230

@AndresL230AndresL230 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Closes#519.

Option 2 from the issue: newsletter_emails.email stays plaintext, deliberately —
the UNIQUE constraint, lookup index, and both upserts (subscribe + admin allowlist)
key on the email value, which AES-GCM's per-call nonce breaks. ADR 0026 records the
decision and the rejected HMAC alternative; CLAUDE.md names the exception so the gap
is not re-filed. Also carries the approved design spec + implementation plan for the #522 stack.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Documented that newsletter email addresses remain plaintext to support uniqueness and reliable lookups.
    • Added an architectural decision record explaining the rationale, alternatives, and consequences of this exception.
    • Added an implementation plan and approved design for expanding encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.
    • Documented compatibility with existing plaintext data, staged backfills, testing, and administrative access requirements.

AndresL230and others added 3 commits August 5, 2026 15:35
Approved design for the four-PR stack: #519 plaintext-by-decision ADR,
#520 feedback encryption + admin read surface, #521 quiz JSON columns,
#518 derived content (flashcards, study guides, room summaries).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging064e44dCommit Preview URL

Branch Preview URL
Aug 05 2026, 10:55 PM

@supabase

supabaseBot commented Aug 5, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change documents newsletter_emails.email as an intentional plaintext exception and adds a design and implementation plan for encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.

Changes

Encryption coverage documentation

Layer / File(s)Summary
Newsletter plaintext exception
CLAUDE.md, docs/decisions/0026-newsletter-email-plaintext.md, docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation records the plaintext decision and its uniqueness and lookup constraints.
Feedback encryption and admin surfaces
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted feedback and issue-report fields, admin read endpoints, frontend rendering, tests, seeds, and backfills.
Quiz JSON encryption compatibility
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted quiz JSON writes, legacy plaintext reads, decryption boundaries, backfills, and validation.
Derived content encryption
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encryption and decryption for flashcards, study guides, and room summaries, including cache and deduplication behavior.
Rollout and completion runbook
docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation defines shared testing, ciphertext manifests, staged backfills, stacked-PR operations, and closeout criteria.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • Issue 522 — The change documents the newsletter plaintext exception described by this issue.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check nameStatusExplanationResolution
Description check⚠️ WarningThe description explains the decision and links #519, but it omits most required template sections, including Changes Made, Testing, and Notes for Reviewers.Add the required headings and complete the change summary, testing status, screenshots applicability, and reviewer notes.
Out of Scope Changes check⚠️ WarningThe #522 design specification and 1,446-line implementation plan extend beyond the directly linked #519 decision and acceptance criteria.Move the #522 specification and implementation plan to a separate PR, or link the relevant #522 issue and justify their inclusion here.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly identifies the newsletter plaintext decision ADR and the related encryption-gaps specification.
Linked Issues check✅ PassedThe PR selects option 2, documents its rationale in ADR 0026, preserves uniqueness and lookup behavior, and records the intentional exception in CLAUDE.md [#519].
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/522-a-519-newsletter-adr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md`:
- Line 1393: Update the expected ciphertext-column count in the oracle
validation text near the “journeys green; oracles exit 0” statement from 13 to
11, matching the documented additions and the count referenced later in the
plan.
- Around line 63-64: Update the pre-PR read-path statement near the
UNIQUE(email) discussion to clarify that, before PR B, no filters or search
operations read these columns; do not claim the application never read the
tables, since the new admin endpoints add application reads.
- Around line 857-862: Update the context decryption flow around
decrypt_json_column to stop converting every exception into an empty context
object. Remove the broad exception handler, or restrict it to documented
compatibility errors while emitting the required telemetry; preserve the
existing fallback for absent or validly empty context_json.
- Around line 494-500: Update the reports rendering in the reports.map block to
implement the screenshot-link contract: validate each r.screenshot_urls entry
and render links to the private screenshot URLs instead of displaying only the
count. If count-only behavior is intended, update the design specification and
response contract consistently instead.
- Around line 99-101: Update the fenced block containing the “Deliberate
exception” text to specify the text language tag, changing the untyped fence to
a text fence while preserving its contents.
- Around line 1380-1389: Update the locked E2E shell command around the make
e2e-up, Playwright, and e2e_oracles steps to guarantee make e2e-down runs on any
failure despite set -e. Install an EXIT trap before make e2e-up that performs
teardown, while preserving the original command failure status as the script’s
exit status.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 373b84fa-2ed1-425a-a53e-b549009534fc

📥 Commits

Reviewing files that changed from the base of the PR and between ec34bf1 and 064e44d.

📒 Files selected for processing (4)
  • CLAUDE.md
  • docs/decisions/0026-newsletter-email-plaintext.md
  • docs/superpowers/plans/2026-08-05-encryption-gaps.md
  • docs/superpowers/specs/2026-08-05-encryption-gaps-design.md

Comment on lines +63 to +64
breaks this table's `UNIQUE(email)` constraint, its lookup index, and both
upserts' conflict detection, silently converting dedupe into duplicate rows.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Qualify the pre-PR read-path statement.

Lines 51-60 add application reads through the new admin endpoints, but Line 63 says the app never reads these tables. Change this to “Before PR B, no filters or search read these columns” or equivalent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 63 - 64,
Update the pre-PR read-path statement near the UNIQUE(email) discussion to
clarify that, before PR B, no filters or search operations read these columns;
do not claim the application never read the tables, since the new admin
endpoints add application reads.

Comment on lines +99 to +101
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced block.

The block at Line 99 contains plain text. Use a text fence so markdownlint MD040 passes.

Proposed fix
-```+```text
Deliberate exception: `newsletter_emails.email` stays plaintext ...
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 99-99: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 99 - 101,
Update the fenced block containing the “Deliberate exception” text to specify
the text language tag, changing the untyped fence to a text fence while
preserving its contents.

Source: Linters/SAST tools

Comment on lines +494 to +500
{reports.map((r) => (
<div key={r.id} style={{ padding: "12px 16px", borderBottom: "1px solid var(--border)" }} data-testid="adminfb-issue-row">
<div style={{ display: "flex", gap: 12, color: "var(--text-dim)", fontSize: 13 }}>
<span>{when(r.created_at)}</span>
<span style={{ color: "var(--text)" }}>{r.user_name || r.user_id}</span>
<span>{r.topic}</span>
{r.screenshot_urls.length > 0 && <span>{r.screenshot_urls.length} screenshot(s)</span>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Implement the screenshot-link contract.

The proposed component displays only the screenshot count at Line 500. The design specification requires links to the private screenshot URLs. Render validated links, or change the specification and response contract to require count-only behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 494 - 500,
Update the reports rendering in the reports.map block to implement the
screenshot-link contract: validate each r.screenshot_urls entry and render links
to the private screenshot URLs instead of displaying only the count. If
count-only behavior is intended, update the design specification and response
contract consistently instead.

Comment on lines +857 to +862
from services.encryption import decrypt_json_column
cj = ctx.get("context_json") or {}
try:
cj = decrypt_json_column(cj) or {}
except Exception:
cj = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not hide all decryption failures as empty context.

The broad except Exception converts key mismatches, corrupted ciphertext, and programming errors into {}. Users then lose quiz context while the request appears successful. decrypt_json_column already handles the documented legacy shapes, so remove this catch or catch only documented compatibility errors and emit telemetry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 857 - 862,
Update the context decryption flow around decrypt_json_column to stop converting
every exception into an empty context object. Remove the broad exception
handler, or restrict it to documented compatibility errors while emitting the
required telemetry; preserve the existing fallback for absent or validly empty
context_json.

Comment on lines +1380 to +1389
flock /tmp/claude-1000/sapling-e2e-stack.lock bash -c '
set -e
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
status=$?
make e2e-down
exit $status

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guarantee teardown when a test command fails.

With set -e, a Playwright or oracle failure exits before status=$? and before make e2e-down. The local stack can remain running and poison later runs. Install an EXIT trap before make e2e-up, or disable errexit while capturing both command statuses.

Proposed fix
 set -e
+ trap 'rc=$?; make e2e-down || true; exit "$rc"' EXIT
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
- status=$?- make e2e-down- exit $status
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 1380 -
1389, Update the locked E2E shell command around the make e2e-up, Playwright,
and e2e_oracles steps to guarantee make e2e-down runs on any failure despite set
-e. Install an EXIT trap before make e2e-up that performs teardown, while
preserving the original command failure status as the script’s exit status.

'
```

Expected: journeys green; oracles exit 0 — the ciphertext oracle now proves all 13 new columns encrypted at rest in a really-running stack. Testing the tip covers B and C's changes too; if the tip needs a fix, re-run after fixing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the ciphertext-column count.

The listed additions total 11 columns: four feedback/issue columns, three quiz columns, and four derived-content columns. Line 1393 says 13, while Line 1437 says 11. Align the count so operators do not validate the oracle against the wrong coverage target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` at line 1393, Update
the expected ciphertext-column count in the oracle validation text near the
“journeys green; oracles exit 0” statement from 13 to 11, matching the
documented additions and the count referenced later in the plan.

@AndresL230
AndresL230 merged commit 104b573 into mainAug 6, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/522-a-519-newsletter-adr branch August 6, 2026 01:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

newsletter_emails.email is plaintext while users.email is encrypted — needs a decision, not a swap

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519) - #526

Merged
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr
Aug 6, 2026
Merged

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519)#526
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr

Conversation

@AndresL230

@AndresL230AndresL230 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Closes#519.

Option 2 from the issue: newsletter_emails.email stays plaintext, deliberately —
the UNIQUE constraint, lookup index, and both upserts (subscribe + admin allowlist)
key on the email value, which AES-GCM's per-call nonce breaks. ADR 0026 records the
decision and the rejected HMAC alternative; CLAUDE.md names the exception so the gap
is not re-filed. Also carries the approved design spec + implementation plan for the #522 stack.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Documented that newsletter email addresses remain plaintext to support uniqueness and reliable lookups.
    • Added an architectural decision record explaining the rationale, alternatives, and consequences of this exception.
    • Added an implementation plan and approved design for expanding encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.
    • Documented compatibility with existing plaintext data, staged backfills, testing, and administrative access requirements.

AndresL230and others added 3 commits August 5, 2026 15:35
Approved design for the four-PR stack: #519 plaintext-by-decision ADR,
#520 feedback encryption + admin read surface, #521 quiz JSON columns,
#518 derived content (flashcards, study guides, room summaries).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging064e44dCommit Preview URL

Branch Preview URL
Aug 05 2026, 10:55 PM

@supabase

supabaseBot commented Aug 5, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change documents newsletter_emails.email as an intentional plaintext exception and adds a design and implementation plan for encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.

Changes

Encryption coverage documentation

Layer / File(s)Summary
Newsletter plaintext exception
CLAUDE.md, docs/decisions/0026-newsletter-email-plaintext.md, docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation records the plaintext decision and its uniqueness and lookup constraints.
Feedback encryption and admin surfaces
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted feedback and issue-report fields, admin read endpoints, frontend rendering, tests, seeds, and backfills.
Quiz JSON encryption compatibility
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted quiz JSON writes, legacy plaintext reads, decryption boundaries, backfills, and validation.
Derived content encryption
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encryption and decryption for flashcards, study guides, and room summaries, including cache and deduplication behavior.
Rollout and completion runbook
docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation defines shared testing, ciphertext manifests, staged backfills, stacked-PR operations, and closeout criteria.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • Issue 522 — The change documents the newsletter plaintext exception described by this issue.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check nameStatusExplanationResolution
Description check⚠️ WarningThe description explains the decision and links #519, but it omits most required template sections, including Changes Made, Testing, and Notes for Reviewers.Add the required headings and complete the change summary, testing status, screenshots applicability, and reviewer notes.
Out of Scope Changes check⚠️ WarningThe #522 design specification and 1,446-line implementation plan extend beyond the directly linked #519 decision and acceptance criteria.Move the #522 specification and implementation plan to a separate PR, or link the relevant #522 issue and justify their inclusion here.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly identifies the newsletter plaintext decision ADR and the related encryption-gaps specification.
Linked Issues check✅ PassedThe PR selects option 2, documents its rationale in ADR 0026, preserves uniqueness and lookup behavior, and records the intentional exception in CLAUDE.md [#519].
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/522-a-519-newsletter-adr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md`:
- Line 1393: Update the expected ciphertext-column count in the oracle
validation text near the “journeys green; oracles exit 0” statement from 13 to
11, matching the documented additions and the count referenced later in the
plan.
- Around line 63-64: Update the pre-PR read-path statement near the
UNIQUE(email) discussion to clarify that, before PR B, no filters or search
operations read these columns; do not claim the application never read the
tables, since the new admin endpoints add application reads.
- Around line 857-862: Update the context decryption flow around
decrypt_json_column to stop converting every exception into an empty context
object. Remove the broad exception handler, or restrict it to documented
compatibility errors while emitting the required telemetry; preserve the
existing fallback for absent or validly empty context_json.
- Around line 494-500: Update the reports rendering in the reports.map block to
implement the screenshot-link contract: validate each r.screenshot_urls entry
and render links to the private screenshot URLs instead of displaying only the
count. If count-only behavior is intended, update the design specification and
response contract consistently instead.
- Around line 99-101: Update the fenced block containing the “Deliberate
exception” text to specify the text language tag, changing the untyped fence to
a text fence while preserving its contents.
- Around line 1380-1389: Update the locked E2E shell command around the make
e2e-up, Playwright, and e2e_oracles steps to guarantee make e2e-down runs on any
failure despite set -e. Install an EXIT trap before make e2e-up that performs
teardown, while preserving the original command failure status as the script’s
exit status.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 373b84fa-2ed1-425a-a53e-b549009534fc

📥 Commits

Reviewing files that changed from the base of the PR and between ec34bf1 and 064e44d.

📒 Files selected for processing (4)
  • CLAUDE.md
  • docs/decisions/0026-newsletter-email-plaintext.md
  • docs/superpowers/plans/2026-08-05-encryption-gaps.md
  • docs/superpowers/specs/2026-08-05-encryption-gaps-design.md

Comment on lines +63 to +64
breaks this table's `UNIQUE(email)` constraint, its lookup index, and both
upserts' conflict detection, silently converting dedupe into duplicate rows.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Qualify the pre-PR read-path statement.

Lines 51-60 add application reads through the new admin endpoints, but Line 63 says the app never reads these tables. Change this to “Before PR B, no filters or search read these columns” or equivalent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 63 - 64,
Update the pre-PR read-path statement near the UNIQUE(email) discussion to
clarify that, before PR B, no filters or search operations read these columns;
do not claim the application never read the tables, since the new admin
endpoints add application reads.

Comment on lines +99 to +101
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced block.

The block at Line 99 contains plain text. Use a text fence so markdownlint MD040 passes.

Proposed fix
-```+```text
Deliberate exception: `newsletter_emails.email` stays plaintext ...
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 99-99: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 99 - 101,
Update the fenced block containing the “Deliberate exception” text to specify
the text language tag, changing the untyped fence to a text fence while
preserving its contents.

Source: Linters/SAST tools

Comment on lines +494 to +500
{reports.map((r) => (
<div key={r.id} style={{ padding: "12px 16px", borderBottom: "1px solid var(--border)" }} data-testid="adminfb-issue-row">
<div style={{ display: "flex", gap: 12, color: "var(--text-dim)", fontSize: 13 }}>
<span>{when(r.created_at)}</span>
<span style={{ color: "var(--text)" }}>{r.user_name || r.user_id}</span>
<span>{r.topic}</span>
{r.screenshot_urls.length > 0 && <span>{r.screenshot_urls.length} screenshot(s)</span>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Implement the screenshot-link contract.

The proposed component displays only the screenshot count at Line 500. The design specification requires links to the private screenshot URLs. Render validated links, or change the specification and response contract to require count-only behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 494 - 500,
Update the reports rendering in the reports.map block to implement the
screenshot-link contract: validate each r.screenshot_urls entry and render links
to the private screenshot URLs instead of displaying only the count. If
count-only behavior is intended, update the design specification and response
contract consistently instead.

Comment on lines +857 to +862
from services.encryption import decrypt_json_column
cj = ctx.get("context_json") or {}
try:
cj = decrypt_json_column(cj) or {}
except Exception:
cj = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not hide all decryption failures as empty context.

The broad except Exception converts key mismatches, corrupted ciphertext, and programming errors into {}. Users then lose quiz context while the request appears successful. decrypt_json_column already handles the documented legacy shapes, so remove this catch or catch only documented compatibility errors and emit telemetry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 857 - 862,
Update the context decryption flow around decrypt_json_column to stop converting
every exception into an empty context object. Remove the broad exception
handler, or restrict it to documented compatibility errors while emitting the
required telemetry; preserve the existing fallback for absent or validly empty
context_json.

Comment on lines +1380 to +1389
flock /tmp/claude-1000/sapling-e2e-stack.lock bash -c '
set -e
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
status=$?
make e2e-down
exit $status

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guarantee teardown when a test command fails.

With set -e, a Playwright or oracle failure exits before status=$? and before make e2e-down. The local stack can remain running and poison later runs. Install an EXIT trap before make e2e-up, or disable errexit while capturing both command statuses.

Proposed fix
 set -e
+ trap 'rc=$?; make e2e-down || true; exit "$rc"' EXIT
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
- status=$?- make e2e-down- exit $status
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 1380 -
1389, Update the locked E2E shell command around the make e2e-up, Playwright,
and e2e_oracles steps to guarantee make e2e-down runs on any failure despite set
-e. Install an EXIT trap before make e2e-up that performs teardown, while
preserving the original command failure status as the script’s exit status.

'
```

Expected: journeys green; oracles exit 0 — the ciphertext oracle now proves all 13 new columns encrypted at rest in a really-running stack. Testing the tip covers B and C's changes too; if the tip needs a fix, re-run after fixing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the ciphertext-column count.

The listed additions total 11 columns: four feedback/issue columns, three quiz columns, and four derived-content columns. Line 1393 says 13, while Line 1437 says 11. Align the count so operators do not validate the oracle against the wrong coverage target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` at line 1393, Update
the expected ciphertext-column count in the oracle validation text near the
“journeys green; oracles exit 0” statement from 13 to 11, matching the
documented additions and the count referenced later in the plan.

@AndresL230
AndresL230 merged commit 104b573 into mainAug 6, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/522-a-519-newsletter-adr branch August 6, 2026 01:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

newsletter_emails.email is plaintext while users.email is encrypted — needs a decision, not a swap

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519) - #526

Merged
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr
Aug 6, 2026
Merged

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519)#526
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr

Conversation

@AndresL230

@AndresL230AndresL230 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Closes#519.

Option 2 from the issue: newsletter_emails.email stays plaintext, deliberately —
the UNIQUE constraint, lookup index, and both upserts (subscribe + admin allowlist)
key on the email value, which AES-GCM's per-call nonce breaks. ADR 0026 records the
decision and the rejected HMAC alternative; CLAUDE.md names the exception so the gap
is not re-filed. Also carries the approved design spec + implementation plan for the #522 stack.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Documented that newsletter email addresses remain plaintext to support uniqueness and reliable lookups.
    • Added an architectural decision record explaining the rationale, alternatives, and consequences of this exception.
    • Added an implementation plan and approved design for expanding encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.
    • Documented compatibility with existing plaintext data, staged backfills, testing, and administrative access requirements.

AndresL230and others added 3 commits August 5, 2026 15:35
Approved design for the four-PR stack: #519 plaintext-by-decision ADR,
#520 feedback encryption + admin read surface, #521 quiz JSON columns,
#518 derived content (flashcards, study guides, room summaries).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging064e44dCommit Preview URL

Branch Preview URL
Aug 05 2026, 10:55 PM

@supabase

supabaseBot commented Aug 5, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change documents newsletter_emails.email as an intentional plaintext exception and adds a design and implementation plan for encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.

Changes

Encryption coverage documentation

Layer / File(s)Summary
Newsletter plaintext exception
CLAUDE.md, docs/decisions/0026-newsletter-email-plaintext.md, docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation records the plaintext decision and its uniqueness and lookup constraints.
Feedback encryption and admin surfaces
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted feedback and issue-report fields, admin read endpoints, frontend rendering, tests, seeds, and backfills.
Quiz JSON encryption compatibility
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted quiz JSON writes, legacy plaintext reads, decryption boundaries, backfills, and validation.
Derived content encryption
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encryption and decryption for flashcards, study guides, and room summaries, including cache and deduplication behavior.
Rollout and completion runbook
docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation defines shared testing, ciphertext manifests, staged backfills, stacked-PR operations, and closeout criteria.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • Issue 522 — The change documents the newsletter plaintext exception described by this issue.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check nameStatusExplanationResolution
Description check⚠️ WarningThe description explains the decision and links #519, but it omits most required template sections, including Changes Made, Testing, and Notes for Reviewers.Add the required headings and complete the change summary, testing status, screenshots applicability, and reviewer notes.
Out of Scope Changes check⚠️ WarningThe #522 design specification and 1,446-line implementation plan extend beyond the directly linked #519 decision and acceptance criteria.Move the #522 specification and implementation plan to a separate PR, or link the relevant #522 issue and justify their inclusion here.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly identifies the newsletter plaintext decision ADR and the related encryption-gaps specification.
Linked Issues check✅ PassedThe PR selects option 2, documents its rationale in ADR 0026, preserves uniqueness and lookup behavior, and records the intentional exception in CLAUDE.md [#519].
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/522-a-519-newsletter-adr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md`:
- Line 1393: Update the expected ciphertext-column count in the oracle
validation text near the “journeys green; oracles exit 0” statement from 13 to
11, matching the documented additions and the count referenced later in the
plan.
- Around line 63-64: Update the pre-PR read-path statement near the
UNIQUE(email) discussion to clarify that, before PR B, no filters or search
operations read these columns; do not claim the application never read the
tables, since the new admin endpoints add application reads.
- Around line 857-862: Update the context decryption flow around
decrypt_json_column to stop converting every exception into an empty context
object. Remove the broad exception handler, or restrict it to documented
compatibility errors while emitting the required telemetry; preserve the
existing fallback for absent or validly empty context_json.
- Around line 494-500: Update the reports rendering in the reports.map block to
implement the screenshot-link contract: validate each r.screenshot_urls entry
and render links to the private screenshot URLs instead of displaying only the
count. If count-only behavior is intended, update the design specification and
response contract consistently instead.
- Around line 99-101: Update the fenced block containing the “Deliberate
exception” text to specify the text language tag, changing the untyped fence to
a text fence while preserving its contents.
- Around line 1380-1389: Update the locked E2E shell command around the make
e2e-up, Playwright, and e2e_oracles steps to guarantee make e2e-down runs on any
failure despite set -e. Install an EXIT trap before make e2e-up that performs
teardown, while preserving the original command failure status as the script’s
exit status.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 373b84fa-2ed1-425a-a53e-b549009534fc

📥 Commits

Reviewing files that changed from the base of the PR and between ec34bf1 and 064e44d.

📒 Files selected for processing (4)
  • CLAUDE.md
  • docs/decisions/0026-newsletter-email-plaintext.md
  • docs/superpowers/plans/2026-08-05-encryption-gaps.md
  • docs/superpowers/specs/2026-08-05-encryption-gaps-design.md

Comment on lines +63 to +64
breaks this table's `UNIQUE(email)` constraint, its lookup index, and both
upserts' conflict detection, silently converting dedupe into duplicate rows.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Qualify the pre-PR read-path statement.

Lines 51-60 add application reads through the new admin endpoints, but Line 63 says the app never reads these tables. Change this to “Before PR B, no filters or search read these columns” or equivalent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 63 - 64,
Update the pre-PR read-path statement near the UNIQUE(email) discussion to
clarify that, before PR B, no filters or search operations read these columns;
do not claim the application never read the tables, since the new admin
endpoints add application reads.

Comment on lines +99 to +101
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced block.

The block at Line 99 contains plain text. Use a text fence so markdownlint MD040 passes.

Proposed fix
-```+```text
Deliberate exception: `newsletter_emails.email` stays plaintext ...
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 99-99: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 99 - 101,
Update the fenced block containing the “Deliberate exception” text to specify
the text language tag, changing the untyped fence to a text fence while
preserving its contents.

Source: Linters/SAST tools

Comment on lines +494 to +500
{reports.map((r) => (
<div key={r.id} style={{ padding: "12px 16px", borderBottom: "1px solid var(--border)" }} data-testid="adminfb-issue-row">
<div style={{ display: "flex", gap: 12, color: "var(--text-dim)", fontSize: 13 }}>
<span>{when(r.created_at)}</span>
<span style={{ color: "var(--text)" }}>{r.user_name || r.user_id}</span>
<span>{r.topic}</span>
{r.screenshot_urls.length > 0 && <span>{r.screenshot_urls.length} screenshot(s)</span>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Implement the screenshot-link contract.

The proposed component displays only the screenshot count at Line 500. The design specification requires links to the private screenshot URLs. Render validated links, or change the specification and response contract to require count-only behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 494 - 500,
Update the reports rendering in the reports.map block to implement the
screenshot-link contract: validate each r.screenshot_urls entry and render links
to the private screenshot URLs instead of displaying only the count. If
count-only behavior is intended, update the design specification and response
contract consistently instead.

Comment on lines +857 to +862
from services.encryption import decrypt_json_column
cj = ctx.get("context_json") or {}
try:
cj = decrypt_json_column(cj) or {}
except Exception:
cj = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not hide all decryption failures as empty context.

The broad except Exception converts key mismatches, corrupted ciphertext, and programming errors into {}. Users then lose quiz context while the request appears successful. decrypt_json_column already handles the documented legacy shapes, so remove this catch or catch only documented compatibility errors and emit telemetry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 857 - 862,
Update the context decryption flow around decrypt_json_column to stop converting
every exception into an empty context object. Remove the broad exception
handler, or restrict it to documented compatibility errors while emitting the
required telemetry; preserve the existing fallback for absent or validly empty
context_json.

Comment on lines +1380 to +1389
flock /tmp/claude-1000/sapling-e2e-stack.lock bash -c '
set -e
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
status=$?
make e2e-down
exit $status

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guarantee teardown when a test command fails.

With set -e, a Playwright or oracle failure exits before status=$? and before make e2e-down. The local stack can remain running and poison later runs. Install an EXIT trap before make e2e-up, or disable errexit while capturing both command statuses.

Proposed fix
 set -e
+ trap 'rc=$?; make e2e-down || true; exit "$rc"' EXIT
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
- status=$?- make e2e-down- exit $status
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 1380 -
1389, Update the locked E2E shell command around the make e2e-up, Playwright,
and e2e_oracles steps to guarantee make e2e-down runs on any failure despite set
-e. Install an EXIT trap before make e2e-up that performs teardown, while
preserving the original command failure status as the script’s exit status.

'
```

Expected: journeys green; oracles exit 0 — the ciphertext oracle now proves all 13 new columns encrypted at rest in a really-running stack. Testing the tip covers B and C's changes too; if the tip needs a fix, re-run after fixing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the ciphertext-column count.

The listed additions total 11 columns: four feedback/issue columns, three quiz columns, and four derived-content columns. Line 1393 says 13, while Line 1437 says 11. Align the count so operators do not validate the oracle against the wrong coverage target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` at line 1393, Update
the expected ciphertext-column count in the oracle validation text near the
“journeys green; oracles exit 0” statement from 13 to 11, matching the
documented additions and the count referenced later in the plan.

@AndresL230
AndresL230 merged commit 104b573 into mainAug 6, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/522-a-519-newsletter-adr branch August 6, 2026 01:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

newsletter_emails.email is plaintext while users.email is encrypted — needs a decision, not a swap

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519) - #526

Merged
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr
Aug 6, 2026
Merged

docs: newsletter email plaintext-by-decision ADR + encryption-gaps spec (#519)#526
AndresL230 merged 3 commits into
mainfrom
feat/522-a-519-newsletter-adr

Conversation

@AndresL230

@AndresL230AndresL230 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Closes#519.

Option 2 from the issue: newsletter_emails.email stays plaintext, deliberately —
the UNIQUE constraint, lookup index, and both upserts (subscribe + admin allowlist)
key on the email value, which AES-GCM's per-call nonce breaks. ADR 0026 records the
decision and the rejected HMAC alternative; CLAUDE.md names the exception so the gap
is not re-filed. Also carries the approved design spec + implementation plan for the #522 stack.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Documented that newsletter email addresses remain plaintext to support uniqueness and reliable lookups.
    • Added an architectural decision record explaining the rationale, alternatives, and consequences of this exception.
    • Added an implementation plan and approved design for expanding encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.
    • Documented compatibility with existing plaintext data, staged backfills, testing, and administrative access requirements.

AndresL230and others added 3 commits August 5, 2026 15:35
Approved design for the four-PR stack: #519 plaintext-by-decision ADR,
#520 feedback encryption + admin read surface, #521 quiz JSON columns,
#518 derived content (flashcards, study guides, room summaries).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging064e44dCommit Preview URL

Branch Preview URL
Aug 05 2026, 10:55 PM

@supabase

supabaseBot commented Aug 5, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change documents newsletter_emails.email as an intentional plaintext exception and adds a design and implementation plan for encryption coverage across feedback, issue reports, quiz data, flashcards, study guides, and room summaries.

Changes

Encryption coverage documentation

Layer / File(s)Summary
Newsletter plaintext exception
CLAUDE.md, docs/decisions/0026-newsletter-email-plaintext.md, docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation records the plaintext decision and its uniqueness and lookup constraints.
Feedback encryption and admin surfaces
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted feedback and issue-report fields, admin read endpoints, frontend rendering, tests, seeds, and backfills.
Quiz JSON encryption compatibility
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encrypted quiz JSON writes, legacy plaintext reads, decryption boundaries, backfills, and validation.
Derived content encryption
docs/superpowers/plans/..., docs/superpowers/specs/...
The plan defines encryption and decryption for flashcards, study guides, and room summaries, including cache and deduplication behavior.
Rollout and completion runbook
docs/superpowers/plans/..., docs/superpowers/specs/...
The documentation defines shared testing, ciphertext manifests, staged backfills, stacked-PR operations, and closeout criteria.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • Issue 522 — The change documents the newsletter plaintext exception described by this issue.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check nameStatusExplanationResolution
Description check⚠️ WarningThe description explains the decision and links #519, but it omits most required template sections, including Changes Made, Testing, and Notes for Reviewers.Add the required headings and complete the change summary, testing status, screenshots applicability, and reviewer notes.
Out of Scope Changes check⚠️ WarningThe #522 design specification and 1,446-line implementation plan extend beyond the directly linked #519 decision and acceptance criteria.Move the #522 specification and implementation plan to a separate PR, or link the relevant #522 issue and justify their inclusion here.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly identifies the newsletter plaintext decision ADR and the related encryption-gaps specification.
Linked Issues check✅ PassedThe PR selects option 2, documents its rationale in ADR 0026, preserves uniqueness and lookup behavior, and records the intentional exception in CLAUDE.md [#519].
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/522-a-519-newsletter-adr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md`:
- Line 1393: Update the expected ciphertext-column count in the oracle
validation text near the “journeys green; oracles exit 0” statement from 13 to
11, matching the documented additions and the count referenced later in the
plan.
- Around line 63-64: Update the pre-PR read-path statement near the
UNIQUE(email) discussion to clarify that, before PR B, no filters or search
operations read these columns; do not claim the application never read the
tables, since the new admin endpoints add application reads.
- Around line 857-862: Update the context decryption flow around
decrypt_json_column to stop converting every exception into an empty context
object. Remove the broad exception handler, or restrict it to documented
compatibility errors while emitting the required telemetry; preserve the
existing fallback for absent or validly empty context_json.
- Around line 494-500: Update the reports rendering in the reports.map block to
implement the screenshot-link contract: validate each r.screenshot_urls entry
and render links to the private screenshot URLs instead of displaying only the
count. If count-only behavior is intended, update the design specification and
response contract consistently instead.
- Around line 99-101: Update the fenced block containing the “Deliberate
exception” text to specify the text language tag, changing the untyped fence to
a text fence while preserving its contents.
- Around line 1380-1389: Update the locked E2E shell command around the make
e2e-up, Playwright, and e2e_oracles steps to guarantee make e2e-down runs on any
failure despite set -e. Install an EXIT trap before make e2e-up that performs
teardown, while preserving the original command failure status as the script’s
exit status.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 373b84fa-2ed1-425a-a53e-b549009534fc

📥 Commits

Reviewing files that changed from the base of the PR and between ec34bf1 and 064e44d.

📒 Files selected for processing (4)
  • CLAUDE.md
  • docs/decisions/0026-newsletter-email-plaintext.md
  • docs/superpowers/plans/2026-08-05-encryption-gaps.md
  • docs/superpowers/specs/2026-08-05-encryption-gaps-design.md

Comment on lines +63 to +64
breaks this table's `UNIQUE(email)` constraint, its lookup index, and both
upserts' conflict detection, silently converting dedupe into duplicate rows.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Qualify the pre-PR read-path statement.

Lines 51-60 add application reads through the new admin endpoints, but Line 63 says the app never reads these tables. Change this to “Before PR B, no filters or search read these columns” or equivalent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 63 - 64,
Update the pre-PR read-path statement near the UNIQUE(email) discussion to
clarify that, before PR B, no filters or search operations read these columns;
do not claim the application never read the tables, since the new admin
endpoints add application reads.

Comment on lines +99 to +101
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced block.

The block at Line 99 contains plain text. Use a text fence so markdownlint MD040 passes.

Proposed fix
-```+```text
Deliberate exception: `newsletter_emails.email` stays plaintext ...
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
Deliberate exception: `newsletter_emails.email` stays plaintext (ADR 0026) — the UNIQUE constraint, lookup index, and both subscribe/allowlist upserts key on the value, and AES-GCM's per-call nonce breaks value equality.
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 99-99: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 99 - 101,
Update the fenced block containing the “Deliberate exception” text to specify
the text language tag, changing the untyped fence to a text fence while
preserving its contents.

Source: Linters/SAST tools

Comment on lines +494 to +500
{reports.map((r) => (
<div key={r.id} style={{ padding: "12px 16px", borderBottom: "1px solid var(--border)" }} data-testid="adminfb-issue-row">
<div style={{ display: "flex", gap: 12, color: "var(--text-dim)", fontSize: 13 }}>
<span>{when(r.created_at)}</span>
<span style={{ color: "var(--text)" }}>{r.user_name || r.user_id}</span>
<span>{r.topic}</span>
{r.screenshot_urls.length > 0 && <span>{r.screenshot_urls.length} screenshot(s)</span>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Implement the screenshot-link contract.

The proposed component displays only the screenshot count at Line 500. The design specification requires links to the private screenshot URLs. Render validated links, or change the specification and response contract to require count-only behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 494 - 500,
Update the reports rendering in the reports.map block to implement the
screenshot-link contract: validate each r.screenshot_urls entry and render links
to the private screenshot URLs instead of displaying only the count. If
count-only behavior is intended, update the design specification and response
contract consistently instead.

Comment on lines +857 to +862
from services.encryption import decrypt_json_column
cj = ctx.get("context_json") or {}
try:
cj = decrypt_json_column(cj) or {}
except Exception:
cj = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not hide all decryption failures as empty context.

The broad except Exception converts key mismatches, corrupted ciphertext, and programming errors into {}. Users then lose quiz context while the request appears successful. decrypt_json_column already handles the documented legacy shapes, so remove this catch or catch only documented compatibility errors and emit telemetry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 857 - 862,
Update the context decryption flow around decrypt_json_column to stop converting
every exception into an empty context object. Remove the broad exception
handler, or restrict it to documented compatibility errors while emitting the
required telemetry; preserve the existing fallback for absent or validly empty
context_json.

Comment on lines +1380 to +1389
flock /tmp/claude-1000/sapling-e2e-stack.lock bash -c '
set -e
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
status=$?
make e2e-down
exit $status

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guarantee teardown when a test command fails.

With set -e, a Playwright or oracle failure exits before status=$? and before make e2e-down. The local stack can remain running and poison later runs. Install an EXIT trap before make e2e-up, or disable errexit while capturing both command statuses.

Proposed fix
 set -e
+ trap 'rc=$?; make e2e-down || true; exit "$rc"' EXIT
export SAPLING_MODEL_MODE=function
export SAPLING_FUNCTION_HANDLERS=agents.function_handlers_e2e
make e2e-up
(cd frontend && npx playwright test)
(cd backend && venv/bin/python -m e2e_oracles)
- status=$?- make e2e-down- exit $status
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` around lines 1380 -
1389, Update the locked E2E shell command around the make e2e-up, Playwright,
and e2e_oracles steps to guarantee make e2e-down runs on any failure despite set
-e. Install an EXIT trap before make e2e-up that performs teardown, while
preserving the original command failure status as the script’s exit status.

'
```

Expected: journeys green; oracles exit 0 — the ciphertext oracle now proves all 13 new columns encrypted at rest in a really-running stack. Testing the tip covers B and C's changes too; if the tip needs a fix, re-run after fixing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the ciphertext-column count.

The listed additions total 11 columns: four feedback/issue columns, three quiz columns, and four derived-content columns. Line 1393 says 13, while Line 1437 says 11. Align the count so operators do not validate the oracle against the wrong coverage target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-08-05-encryption-gaps.md` at line 1393, Update
the expected ciphertext-column count in the oracle validation text near the
“journeys green; oracles exit 0” statement from 13 to 11, matching the
documented additions and the count referenced later in the plan.

@AndresL230
AndresL230 merged commit 104b573 into mainAug 6, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/522-a-519-newsletter-adr branch August 6, 2026 01:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

newsletter_emails.email is plaintext while users.email is encrypted — needs a decision, not a swap

1 participant

@AndresL230