Address the security-auditor findings the user selected (items 2, 3, 4):
Default admin credentials (P2): remove the fixed `Ten31` default. First
boot now generates a strong random admin password (secrets.token_urlsafe),
records it 0600 at /data/.admin-password, and surfaces it once via a new
"Show Initial Admin Password" StartOS action (CLI `show-admin-password`).
The stored password is cleared when the admin is reset (CLI reset-password)
or self-changes it (change-password endpoint).
Login hardening (P2): add a per-IP in-memory sliding-window rate limiter
(10 failures / 5 min -> 429 + Retry-After) in ratelimit.py; run a dummy
argon2 verify when the user is unknown so timing can't enumerate usernames;
keep a single generic 401 for unknown-user and wrong-password.
Hardening (P3): server process now runs unprivileged -- Dockerfile adds
uid 10001 appuser; start.sh (still root) chowns the mounted /data then
drops via `setpriv` before exec'ing uvicorn. Spreadsheet imports are
size-capped via storage.read_capped (413 past MAX_UPLOAD_SIZE) in the
schedule, capital preview, and batch paths. batch_import no longer returns
raw exception text (generic per-file messages).
Verified in the packed amd64 container: PID1 uvicorn runs as uid 10001,
/data owned 10001 with 0600 secrets; generated admin password retrievable
via CLI and logs in (200); 11th bad login -> 429; admin reset clears the
stored password. Tests: test_auth_hardening.py (4). Full suite 21 passed;
frontend tsc + StartOS bundle clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add POST /api/import/capital-accounts/batch: upload several of a fund's
eNAV workbooks at once; each file's ALLOC SI roster is auto-matched to
existing members (by fund-admin investor ID, else name/username) and their
capital statement is saved at that file's own as-of date, building
trend-lines without replacing the latest figures. Members not already in
the portal are skipped and reported per file (never created). Capital
statements only -- holdings/NAV are untouched. One bad file (wrong
password, no ALLOC SI, unreadable date) is reported per-file and does not
abort the rest.
Import page gains a "Backfill historical capital" batch section (fund
picker, multi-file .xlsx input, shared password, per-file results table).
Investor portal "Capital over time" chart is now collapsed by default and
expands per fund (first login opens clean); applies to InvestorHome and
the admin Investor View via the shared component.
Tests: backend/tests/test_capital_batch.py (2). Full suite 17 passed;
frontend tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes a linked GP entity showing an empty Assets tab when its capital is
held under the linked account's other legal names (the eNAV often splits
one LLC across names).
- New GET /api/entities/{id}/asset-balances: household-aware, returns the
linked account's (and its linked names') capital balances per fund, plus
the linked account name for a clear empty state.
- Assets tab uses it; shows "linked to X but no balances on file" instead
of a blank table when the wrong account is linked.
- GP/mgmt Overview now surfaces the total linked balance across funds with
a "View by fund" link, so assets are visible without opening the tab.
Verified: 15/15 backend tests (incl. household case); frontend tsc + vite
build clean.
Ten31 LLC (and any GP/mgmt entity) can be linked to its investor account,
so its Assets tab shows its real capital-account balance in each fund,
pulled live from the eNAV capital accounts instead of manual entry.
- entities.linked_user_id (migration c9d0e1f2a3b4) + EntityCreate/Update/
Response fields; validated to be an investor account.
- Edit-entity form gains a "Linked investor account" picker for GP/mgmt.
- Assets tab now auto-lists the linked account's balance per fund (the
earlier manual-stakes API remains but is no longer used by the UI).
Verified: 13/13 backend tests pass; alembic head c9d0e1f2a3b4; frontend
tsc + vite build clean.
Snapshot commit bringing the uncommitted phase-2 work into version control
together with four new features and the 0.2.22 version bump.
New features:
- Investor capital-over-time chart (value, paid-in, distributions per
quarter), rendered from existing capital-account history.
- Admin Investor View: read-only reconstruction of an investor's portal
(GET /api/users/{id}/investor-view), reusing the investor portal UI.
- Document upload scoped to the selected fund's own investors, with an
explicit upload-target confirmation to prevent mis-attaching.
- GP/mgmt entities gain an Assets tab listing their stakes in the funds
they manage (new entity_stakes table + /api/entities/{id}/stakes).
- Edit-entity form (change type/status/etc.), so GP entities can be
categorized correctly.
Verified: 11/11 backend tests pass; alembic upgrades to head b8c9d0e1f2a3;
frontend tsc + vite build clean; s9pk packs at 0.2.22:0 (x86_64).
Also: ignore .DS_Store and *.s9pk artifacts.
Cut version 0.2.21 (current = v_0_2_21) with the adjudicated DO-item fixes.
Also commits the previously-untracked v_0_2_* version files that
versions/index.ts imports (ROADMAP item D2), so a fresh checkout builds
without dangling imports. The missing v_0_2_2 is intentional (versions are
an unordered set).
Built and verified: ten31portal_x86_64.s9pk packs cleanly at 0.2.21:0,
arch x86_64, SDK 0.4.0.
- Import page: schedule (XLSX/CSV) and entity import with dry-run toggle
- Audit Log page: paginated, filterable by object type
- Nav items now clickable (removed stubs)
- StartOS Action: Create User with name/email/password/role inputs
- Default first-boot creds: jonathan@ten31.xyz
- Dockerfile: multi-stage build (Node frontend + Python backend)
- StartOS manifest, interfaces, backups, version graph
- start.sh: auto-generates session secret, creates first approver on boot
- Backend serves built frontend as SPA in production
- Single data volume for SQLite DB, platform backups cover it