a31cc5d3 widened the jwt-only census to app/plugins and shipped a ceiling
of 611 measured on a dev box. CI measured 591 and went red: most
app/plugins/ live copies are GITIGNORED, so the count depended on which
extensions a machine happened to have installed. That is the same
machine-dependence the builtin-copy drift census exists to prevent, and
this walked straight into it.
Count builtin-extensions/ instead -- the source, pinned equal to the live
copy by test_builtin_extension_drift.py, and tracked. Filter to
git-tracked files as well, because extractions leave stale ignored
directories behind (cloudflare-ops, git, localkit after plan 52) that
would otherwise be counted on some working copies but not others.
Stable population: 608 (35 of them extension routes across
cloud-provision, remote-access and status). Also regenerates
docs/MIGRATION_INVENTORY.md, whose row is derived from this ceiling, and
points the CLAUDE.md / AGENTS.md notes at the scope actually scanned.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PR #122 review suggested converting the serverkit-status routes from
@jwt_required() + an inline is_admin check to rbac decorators. Declined:
plan 76's recorded decision (2026-08-19) is that JWT-only is the
deliberate default, because every rbac decorator authenticates through
auth_required() and therefore GRANTS API-key callers access -- an
expansion of the authenticated surface, never a mechanical sweep.
Two gaps let that suggestion look uncontroversial:
1. jwt_only_census.py scanned only app/api, so the extension's 15 bare
@jwt_required() routes were invisible to the ratchet. It now scans
app/plugins as well; ceiling 577 -> 611 for the newly visible
population (a scope widening, not a regression). Only the live copy
is counted -- builtin-extensions/ is the same code twice and the
drift guard pins them equal.
2. Neither CLAUDE.md nor AGENTS.md said the inline shape is already a
valid explicit decision (test_route_authz_static counts .is_admin via
ATTRIBUTE_GATES), or that ~91 routes across 21 files use it. Both now
do, with the plan 76 quote and a note that automated reviewers raise
this routinely.
Audited 2026-08-31 by AST over app/api + app/plugins + builtin-extensions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both are now enforced by tests that fail the build, so they belong where the
"adding a feature" checklist is rather than in a commit message nobody
re-reads. The one-boundary rule is the trap this round fell into: stacking
`@jwt_required()` outside a policy decorator looks like belt-and-braces and
is actually a regression, because it rejects API-key callers before the
policy can authorize them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Issue #96: an operator fronting the panel with Caddy and a smallstep private CA
hit a redirect loop, and there was no documented path to follow. The
architecture already tolerated this deployment -- the fix was a config file we
already ship -- but nothing said so anywhere.
Adds an INSTALLATION.md section covering both topologies (external proxy ->
ServerKit nginx, full features, HOPS=2; external proxy -> the app directly,
panel-only, HOPS=1), why the loop happens, how to count TRUSTED_PROXY_HOPS,
Caddy (with acme_ca for a private CA), Traefik, Nginx Proxy Manager and plain
nginx snippets, what a private CA does and does not require (the panel never
sees it; a remote agent's host must trust the root), and a symptom-to-cause
table for the loop, stale HSTS, the rejected websocket and wrong client IPs.
Also corrects the Docker documentation, which had drifted badly:
- README (en/es/pt/zh-CN), INSTALLATION and DEPLOYMENT all told people to run
`docker compose up -d` and reach the panel at http://localhost. It starts the
all-in-one container on :5000, and a containerised panel cannot manage its own
host -- now stated up front rather than discovered.
- DEPLOYMENT is a host-install guide that told operators to run
`docker compose build && up` to fix a panel, which would build a second panel
beside theirs, and carried a certbot cron copying certs and restarting a
container that does not exist on a host install.
- CLAUDE.md, AGENTS.md, CONTRIBUTING.md and LOCAL_DEVELOPMENT.md (x3) pointed at
a docker-compose.dev.yml that has never existed; the snippet offering to
create it referenced a frontend/Dockerfile.dev that does not exist either.
Every anchor introduced here was checked to resolve.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One anatomy diagram, the toolbar placement rule, the drawer width scale, and
the EmptyState/Modal/useConfirm/row-click conventions; primitives list gains
ListToolbar, SortChipBar, ViewMenu and the views hook.
File counts were roughly a third of reality (api 36 vs 90, services 48 vs 165,
models 15 vs 80, pages 29 vs 60), so both files understated the codebase enough
to mislead anyone sizing a change.
Two claims were wrong rather than stale. `db.create_all()` is never called —
schema is 74 Alembic migrations, and describing tables as auto-created invites a
model change that ships without one. And the Production Build section
recommended GeventWebSocket workers while the deployment note 25 lines above
warned against exactly that; the Dockerfile and systemd unit both use a plain
threaded worker.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Plan docs in docs/plans/ (git-ignored) now carry a NN_ prefix in
creation order so new plans sort last. Updated the tracked references
(test docstrings, AGENTS.md, serverkit_installer, _utilities.scss) and
dropped two stale per-file .gitignore lines already covered by the
/docs/plans rule.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a shared git hook (.githooks/pre-commit) that lints staged frontend JS/JSX
and blocks the commit on ESLint ERRORS only (warnings allowed). It exits early
for commits with no frontend JS/JSX (backend, SCSS, docs), so it never disrupts
those. Enable once per clone: `git config core.hooksPath .githooks` (or
`sh frontend/setup-hooks.sh`).
Chose a committed .githooks script over husky/lint-staged: this is a monorepo
(git root has the Python backend; only frontend/ is a node package with no root
package.json), so a dependency-free hook avoids an npm-install-time git-config
mutation and keeps backend committers unaffected. Documented in AGENTS.md.
Tested: blocks a staged file with a parse error (exit 123), passes when clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per docs/plans/FRONTEND_UX_TAILWIND_CLEANUP_PLAN.md Phase 1:
- AGENTS.md: document the SCSS-only styling standard (forbid Tailwind/CSS-in-JS),
canonical primitives/utilities, and the in-progress Tailwind removal.
- ScheduleCard.jsx: fix Rules-of-Hooks violation (early return before
useState/useEffect/useMemo); hooks now run unconditionally before the loading
branch. Clears the 10 ESLint errors.
- Add canonical shared utilities to replace scattered duplicates:
utils/formatBytes.js, utils/time.js (timeAgo + formatRelativeTime),
utils/clipboard.js + hooks/useClipboard.js. timeAgo.js now re-exports time.js.
Verified: npm run build passes; eslint reports 0 errors in touched files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce fallback audit logging and enrich audit tooling; add builtin extension discovery/install; and update default launcher ports and related docs/config.
Key changes:
- New middleware app.middleware.audit registers an after-request fallback that records authenticated mutating API requests when routes don't emit explicit audit logs.
- Audit model/action constants extended and AuditService enhanced (request context handling, redaction for sensitive settings, optional commit flag, sets g.audit_logged, emits events).
- Routes/services updated to create explicit audit entries for marketplace and plugin operations (create/update/publish/install/uninstall/enable/disable) and SSO/settings flows switched to use AuditService.
- Added tests for audit logging behavior and redaction (backend/tests/test_audit_log.py).
- Plugin service: added BUILTIN_EXTENSIONS_DIR, list_builtin_extensions and install_builtin_extension; plugin API endpoints to list/install builtin extensions.
- Contribution system extended to support layouts and route layout metadata; frontend wiring updated to surface standalone/custom layouts and extension routes.
- Launcher port defaults changed (backend -> 47927, frontend -> 41921). Updated backend .env.example, config CORS defaults, Dockerfiles and docker-compose to use SERVERKIT_BACKEND_PORT, and many docs/README files and frontend envs to reflect new ports.
- Minor frontend cleanups and new builtin extension example (serverkit-git) plus associated manifest and frontend re-export.
These changes improve observability/security for mutating APIs, enable opt-in builtin plugins, and align local dev/run ports with the launcher defaults.
Add a unified, cross-platform development launcher and update docs/styles to SCSS.
- Add dev/dev.sh: comprehensive Bash dev launcher with stable non-default ports, port resolution/cleanup, WSL-aware behavior, ngrok support, and validation tooling.
- Rewrite Windows entrypoint (dev.ps1) to require WSL and delegate to dev/dev.sh; make top-level dev.sh a thin wrapper that execs dev/dev.sh.
- Add AGENTS.md with project overview, architecture, and development instructions for assistance tools.
- Update docs (CLAUDE.md, CONTRIBUTING.md) to reflect migration from LESS to SCSS and update style guidance.
- Update .gitignore to ignore backend/.venv-wsl, frontend.env.development.local, and /.agents.
- Frontend: add new pages and page-specific SCSS files, update main.scss, components, pages, client/socket services, and vite.config.js to support the SCSS-based frontend changes.
These changes unify local development workflows (especially via WSL), improve port stability, and complete the frontend styling migration from LESS to SCSS.