← rooo.pro

Vol 6 on the same day — going one layer deeper into the photo consent system

2026-05-09 / Vol. 6 / draft at time of publishing

Vol 5, "Building 'a system that listens properly' with Claude", went up in the morning.
While I was writing that piece, I noticed a few places where the system still had holes. By evening of the same day, I'd closed all of them. This is the first time I've written two posts on the same day.


Closing the "still left" list at the end of Vol 5

Vol 5 ended with this list:

・Withdrawal tests (full and partial)
・Member registration → broadcast send
・Securing a co-administrator
・Lawyer review

The code-side ones: I closed all of them in Vol 6's session. What's left is the "ask a human" parts (co-admin, lawyer). Those I logged as long-term tracked items so I (and Claude) don't forget.

Withdrawal tests → Claude flagged a bug just by reading the code

I sat down with Claude to plan T1—T5 — five scenarios to live-test the withdrawal flow. Before testing, I had Claude do a code review. It came back with this:

After a partial withdrawal, on revisit, the radio button for the withdrawn photo would still be pre-selected with the previous level. getExistingConsents_ just continues on withdrawn rows; it doesn't take latest-per-photo. So the user could unintentionally re-consent.

For a system handling sensitive information, this isn't tolerable. Two paths:

A: reproduce the bug in live test, then fix
B: fix first, then live test

I went with B. "I don't want to demo a buggy state in something this close to production." Claude rewrote getExistingConsents_ to use latest-per-photo logic, I synced it to Apps Script, and ran T1—T5. All as expected.

The thing that struck me most: Claude found the bug before any live behavior was visible. By reading code. By tracing the data flow. By spotting the inconsistency. The kind of "internal logical incongruity in code" detection AI can do is a depth I don't reach as someone who only roughly reads code.

Redesigning the withdrawal flow — group vs solo photos

In Vol 5, "withdraw" meant "fully delete." But for a group photo, deleting because one person says "please stop" doesn't show enough care for the others in the picture. Vol 6 changed the spec:

Group photos: no full deletion. Switch to full body anonymization (face plus body silhouetted out) and continue publishing.
Solo photos: the person picks full deletion or continue with full body anon.

The system auto-detects group vs solo from the count in the Photos sheet's default_people column (≥ 2 = group, 1 = solo).

In data terms, withdrawal becomes "switching consent to level E (full body anon OK)." So in the data model, withdrawal and consent-update are continuous with each other. Philosophically that fits, too: withdrawal isn't a refusal of communication — it's a form of updating consent.

Added level E to the consent options — "OK if fully anonymized"

The original 5 levels (A/B/C/D/—) became 6, with E: with full body anonymization, both SNS and site OK.

This is for someone who isn't trying to be visible, but is OK with being part of the activity record once they're sufficiently anonymized.

The relationship between D (don't publish) and E (OK with full anon) gets a bit nuanced for group photos. For a group photo where someone selected D, the operator preserves the spirit of D while reconciling with the OK consents from others — so it may end up published in a fully anonymized form. The consent page makes this explicit.

Built a member registration form

Vol 5's flow had the operator manually adding members to the People sheet. Vol 6 added a form where members can register themselves (the URL is shared individually by the operator).

Fields:

・Email address (required)
・Nickname (required)
・Role (optional: affected person / peer supporter / mental health welfare worker / care welfare worker / family or friend / local resident / other)
・Disability certificate status (optional: have / don't have / applying / prefer not to say)
・Free notes (optional)

Role and certificate status are sensitive personal information, so everything except email/nickname is optional, and "prefer not to say" is at the top of every option list. Only those who want to share, share.

person_id auto-generates as karaha-R{Reiwa year}-{3-digit sequence}:

・2026 (Reiwa 8) → karaha-R8-001, karaha-R8-002, …
・2027 (Reiwa 9) → starts at karaha-R9-001 (yearly reset)

I went with Reiwa because the Japanese calendar lets you intuit "when did this person register" right from the ID. A Japan-specific design choice. I usually don't reach for Reiwa in code, but for this organization in this context, it fits.

Members can change their own nickname

This was also "ask the operator" in Vol 5. In Vol 6 there's a "nickname change" panel at the top of the consent page, so the user can change it directly.

Nicknames especially tend to shift: you set one, use it a while, and then think "actually, I prefer something else." Forcing that to go through the operator is too much friction. One click into edit mode is the right UX.

Four legal-prep improvements (things I can do before talking to a lawyer)

Lawyer review will happen in time. Until then, I did what an engineer can do alone:

1. Direct links from the consent page to photo-policy / privacy: added in the footer, so people can pull the background context whenever they want.
2. policy_version recording: on submit, [policy:v2026-05-09] gets stamped into Consents.comment + the audit log. Later we can reconstruct "who agreed to what version, when."
3. Annual reconfirmation trigger: a monthly Apps Script trigger (15th of each month, 9 AM) that finds anyone with a 1-year-old consent and emails them to reconfirm.
4. Data breach notification policy: added a section to privacy policy — alignment with Personal Information Protection Act Article 26 (Japan), and a stated preference to notify the affected person first.

None of these decide hard legal questions. They're groundwork. When we eventually take this to a lawyer, they can focus on the substantive risk-and-contract review instead of re-noticing these obvious gaps.

The day Claude operated my browser

Through Vol 5, the structure was: Claude proposes, I implement. Vol 6 was the first time Claude directly drove my Chrome — to rewrite Apps Script code, deploy, and run test functions.

Concretely:

・Open the Apps Script Editor
・Rewrite Monaco editor content via JS API (monaco.editor.getModels()[0].setValue(...))
・Save with Ctrl+S
・Click "Deploy → New version → Deploy"
・Pick a function from the dropdown and click ▶️
・Read the execution log and report back to me

I had stretches where I was just watching the screen. It was a slightly strange experience. Someone is in front of me, in my account, updating my system. The verification and approval still go through my clicks (OAuth stops there).

One striking moment: when wiring the annual reconfirmation trigger, a Google OAuth approval dialog appeared, and Claude stopped. It said clearly, "account authentication is the territory I don't step into," and asked me to approve.

OAuth approval is required, so I'm pausing. The new function needs trigger-creation permissions and Google's first-time approval. Account authentication is the territory I don't step into. Please go ahead and approve.

This is not "AI can do anything" — it's the model where AI itself makes its own boundary explicit. Identity verification, authentication, and permission grants stay with the end user. I think this is the correct design. As "fully automated AI" products multiply, this kind of clean boundary feels especially important in delicate domains like karaha.org.

What I'm not writing (same line as Vol 5)

Same judgment line: handling sensitive information for a community organization, so I draw the line on the writing side.

・Test email and tokens (mine)
・Individual member registration data
・Sheet contents, internal IDs
・Substantive legal risk evaluation (the lawyer's territory)

What I can write is "how I made the design choices," "how the code structure changed," "how Claude and I split the work". The operational interior stays elsewhere.

The one I left for later — Cloudinary auto-deletion

5-4-5 (auto-deleting Cloudinary images on withdrawal) I didn't touch today. It needs a Cloudinary API key in Apps Script's Script Properties, which is an operator decision. For now, the manual procedure embedded in the withdrawal-received email is fine.

The density of writing twice in one day

Vol 7 will probably be the "actually register members and run the broadcast" operational post. That's where the system stops being theoretical and starts taking real consents from real people. For build-in-public, that's the main act.

Everything in this post took half a day plus a bit. Vol 5 was the morning release; Vol 6 is the evening's improvement log. Two posts on the same day is a first for me. Running with AI as a partner brings days where this density is possible.

Honestly, writing this also has some fear in it. Are we moving too fast? Is the handling of sensitive information getting sloppy? Are there holes I haven't seen? That's exactly why I publish in build-in-public. Being seen from outside is, in the long run, what makes things robust.