From 57177ed2a66cadd4f4f59a212b9d2f2d8c5e745e Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Thu, 1 Oct 2026 21:20:18 +0530 Subject: [PATCH 1/3] AppConfig.user_store: let an app opt into the per-user store whatever its access A public app with optional sign-in (its signed-in visitors keep their data on the server, everyone else keeps it in the browser) needs the auth plugin's per-user store, which today only protected:user apps get. `user_store = true` in app.toml says so. Like `access`, enlace carries it and does not interpret it; enlace_auth reads it (owner-granted data shares, enlace_auth ADR 0001). --- enlace/base.py | 9 +++++++++ enlace/discover.py | 1 + enlace/tests/test_discover.py | 14 ++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/enlace/base.py b/enlace/base.py index fac9aca..6adde84 100644 --- a/enlace/base.py +++ b/enlace/base.py @@ -153,6 +153,15 @@ class AppConfig(BaseModel): "Empty list means any authenticated user allowed." ), ) + user_store: bool = Field( + default=False, + description=( + "Offer this app a per-user store to signed-in visitors even when its " + "access is not protected:user (e.g. a public app with optional sign-in). " + "Consumed by enlace_auth, like ``access``; enlace itself does not " + "interpret it." + ), + ) display_name: str = "" provenance: dict[str, str] = Field(default_factory=dict) diff --git a/enlace/discover.py b/enlace/discover.py index 829e708..a8b6c8c 100644 --- a/enlace/discover.py +++ b/enlace/discover.py @@ -367,6 +367,7 @@ def _load_toml(path: Path) -> dict: "access": "access", "shared_password_env": "shared_password_env", "allowed_users": "allowed_users", + "user_store": "user_store", "display_name": "display_name", # `title` is an alias for `display_name`. If an app.toml sets BOTH, the # later key in this map wins (dict-insertion order in _overlay_toml_fields) diff --git a/enlace/tests/test_discover.py b/enlace/tests/test_discover.py index 765782f..0d6a10a 100644 --- a/enlace/tests/test_discover.py +++ b/enlace/tests/test_discover.py @@ -107,6 +107,20 @@ def test_discover_app_toml_override(single_app_dir): assert app.provenance["route_prefix"] == "override: app.toml" +def test_discover_user_store_flag(single_app_dir, tmp_apps_dir): + """``user_store = true`` reaches the AppConfig (asgi and frontend-only apps); default off.""" + discoverer = _make_discoverer() + assert discoverer.discover(single_app_dir)[0].user_store is False + (single_app_dir / "foo" / "app.toml").write_text('access = "public"\nuser_store = true\n') + static = tmp_apps_dir / "site" + (static / "frontend").mkdir(parents=True) + (static / "frontend" / "index.html").write_text("
hi
") + (static / "app.toml").write_text('access = "public"\nuser_store = true\nfrontend_dir = "frontend"\n') + apps = {a.name: a for a in discoverer.discover(tmp_apps_dir)} + assert apps["foo"].user_store is True + assert apps["site"].user_store is True + + def test_discover_conflict_detection(tmp_apps_dir): """Two apps resolving to the same route trigger a conflict.""" for name in ["app_a", "app_b"]: From e989f1258d9211899120ff280f62c4e67017312f Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Thu, 1 Oct 2026 21:47:42 +0530 Subject: [PATCH 2/3] test: keep lines under 88 --- enlace/tests/test_discover.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/enlace/tests/test_discover.py b/enlace/tests/test_discover.py index 0d6a10a..5de1dff 100644 --- a/enlace/tests/test_discover.py +++ b/enlace/tests/test_discover.py @@ -111,11 +111,12 @@ def test_discover_user_store_flag(single_app_dir, tmp_apps_dir): """``user_store = true`` reaches the AppConfig (asgi and frontend-only apps); default off.""" discoverer = _make_discoverer() assert discoverer.discover(single_app_dir)[0].user_store is False - (single_app_dir / "foo" / "app.toml").write_text('access = "public"\nuser_store = true\n') + toml = 'access = "public"\nuser_store = true\n' + (single_app_dir / "foo" / "app.toml").write_text(toml) static = tmp_apps_dir / "site" (static / "frontend").mkdir(parents=True) (static / "frontend" / "index.html").write_text("hi
") - (static / "app.toml").write_text('access = "public"\nuser_store = true\nfrontend_dir = "frontend"\n') + (static / "app.toml").write_text(toml + 'frontend_dir = "frontend"\n') apps = {a.name: a for a in discoverer.discover(tmp_apps_dir)} assert apps["foo"].user_store is True assert apps["site"].user_store is True From 90db8731472b71d85ad792e466a04c233903b68d Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Thu, 1 Oct 2026 21:47:51 +0530 Subject: [PATCH 3/3] test: shorter docstring line --- enlace/tests/test_discover.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enlace/tests/test_discover.py b/enlace/tests/test_discover.py index 5de1dff..92af9f0 100644 --- a/enlace/tests/test_discover.py +++ b/enlace/tests/test_discover.py @@ -108,7 +108,7 @@ def test_discover_app_toml_override(single_app_dir): def test_discover_user_store_flag(single_app_dir, tmp_apps_dir): - """``user_store = true`` reaches the AppConfig (asgi and frontend-only apps); default off.""" + """``user_store = true`` reaches AppConfig (asgi and frontend-only); default off.""" discoverer = _make_discoverer() assert discoverer.discover(single_app_dir)[0].user_store is False toml = 'access = "public"\nuser_store = true\n'