From f088df25f5f8dc7cecba58935000cf5b5182ace2 Mon Sep 17 00:00:00 2001 From: jacob-web10-nyc Date: Wed, 26 Aug 2026 21:47:23 -0400 Subject: [PATCH 1/2] =?UTF-8?q?docs(kb)=20+=20docs(strategy):=20D49=20?= =?UTF-8?q?=E2=80=94=20app=20store=20endpoint=20surface=20spec=20(URL-keye?= =?UTF-8?q?d=20product=20page,=20reviews)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- knowledge/changelogs/CHANGELOG.md | 3 + knowledge/knowledge-base/web10-v3/README.md | 5 +- .../web10-v3/app-store/endpoints.md | 233 ++++++++++++++++++ .../web10-v3/app-store/overview.md | 8 +- .../knowledge-base/web10-v3/db/clickhouse.md | 7 +- knowledge/strategy/decisions.md | 62 +++++ 6 files changed, 311 insertions(+), 7 deletions(-) create mode 100644 knowledge/knowledge-base/web10-v3/app-store/endpoints.md diff --git a/knowledge/changelogs/CHANGELOG.md b/knowledge/changelogs/CHANGELOG.md index c8528022..9bcd7ded 100644 --- a/knowledge/changelogs/CHANGELOG.md +++ b/knowledge/changelogs/CHANGELOG.md @@ -1,3 +1,6 @@ +3.14.1 || 26.08.2026 +docs(kb) + docs(strategy): D49 — the app store's endpoint surface is spec'd, and the product page's identity gap is closed on paper. Operator: "in the apps would be cool if there was a little button that said to see more … it expands, has a deeper paragraph description from the manifest, and then has ALL the available stats, the 90d users, the visits, the 30d users, you get what i mean, everything in one screen, and the apps reviews comments kind of thing" — then "clicking the tile opens this modal or whatever it is" — then "in the knowledge base we need to talk about how the app store is going to work, it will need its own endpoints." **The finding that shaped the call:** the product page (PR #426, `/app-store/app/:id`) is broken in two independent ways — it fetches `PATCH /discover/app/{id}`, a route that does not exist in the API (every page 404s into "App not found"), and `/v3/stats` blanks `web10apps_post_id` to `""` (the v3 `apps` table never had the column — it's a v2 vestige), so the card never takes the internal-Link path and the tile opens the site directly. The root cause is an identity gap: the UI wanted a short ID for the route, but D47 already made the full URL the app's identity. **Decided (D49):** (1) the product page is a PAGE, not a modal — the existing route is the surface; tap tile → page, Open button → launch (App Store convention; a modal with no URL state is a review rejection per the deep-link rule, and it's cramped for "everything in one screen"); a "see more" expander on the card is rejected too (third tap target on a small tile, breaks grid row alignment); (2) a new `GET /v3/apps/detail?url=` serves the page — public, pure read, one call: app + rating aggregate + rating list + stats, and NO visit bump (a product-page view is not an app visit; visits are SDK pings on the app's own pages); (3) the URL is the key — `web10apps_post_id` is retired, `/v3/stats` stops blanking it, the route param becomes the URL-encoded URL; a parallel ID (post_id / slug / numeric) is rejected — D47: the URL is the identity, a second ID system is drift by construction; (4) reviews = rating + optional comment — `app_ratings` gains a `comment` column, one voice per author (dedup key `(target_app_id, author)`), no separate reviews table; (5) the stats model is honest about what the node knows — per-app: visits (anonymous pings) + `authorized_users` (distinct `app_contracts` holders for the app's origin — origin-scoped because contracts are CORS-scoped, so two path-apps on one host share it); node-level: members + new members 30d/90d from `users.created_at`; there is no per-app usage log (the store counts *runs* and *consents*, not *users of app X*), and the page labels node numbers as context, not app metrics. **New KB doc:** `knowledge-base/web10-v3/app-store/endpoints.md` — the full surface (register / detail / list / rating / ratings / admin / approve / pwa_listing / stats), the auth split (reads public, writes signed or admin — D41), the detail response shape, what each number means, the product-page flow, and a logistics table (built vs scoped). The build itself (the endpoint, the comment column, the UI wiring) is the next bite — this is the spec, per the KB-is-root-of-trust rule. No code changes; docs only. + 3.14.0 || 26.08.2026 feature(api) + fix(auth-ui) + docs(kb): node config moves to ClickHouse — the admin panel works again (D48). The v3 ecosystem stack runs no Mongo (removed in #526), but `config.py` still read/wrote the Mongo `web10.config` / `web10.jwt_keys` collections — so on dev/prod every config read blocked ~30s on a dead pymongo server-selection timeout, then raised: `check_admin` 500'd, the auth UI's `checkAdmin` set `isAdmin=false`, and the Node Config panel never rendered for the node's admin (operator: "I am not seeing the admin panel … I am jacoby149"). `/setup` status and `/setup/configure` were broken the same way. e2e never caught it — the e2e compose still runs FerretDB. New `node_config` table (ReplacingMergeTree, house dedup-then-filter reads; saves append): `config_id='node'` = the node config JSON, `config_id='jwt:'` = JWT key records. `config.py` keeps its interface and delegates to ClickHouse (`get_node_config`/`save_node_config`/`save_jwt_key`/`get_latest_jwt_key` in the v3 service); the boot-time schema self-heal gains an idempotent `CREATE TABLE IF NOT EXISTS node_config` for pre-existing volumes (alongside the `apps.visits` ALTER). Admin-ness is unchanged in model: the config `admins` list unioned with `settings.DEFAULT_ADMINS` (the lockout-proof baseline — `jacoby149` is admin on every node until setup says otherwise). The auth UI's `checkAdmin` now calls the purpose-built `POST /am_admin` (`{admin: bool}`, never errors) instead of the app-store admin list (`/v3/apps/admin` — semantically wrong, and its 200/401 shape was load-bearing by accident). Tests: 12 new API unit tests (`test_node_config.py` — get/save with named columns, list_admins baseline+union, admin_exists, /am_admin admin/non-admin/no-token never-500, /setup shape) + a new e2e browser test (Node Config nav visible to the node admin, hidden to a regular user — the real seam through checkAdmin). KB: D48 in decisions.md, `node_config` in db/clickhouse.md, new `setup/node-config.md` (the model: config contents, admins union, setup flow, the /am_admin gate) + README links. diff --git a/knowledge/knowledge-base/web10-v3/README.md b/knowledge/knowledge-base/web10-v3/README.md index fb8c9c5b..28288d0a 100644 --- a/knowledge/knowledge-base/web10-v3/README.md +++ b/knowledge/knowledge-base/web10-v3/README.md @@ -30,7 +30,8 @@ web10-v3/ ├── setup/ ← node setup & operator config │ └── node-config.md ← the node_config table, admins model, setup flow, /am_admin gate ├── app-store/ ← the node's public storefront -│ └── overview.md ← registration (a path is an app), visits, PWA manifests, store UI +│ ├── overview.md ← registration (a path is an app), visits, PWA manifests, store UI +│ └── endpoints.md ← the store's endpoint surface: the product page, ratings, stats ├── groups/ ← groups as a platform primitive │ ├── overview.md ← policy containers, roles, join policies │ ├── identity.md ← profiles, URLs, service-scoped roles @@ -60,7 +61,7 @@ web10-v3/ - **Auth & Consent** — `auth/auth.md` (token flow), `auth/consent.md` (consent experience, ideal UX) - **DB** — `db/clickhouse.md` (tables, indexes, patterns) - **Setup & Config** — `setup/node-config.md` (node_config table, admins, /am_admin) -- **App Store** — `app-store/overview.md` (registration, visits, PWA manifests) +- **App Store** — `app-store/overview.md` (registration, visits, PWA manifests), `app-store/endpoints.md` (the endpoint surface: product page, ratings, stats) - **Security** — `security/overview.md` (invariants I1–I5, two-contract model, blocking) - **Groups** — `groups/overview.md` (primitive), `groups/identity.md` (profiles) - **Social** — `social/overview.md` (implementation), `social/cross-app-sharing.md` (patterns) diff --git a/knowledge/knowledge-base/web10-v3/app-store/endpoints.md b/knowledge/knowledge-base/web10-v3/app-store/endpoints.md new file mode 100644 index 00000000..ce0e978c --- /dev/null +++ b/knowledge/knowledge-base/web10-v3/app-store/endpoints.md @@ -0,0 +1,233 @@ +# The App Store's Endpoints + +The store's API surface. `overview.md` covers what an app *is* to the store +(identity, registration, the manifest). This doc covers how the store +*serves* it: every endpoint the storefront and the product page talk to, +what they return, and what the numbers mean. + +## The Page Is the Product + +A visitor is browsing the grid. A tile catches their eye — a name, an icon, +"1,337 visits." Before they tap Open, they want three answers: + +1. **What is it?** — the description, the screenshots. +2. **Is it real?** — the numbers. Visits, users, how long it's been around. +3. **What do people say?** — the ratings. + +A store that can't answer those three with data is a directory of links. +The App Store answers them with one page and one endpoint: the **product +page** (`/app-store/app/:id`) is served by `GET /v3/apps/detail`. The page +is a URL — refreshable, shareable, bookmarkable. You can send someone the +link to an app, the way you send someone a link to anything else. That is +the "oh, okay" moment: the store is a real place with real pages, not a +list of buttons. + +The endpoint behind it is the store's own, and it is the center of gravity +of this doc. + +## The Identity Call: the URL Is the Key + +The product page is keyed by the **app's URL** — the same identity +registration uses (D47). `https://www.web10.app/docs/notes/` *is* the app, +and the page for it is `/app-store/app/{urlencoded-url}`. + +This retires `web10apps_post_id`. The field was a v2 vestige — the +`#web10apps` discovery ledger, dropped in v3 (overview.md). The v3 `apps` +table never had the column; `/v3/stats` was blanking it to `""` for the UI; +the UI's detail route read that blank. The result: the card never linked to +the page, and the page's fetch hit an endpoint that does not exist +(`PATCH /discover/app/{id}` — a phantom). Every tile opened the site; every +page 404'd. + +The root cause was an identity gap: the UI wanted a short ID for the route, +but the store's identity is already the URL. Keying the detail endpoint on +the URL deletes the gap instead of papering over it with a second ID +system. No post IDs, no slugs, no numeric IDs. The URL is the app. + +## The Surface + +All store endpoints live under `/v3/apps`, except the manifest proxy +(`GET /pwa_listing`, system router) and the node stats (`POST /v3/stats`, +system router). + +| Endpoint | Auth | Called by | What it does | +|---|---|---|---| +| `POST /v3/apps/register` | anonymous | the SDK, on every `createV3Client()` | registration + visit ping | +| `GET /v3/apps/detail?url=` | anonymous | the product page | the whole page: app + rating aggregate + rating list + stats | +| `POST /v3/apps/list` | anonymous | SDK `getApps()` | approved apps, with visits | +| `POST /v3/apps/rating` | signed | SDK `rateApp()` | upsert a 1–5 star rating, optional comment | +| `POST /v3/apps/ratings` | anonymous | product page, SDK `getAppRatings()` | the rating list for an app | +| `POST /v3/apps/admin` | admin | the node console | every app with approval state + rating aggregate | +| `POST /v3/apps/approve` | admin | the node console | approve / reject | +| `GET /pwa_listing?url=` | anonymous | the store grid, the product page | PWA manifest proxy | +| `POST /v3/stats` | anonymous | the store page | node numbers (members, apps, storage) + the grid's app list | + +The auth split is the store's posture: **reads are public, writes are +signed or admin.** The store is a public surface (D41 — the node is +readable by design; discovery, search, auditability). A signed-out visitor +can browse, open a product page, and read every rating. Only *rating* +requires a token — a rating is a user's record, and it carries the author's +name. Only *approval* requires admin. + +### `POST /v3/apps/register` — built + +Anonymous. Body: `{url, name?, description?, icon_url?, screenshots?}`. + +- **First time** — inserts the row: `visits: 1`, `approved: 0`, + `review_state: 'pending'`. The app is in the node's app list, not yet in + the public store. +- **Repeat** — appends a new row with `visits + 1` (the + ReplacingMergeTree dedup keeps the latest per url). Non-empty metadata in + the body replaces the stored value and bumps `metadata_version`; the + empty auto-ping keeps what's stored. +- Never blocks app init — the SDK fires it and forgets. + +The full model is in overview.md. This doc adds nothing to it. + +### `GET /v3/apps/detail?url=` — the product page, missing + +**The endpoint this doc exists to specify.** The product page was built +against a phantom (`PATCH /discover/app/{id}` — no such route in the API); +this is the real one. It is a pure read — **no visit bump.** A +product-page view is not an app visit; visits are SDK pings on the app's +own pages, and the counter means what overview.md says it means. + +GET, not POST: a pure public read with a query param is the +`/pwa_listing` class — cacheable, no body, no side effect. + +Response: + +```json +{ + "url": "https://www.web10.app/docs/notes/", + "name": "Notes", + "description": "Private notes on your node.", + "icon_url": "https://www.web10.app/docs/notes/icon-192.png", + "screenshots": ["https://..."], + "visits": 1337, + "review_state": "approved", + "registered_at": "2026-07-30T01:29:37Z", + "rating": { "average": 4.6, "count": 12 }, + "ratings": [ + { "author": "alice", "rating": 5, "comment": "fast.", "created_at": "2026-08-01T12:00:00Z" } + ], + "stats": { + "authorized_users": 42, + "node": { "members": 579, "new_members_30d": 12, "new_members_90d": 40 } + } +} +``` + +- Unknown `url` → 404. The page renders its not-found state. +- The UI prefers the PWA manifest for name and icon (via `/pwa_listing`) + over the stored values — the manifest is the identity source + (overview.md). The endpoint returns what's stored; the page decides what + to show. + +**What the numbers mean:** + +| Field | Source | Meaning | +|---|---|---| +| `visits` | `apps.visits` | Page loads of the app itself (SDK pings). Not product-page views. | +| `rating.average` / `rating.count` | `app_ratings` aggregate | One rating per author — the table's dedup key is `(target_app_id, author)`, so re-rating replaces. | +| `stats.authorized_users` | `app_contracts` | Distinct users holding a live app contract for the app's **origin**. Honest scoping: contracts are origin-scoped (CORS), so two path-apps on one host share this number. It is "users who let this host touch their data," not a per-path count. | +| `stats.node.members` | `users` | Node members, all time. | +| `stats.node.new_members_30d` / `new_members_90d` | `users.created_at` | Members who joined in the window. | + +The 30d/90d numbers are **node-level by construction.** There is no +per-app usage log: visits are anonymous pings, so the store knows *how many +times* an app ran, never *who* ran it. Showing node growth on the product +page is context — "this node is alive" — not an app metric. The doc says +so, and the UI labels it so. A per-app user count would require a usage +event the protocol does not have; that is v4 territory if it ever comes. + +### `POST /v3/apps/rating` — built, gains a comment + +Signed. Body: `{token, body: {target_app_id, rating, comment?}}`. +`target_app_id` is the app's **URL** — the SDK's `rateApp({appId: url})` +already keys on it. `rating` is 1–5. `comment` is optional text. + +**Reviews are a rating with words.** No separate reviews table. The +`app_ratings` table gains a `comment String DEFAULT ''` column (an ALTER, +the way `apps.visits` got its column), the rating endpoint accepts it, and +the ratings list returns it. The dedup semantics carry over untouched: one +voice per user per app, latest wins, no history. The store shows the star +first, the text under it. + +### `POST /v3/apps/ratings` — built, gains a comment + +Anonymous. Body: `{body: {target_app_id}}`. Returns the rating list — +author, rating, comment, created_at — newest first. The product page +renders this as the reviews section; an empty list renders an empty state, +not a gap. + +### `POST /v3/apps/list`, `POST /v3/stats`, `GET /pwa_listing` — built + +The grid's plumbing, documented in overview.md (registration, the +plug-slot filter, the manifest proxy). The division of labor: + +- **`/v3/stats`** serves the store *page* — the header numbers (members, + app count, storage) and the grid's app list (approved, with visits) in + one round trip. +- **`/v3/apps/list`** serves the *SDK* — `getApps()`, approved apps for a + client. The body carries a `token` field (the SDK's `TokenOnly` shape) + that the endpoint does not verify — public in effect, token-shaped for + the SDK's uniform call surface. +- **`/pwa_listing`** serves identity — the manifest proxy. + +Both list endpoints return the same app shape. The store page uses +`/v3/stats` because it needs the node numbers anyway. + +### `POST /v3/apps/admin`, `POST /v3/apps/approve` — built + +The operator surface (the node console). The admin list: every app with +approval state, visits, and the rating aggregate. Approve/reject: the +review. Small node, operator's call (overview.md). + +## The Product Page Flow + +```mermaid +sequenceDiagram + participant V as Visitor + participant S as Store grid (/app-store) + participant P as Product page (/app-store/app/:url) + participant N as Node API + + V->>S: browse + S->>N: POST /v3/stats + N-->>S: members, apps (approved, visits), storage + S->>N: GET /pwa_listing?url= (per app — name + icon) + V->>P: tap a tile (route = /app-store/app/{urlencoded-url}) + P->>N: GET /v3/apps/detail?url= + N-->>P: app + rating aggregate + ratings + stats + P->>N: GET /pwa_listing?url= (manifest preferred for identity) + P-->>V: what it is, the numbers, what people say + V->>N: (signed in) POST /v3/apps/rating +``` + +One call for the page. The manifest fetch is the same proxy the grid uses — +the page prefers it for identity and falls back to the stored values. + +## What This Is Not + +- **Not a usage log.** No per-app, per-user usage tracking. Visits are + anonymous pings; `authorized_users` comes from contracts, not from "who + used the app." The store counts *runs* and *consents*, not *users of app + X*. +- **Not a review platform.** One rating per author, upsert, no history, no + moderation queue. The operator's approve/reject is the moderation. +- **Not a second ID system.** The URL is the app. Anything that introduces + a parallel identifier — post IDs, slugs, numeric IDs — is a rejection. + D47 made the URL the identity, and every store endpoint keys on it. + +## Logistics + +| Piece | State | +|---|---| +| register / list / rating / ratings / admin / approve | built (v3) | +| `/pwa_listing` manifest proxy | built | +| `/v3/stats` (node numbers + grid list) | built | +| `GET /v3/apps/detail` | **missing** — the product page is built against a phantom endpoint and 404s for every app; this doc is the spec | +| `app_ratings.comment` column; comment in rating / ratings / detail | scoped here, not built | +| `stats.authorized_users` + node 30d/90d members | scoped here, not built (two new ClickHouse queries) | +| UI: card → page wiring (URL-keyed route, drop `web10apps_post_id`) | not built — the stats response's blanked `web10apps_post_id` is why the card opens the site instead of the page today | diff --git a/knowledge/knowledge-base/web10-v3/app-store/overview.md b/knowledge/knowledge-base/web10-v3/app-store/overview.md index 94bc537c..78d53d7f 100644 --- a/knowledge/knowledge-base/web10-v3/app-store/overview.md +++ b/knowledge/knowledge-base/web10-v3/app-store/overview.md @@ -7,7 +7,8 @@ visits, no algorithm, no promotion.** This doc is the v3 model: what an app *is* to the store, how it gets there, how it gets counted, and how it gets shown. The data model (the `apps` table) lives in `../db/clickhouse.md`; the SDK surface (`registerApp`, -`getApps`, `rateApp`) in `../sdk/api.md`. +`getApps`, `rateApp`) in `../sdk/api.md`; the endpoint surface — every +route the storefront and the product page talk to — in `endpoints.md`. ## An App Is a URL — Including the Path @@ -156,8 +157,9 @@ Ratings live in `app_ratings` (1–5 stars, per author, per app). ## What This Is Not - **Not a discovery feed.** v2 had a `web10apps` post ledger for social - discovery of apps; v3 drops it (`web10apps_post_id` is a vestigial empty - field). The store is the surface. + discovery of apps; v3 drops it (`web10apps_post_id` is retired — D49; + the URL is the key, and the product page is keyed on it). The store is + the surface. - **Not moderated content.** There is no v2 `pending_on_change` review state machine — an approved app's listing updates on repeat registration, and the operator's approve/reject is the review. Small diff --git a/knowledge/knowledge-base/web10-v3/db/clickhouse.md b/knowledge/knowledge-base/web10-v3/db/clickhouse.md index 685fa166..6ba711fb 100644 --- a/knowledge/knowledge-base/web10-v3/db/clickhouse.md +++ b/knowledge/knowledge-base/web10-v3/db/clickhouse.md @@ -107,6 +107,7 @@ erDiagram String target_app_id PK String author PK "UInt8" rating + String comment String provider } @@ -357,7 +358,7 @@ CREATE TABLE apps ( ORDER BY url; ``` -**Primary key:** `url` — the app's origin. +**Primary key:** `url` — the app's full URL, path included (D47). **`review_state`:** `pending`, `approved`, `pending_on_change`, `rejected`. @@ -365,13 +366,15 @@ ORDER BY url; ## App Ratings -Star ratings for apps. One per (author, target_app). +Star ratings for apps. One per (author, target_app). A review is a rating +with words — `comment` is optional text (D49). ```sql CREATE TABLE app_ratings ( author String, target_app_id String, rating UInt8, + comment String DEFAULT '', provider String, created_at DateTime64(3), updated_at DateTime64(3), diff --git a/knowledge/strategy/decisions.md b/knowledge/strategy/decisions.md index 62ff43e1..93074dc2 100644 --- a/knowledge/strategy/decisions.md +++ b/knowledge/strategy/decisions.md @@ -9,6 +9,68 @@ Status legend: [decided] intent set · [in-progress] · [open] still debating. --- +### D49 — App store product page: the detail endpoint is keyed by the app's URL; `web10apps_post_id` is retired [decided] +Operator, 26.08.2026 — "in the apps would be cool if there was a little +button that said to see more … it expands, has a deeper paragraph +description from the manifest, and then has ALL the available stats, the +90d users, the visits, the 30d users, you get what i mean, everything in +one screen, and the apps reviews comments kind of thing" — then "clicking +the tile opens this modal or whatever it is" — then "in the knowledge base +we need to talk about how the app store is going to work, it will need its +own endpoints." + +**Decided** — + +1. **The product page is a page, not a modal.** The existing + `/app-store/app/:id` route (PR #426) is the surface. Tap tile → page; + the Open button → launch. Deep-linkable and shareable — the address bar + is part of the product. A modal with no URL state is a review rejection + per the deep-link rule, and it is cramped for "everything in one + screen." +2. **A new `GET /v3/apps/detail?url=` serves the page.** Public, pure + read, one call: app + rating aggregate + rating list + stats. **No + visit bump** — a product-page view is not an app visit; visits are SDK + pings on the app's own pages, and the counter means what + `app-store/overview.md` says it means. +3. **The URL is the key.** D47 made the full URL the app's identity; the + detail endpoint keys on it, and `web10apps_post_id` is retired (v2 + vestige — the `#web10apps` discovery ledger, dropped in v3; the v3 + `apps` table never had the column; `/v3/stats` stops blanking it; the + UI route param becomes the URL-encoded URL). +4. **Reviews are a rating with words.** `app_ratings` gains a `comment` + column; one voice per author (dedup key `(target_app_id, author)`), + latest wins, no history. No separate reviews table. +5. **The stats model is honest about what the node knows.** Per-app: + visits (anonymous pings) + `authorized_users` (distinct + `app_contracts` holders for the app's origin — origin-scoped because + contracts are CORS-scoped; two path-apps on one host share it). + Node-level: members + new members 30d/90d from `users.created_at`. + There is no per-app usage log — the store counts *runs* and *consents*, + not *users of app X* — and the page labels node numbers as context, not + app metrics. + +**Why:** the product page was built (PR #426) against a phantom endpoint +(`PATCH /discover/app/{id}` — no such route in the API, so every page 404s +into "App not found") and a blanked ID (`/v3/stats` sets +`web10apps_post_id: ""`, so the card never takes the internal-Link path +and the tile opens the site directly). The root cause is an identity gap: +the UI wanted a short ID for the route, but the store's identity is +already the URL. Keying on the URL deletes the gap instead of papering +over it with a second ID system. + +**Rejected:** a modal (no URL state — review rejection; cramped for the +full stats + reviews layout); a "see more" expander on the card (a third +tap target on a small tile; inline expansion breaks grid row alignment); a +parallel ID for the route (post_id / slug / numeric — D47: the URL is the +identity, a second ID system is drift by construction); a separate +`app_reviews` table (duplicates `app_ratings`' dedup semantics); per-app +windowed user counts (would require a usage event the protocol does not +have — v4 territory if it ever comes). + +Full model: `knowledge-base/web10-v3/app-store/endpoints.md`. + +--- + ### D48 — Node config lives in ClickHouse: v3 stacks run no Mongo [decided] Operator, 26.08.2026 — "I am not seeing the admin panel … confirm I am admin … auth.dev.web10.app is broken, we need to fix the code so I see the From a070646a7a4094b4413a88f27defce66206c48b4 Mon Sep 17 00:00:00 2001 From: jacob-web10-nyc Date: Wed, 26 Aug 2026 22:06:34 -0400 Subject: [PATCH 2/2] =?UTF-8?q?docs(changelog):=20renumber=20D50=20entry?= =?UTF-8?q?=20to=203.16.1=20=E2=80=94=203.16.0=20taken=20by=20#680?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- knowledge/changelogs/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowledge/changelogs/CHANGELOG.md b/knowledge/changelogs/CHANGELOG.md index 7a1f5875..3320b3f0 100644 --- a/knowledge/changelogs/CHANGELOG.md +++ b/knowledge/changelogs/CHANGELOG.md @@ -1,4 +1,4 @@ -3.15.1 || 26.08.2026 +3.16.1 || 26.08.2026 docs(kb) + docs(strategy): D50 — the app store's endpoint surface is spec'd, and the product page's identity gap is closed on paper. Operator: "in the apps would be cool if there was a little button that said to see more … it expands, has a deeper paragraph description from the manifest, and then has ALL the available stats, the 90d users, the visits, the 30d users, you get what i mean, everything in one screen, and the apps reviews comments kind of thing" — then "clicking the tile opens this modal or whatever it is" — then "in the knowledge base we need to talk about how the app store is going to work, it will need its own endpoints." **The finding that shaped the call:** the product page (PR #426, `/app-store/app/:id`) is broken in two independent ways — it fetches `PATCH /discover/app/{id}`, a route that does not exist in the API (every page 404s into "App not found"), and `list_store_apps` blanks `web10apps_post_id` to `""` (the v3 `apps` table never had the column — it's a v2 vestige), so the card never takes the internal-Link path and the tile opens the site directly. The root cause is an identity gap: the UI wanted a short ID for the route, but D47 already made the full URL the app's identity. **Decided (D50 — renumbered from D49, which #681 took for the real-user metrics model):** (1) the product page is a PAGE, not a modal — the existing route is the surface; tap tile → page, Open button → launch (App Store convention; a modal with no URL state is a review rejection per the deep-link rule, and it's cramped for "everything in one screen"); a "see more" expander on the card is rejected too (third tap target on a small tile, breaks grid row alignment); (2) a new `GET /v3/apps/detail?url=` serves the page — public, pure read, one call: app + rating aggregate + rating list + the full five-metric breakdown (D49 item 5: the grid card shows the headline, the detail page shows the full breakdown — this spec is what that line points at) + the node macro, and NO visit bump (a product-page view is not an app visit; `app_visits` rows come only from SDK pings carrying a verified token); (3) the URL is the key — `web10apps_post_id` is retired, `list_store_apps` stops blanking it, the route param becomes the URL-encoded canonical URL; a parallel ID (post_id / slug / numeric) is rejected — D47: the URL is the identity, a second ID system is drift by construction; (4) reviews = rating + optional comment — `app_ratings` gains a `comment` column, one voice per author (dedup key `(target_app_id, author)`), no separate reviews table; (5) the page shows D49's metric set, not an invention — per app: visits + users_1d/30d/90d/1y (the same realtime `get_app_metrics` queries the grid runs over `app_visits`, exact countDistinct) + the `/v3/stats` node macro (users, app_count, active_users, storage); a consent-based user count (`app_contracts` holders) is rejected — D49's metric set is the store's number, consent is a separate trust surface. **New KB doc:** `knowledge-base/web10-v3/app-store/endpoints.md` — the full surface (register / detail / list / rating / ratings / admin / approve / pwa_listing / stats), the auth split (reads public, writes signed or admin — D41), the detail response shape, what each number means, the product-page flow, and a logistics table (built vs scoped). The build itself (the detail endpoint, the comment column, the card-to-page wiring) is the next bite — this is the spec, per the KB-is-root-of-trust rule. No code changes; docs only. 3.16.0 || 26.08.2026 fix(api) + fix(ui) + feature(ui): the Node Config panel shows what the node actually runs — and its Save button works. Operator review of the panel on auth.dev.web10.app: "seeing alot of blanks", "clickhouse url is blank though... should default to the docker network thing", "media storage settings are empty too... should have the minio values", plus field-trimming asks. Root cause of the blanks: `POST /config` returned the raw saved `node_config` row — empty on a fresh node — so the form rendered blank even though the node was running on its `settings.py` values (env-overridden). New `config_svc.effective_config()`: settings.py is the base (what the node connects with and enforces at boot), the saved config overlays it field by field; `POST /config` now returns that. The ClickHouse URL defaults to the docker-network connection string composed from the `CLICKHOUSE_*` settings (`clickhouse://web10:web10@clickhouse:8123/web10` — the node connects with those parts, the URL is the human-readable reference form), and the S3 section defaults to the MinIO values (`http://minio:9000`, `web10-media`, `minioadmin`/`minioadmin`, `us-east-1`). `effective_config` coerces the env-override loop's raw strings back to typed values (int/float/bool). Field trimming per the operator: Node Identity drops Brand Name, Logo (dark), Logo (light), and Signing Algorithm (kept: Provider Domain, CORS Service Managers, Token Expiry); Stripe drops the four subscription-ID fields + Dev Pay Split (kept: Mode, Test API Key, Live API Key). Credential fields (ClickHouse URL, S3 secret, Twilio auth token, Stripe keys) render as visible text instead of password dots — the panel's job is to show the operator what the node runs, and the surface is admin-gated (`check_admin`: node-signed JWT + admin list); `POST /config` correspondingly strips only `private_key` (the signing secret — the UI has no field for it) instead of the five-field strip list. **The Save button was dead** — it sent `PATCH /config`, a route that does not exist (only `POST /config` and `POST /config/update` do), so every save 405'd; it now POSTs the diff-only payload to `/config/update` (the diff logic stays — unchanged values, including the billing fields the form no longer shows, stay off the wire). Tests: 7 new API unit tests in `test_node_config.py` (effective-config defaults, CH-URL composition, saved-overlay, env-string coercion; endpoint: admin sees effective config + no `private_key`, saved values surface, non-admin → 403 through the real exception handler). 708 API tests + 96 UI tests green, `tsc -b` + build clean.