Ratify the language-learning direction and expand it into Phases 15-22
SUGGESTIONS.md (new): the product rationale. Every user gets one
(English + X) pair, X in {zh, pt-PT, fr, maybe es} - bilingual UI in the
pair, type in either language, direction inferred without a detector
(both-dictionaries spellcheck, show-both gloss on collision). Langpacks
keyed by X. LLM-minimalism as a standing principle: the LLM never gates
essential functionality; grammar-lite rules and an embedded
miscollocation list are planned as code-first layers beneath the LLM
families.
MULTIUSER_PLAN.md: all OPEN decisions settled - in-app OIDC (the
parodia.dev VPS hosting plan decides it), 30-day sliding sessions,
allowlist, migration-by-script, image-store ownership fixed alongside
auth, DreamDict imported as a package reading dict.db (module rename
prereq lives in the dreamdict repo), zh stays on ECDICT until compared.
BUILD_PLAN.md: the deferred bucket becomes checkboxed Phases 15-22
(deploy plumbing / OIDC / local-user migration / client-state
namespacing / langpack extraction / DreamDict provider / pt-PT pair /
learning loop + code-first layers) with standing rules: isolation tests
in the same commit as any user-scoped endpoint, LLM-minimalism, and
bilingual aesthetic as acceptance criteria.
This commit is contained in:
+68
-49
@@ -1,11 +1,16 @@
|
||||
# Petal multi-user plan
|
||||
|
||||
**Status:** Phase 0 (identity plumbing) landed 2026-07-26 in `6901cdb`. Everything
|
||||
below it is proposed, not agreed.
|
||||
**Status:** Phase 0 (identity plumbing) landed 2026-07-26 in `6901cdb`.
|
||||
**All OPEN decisions ratified by the user 2026-07-26** (recommendations
|
||||
accepted as written) — see each OPEN for its settled answer. Execution phases
|
||||
live in `BUILD_PLAN.md` (Phase 15 onward); product rationale for the language
|
||||
work is in `SUGGESTIONS.md`.
|
||||
|
||||
This document exists to be argued with. Sections marked **OPEN** are real
|
||||
decisions with real trade-offs, not rhetorical questions — a reviewer should
|
||||
push back on them. Where I have a recommendation I say so and say why.
|
||||
Settled context that postdates the original draft: Petal will be hosted on the
|
||||
**parodia.dev VPS**, reaching vLLM on millenia over **headscale VPN**; Piper
|
||||
TTS is already installed on parodia (VPS-local, no VPN hop). The LLM is the
|
||||
only cross-VPN dependency, and per the LLM-minimalism principle
|
||||
(SUGGESTIONS.md §6) it must never gate essential functionality.
|
||||
|
||||
---
|
||||
|
||||
@@ -61,10 +66,12 @@ global."
|
||||
|
||||
## 3. Phase A — authentication
|
||||
|
||||
### OPEN #1: forward-auth vs. in-app OIDC
|
||||
### OPEN #1: forward-auth vs. in-app OIDC — **SETTLED: Option B (in-app OIDC)**
|
||||
|
||||
This is the biggest fork and it should be settled first, because everything in
|
||||
Phase B depends on it.
|
||||
Ratified 2026-07-26. The deciding fact arrived with the deployment plan: Petal
|
||||
will live on the public parodia.dev VPS, which is exactly the environment where
|
||||
Option A's "must never be reachable except through Traefik" invariant is a
|
||||
footgun. Original analysis kept below for the record.
|
||||
|
||||
**Option A — Traefik forward-auth to an Authentik outpost.**
|
||||
Traefik is already in the deferred deploy bucket. Authentik's outpost terminates
|
||||
@@ -109,10 +116,10 @@ Does the operator want Petal to be independently deployable?
|
||||
Preferred over signed stateless cookies because it makes logout and
|
||||
revocation actually work — worth the one table.
|
||||
- Cookie: `HttpOnly`, `SameSite=Lax`, `Secure` when `BASE_URL` is https.
|
||||
- **OPEN #2:** session lifetime. This is a personal writing tool used daily on
|
||||
trusted devices; a 30-day sliding expiry is kind, an 8-hour one is
|
||||
conventional. I lean long — an editor that logs you out mid-draft is hostile,
|
||||
and the auto-save makes a surprise 401 genuinely costly. Needs a decision.
|
||||
- **OPEN #2 — SETTLED: 30-day sliding expiry** (ratified 2026-07-26). An
|
||||
editor that logs you out mid-draft is hostile, and auto-save makes a
|
||||
surprise 401 genuinely costly. Sliding: each authenticated request extends
|
||||
the session.
|
||||
|
||||
### The 401 problem
|
||||
|
||||
@@ -136,9 +143,11 @@ On first successful login, upsert a `users` row from the OIDC claims
|
||||
(`sub` → `users.id`, plus email and name). No signup flow; whoever Authentik lets
|
||||
in gets an account.
|
||||
|
||||
**OPEN #3:** should there be an allowlist? Authentik may host other applications
|
||||
with a broader user set than Petal should have. A `PETAL_ALLOWED_SUBS` env var,
|
||||
or an Authentik group check on a claim, would gate it. Probably yes, cheaply.
|
||||
**OPEN #3 — SETTLED: yes, allowlist** (ratified 2026-07-26). Authentik may
|
||||
host other applications with a broader user set than Petal should have. Gate
|
||||
via a `PETAL_ALLOWED_SUBS` env var (or an Authentik group claim check —
|
||||
implementer's choice, env var is simpler); a valid login not on the list gets
|
||||
a warm bilingual "this Petal isn't yours to write in" page, not a 500.
|
||||
|
||||
### Migrating the existing `local` user
|
||||
|
||||
@@ -159,9 +168,9 @@ person logs in once. Sequence: deploy auth → she logs in → new empty account
|
||||
created → stop app, back up, run script to move `local`'s rows onto her real id,
|
||||
delete the empty row → restart.
|
||||
|
||||
**OPEN #4:** is that acceptable, or is a small admin endpoint preferable to a
|
||||
script? The existing project convention (`scripts/`, hand-run Python) suggests a
|
||||
script is in keeping.
|
||||
**OPEN #4 — SETTLED: documented one-off script** (ratified 2026-07-26), run
|
||||
with the app stopped and a DB backup taken first, per the existing `scripts/`
|
||||
convention. No admin endpoint.
|
||||
|
||||
---
|
||||
|
||||
@@ -186,10 +195,9 @@ image uploaded by two users is stored once on disk and simply has two rows, so
|
||||
deduplication survives. Deleting the last row referencing a hash removes the
|
||||
file.
|
||||
|
||||
**OPEN #5:** is this worth doing before real multi-user, or is it acceptable to
|
||||
ship auth first and treat this as a known limitation? I lean toward doing it *in
|
||||
the same phase as auth*, since the moment a second account exists the exposure is
|
||||
real and the fix requires a migration either way.
|
||||
**OPEN #5 — SETTLED: fix it in the same phase as auth** (ratified
|
||||
2026-07-26). The moment a second account exists the exposure is real, and the
|
||||
fix requires a migration either way.
|
||||
|
||||
### Frontend `localStorage`
|
||||
|
||||
@@ -241,7 +249,13 @@ It also carries data Petal has no equivalent for and could use: `Antonyms`,
|
||||
|
||||
So Phase D stops being gated on data and becomes an integration decision.
|
||||
|
||||
### OPEN #6a (new): how to integrate
|
||||
### OPEN #6a (new): how to integrate — **SETTLED: Option 3** (ratified 2026-07-26)
|
||||
|
||||
Import the package, open `dict.db` read-only. Prerequisite stands: DreamDict's
|
||||
module path must be renamed (or `replace`-directed) first — **that change
|
||||
lives in the dreamdict repo, not this one.** The migration caution below also
|
||||
stands: pt-PT/fr wire to DreamDict first; zh stays on ECDICT until compared on
|
||||
real lookups. Options kept below for the record.
|
||||
|
||||
**Option 1 — HTTP client.** Petal calls DreamDict on localhost:7777, exactly the
|
||||
pattern already used for Piper TTS (including graceful degradation when it's
|
||||
@@ -305,18 +319,26 @@ the phonetic dataset, the EN voice — is unaffected and stays shared.
|
||||
|
||||
## 7. Suggested sequence
|
||||
|
||||
1. **Settle OPEN #1** (forward-auth vs. in-app OIDC). Everything else follows.
|
||||
2. Deploy plumbing: Dockerfile, Traefik, real hostname, HTTPS. Auth needs a
|
||||
stable `BASE_URL` and a redirect URI regardless of which option wins.
|
||||
3. Auth itself: `Resolver` implementation, sessions if applicable, frontend 401
|
||||
handling.
|
||||
1. ~~Settle OPEN #1~~ **Settled: in-app OIDC.**
|
||||
2. Deploy plumbing: Dockerfile, Traefik, real hostname on parodia.dev, HTTPS,
|
||||
headscale route to vLLM on millenia (bound to the headscale interface
|
||||
only), VPS-local Piper, off-VPS DB backup. Auth needs a stable `BASE_URL`
|
||||
and a redirect URI.
|
||||
3. Auth itself: OIDC `Resolver`, sessions table (30-day sliding), allowlist,
|
||||
frontend 401 handling with draft preservation.
|
||||
4. Image store table + migration (same phase, per OPEN #5).
|
||||
5. Provision the second real account; migrate `local`'s data.
|
||||
6. `localStorage` namespacing.
|
||||
7. Per-user language. **No longer gated on data** — DreamDict covers all four
|
||||
languages. Sequence within it: rename DreamDict's module path → wire it in as
|
||||
a lexicon provider → pt-PT/fr first → compare zh quality → converge if it
|
||||
holds.
|
||||
5. Provision the second real account; migrate `local`'s data (script, app
|
||||
stopped, backup first).
|
||||
6. `localStorage` namespacing (key by user **and** language — see
|
||||
SUGGESTIONS.md §8).
|
||||
7. Per-user language pair. **No longer gated on data** — DreamDict covers all
|
||||
four languages. Sequence within it: rename DreamDict's module path → wire
|
||||
it in as a lexicon provider → pt-PT/fr first → compare zh quality →
|
||||
converge if it holds. The pair model and langpack shape are specified in
|
||||
SUGGESTIONS.md §1–§3.
|
||||
|
||||
These are expanded into checkboxed execution phases in `BUILD_PLAN.md`
|
||||
(Phase 15 onward) — that file remains the source of truth for progress.
|
||||
|
||||
---
|
||||
|
||||
@@ -337,19 +359,16 @@ the phonetic dataset, the EN voice — is unaffected and stays shared.
|
||||
|
||||
---
|
||||
|
||||
## 9. Questions for the reviewer
|
||||
## 9. Questions for the reviewer — all answered 2026-07-26
|
||||
|
||||
1. Forward-auth or in-app OIDC? (OPEN #1 — the one that matters most)
|
||||
2. Session lifetime, given a daily-use editor with auto-save? (OPEN #2)
|
||||
3. Allowlist Petal accounts separately from Authentik's user set? (OPEN #3)
|
||||
4. Migration script vs. admin endpoint for moving `local`'s data? (OPEN #4)
|
||||
5. Fix the image store alongside auth, or ship auth with it as a known
|
||||
limitation? (OPEN #5)
|
||||
6. ~~Is there a usable open English↔European-Portuguese dictionary dataset?~~
|
||||
**Answered: DreamDict**, which covers en/fr/pt-PT/zh. The live question is now
|
||||
*how* to integrate it — HTTP service, build-time extraction, or importing the
|
||||
package and opening `dict.db` read-only. (OPEN #6a; I recommend the third)
|
||||
7. Does replacing the zh gloss (ECDICT → CC-CEDICT) risk regressing a feature in
|
||||
daily use, and should zh stay on ECDICT until the two are compared?
|
||||
|
||||
Anything above that reads as settled but shouldn't be is also fair game.
|
||||
1. ~~Forward-auth or in-app OIDC?~~ **In-app OIDC** (OPEN #1).
|
||||
2. ~~Session lifetime?~~ **30-day sliding** (OPEN #2).
|
||||
3. ~~Allowlist?~~ **Yes**, `PETAL_ALLOWED_SUBS` or group claim (OPEN #3).
|
||||
4. ~~Script vs. admin endpoint?~~ **Script**, app stopped, backup first (OPEN #4).
|
||||
5. ~~Image store timing?~~ **Same phase as auth** (OPEN #5).
|
||||
6. ~~pt-PT dictionary data?~~ **DreamDict**, integrated per **Option 3**
|
||||
(import package, read-only `dict.db`; module rename is the prerequisite)
|
||||
(OPEN #6a).
|
||||
7. ~~zh gloss regression risk?~~ **zh stays on ECDICT** until compared against
|
||||
DreamDict on real lookups from her actual documents; converge only if
|
||||
quality holds.
|
||||
|
||||
Reference in New Issue
Block a user