166 lines
7.5 KiB
Markdown
166 lines
7.5 KiB
Markdown
# SHD.build architecture
|
||
|
||
This project is intentionally being built in vertical slices.
|
||
|
||
## Planned stack
|
||
|
||
- SvelteKit 2 / Svelte 5 with TypeScript
|
||
- Embedded Turso Database via `@tursodatabase/database` for durable application data
|
||
- Auth.js-compatible server authentication with Discord OAuth and credentials providers
|
||
- Argon2id password hashing, server-side sessions, CSRF protection, and role checks
|
||
- Browser `localStorage` only for anonymous build drafts
|
||
|
||
## Product boundaries
|
||
|
||
- Public: browse, sort, and view published builds and guides.
|
||
- Anonymous: create and edit local drafts.
|
||
- Authenticated: publish, rate, comment, favorite, copy, and manage owned builds.
|
||
- Editor: manage builder source data.
|
||
- Admin: manage users, roles, source data, and audit history.
|
||
|
||
Authorization must be checked on the server for every mutation. Hiding an admin link is not authorization.
|
||
|
||
Local credentials are stored as Argon2id PHC strings with a unique random salt
|
||
per password. Current work factors follow OWASP's 19 MiB / 2 iteration /
|
||
single-lane minimum profile and can be increased over time; successful login
|
||
will eventually rehash credentials whose parameters are outdated.
|
||
|
||
## Database runtime
|
||
|
||
The application uses Turso Database in-process through its native Node package.
|
||
It opens a local SQLite-compatible database file and does not use Turso Cloud,
|
||
libSQL HTTP, remote replicas, auth tokens, or synchronization.
|
||
|
||
The database defaults to `./data/shd-build.db`. Production deployments must
|
||
provide a persistent writable filesystem and can override the location with
|
||
`TDR_DATABASE_PATH`. This rules out ephemeral serverless filesystems unless a
|
||
persistent volume is attached.
|
||
|
||
## Increment plan
|
||
|
||
1. Product shell and anonymous local-draft builder.
|
||
2. Embedded Turso migrations, repositories, and seed data.
|
||
3. Credentials and Discord authentication.
|
||
4. Full equipment/stat builder driven by database definitions.
|
||
5. Publishing, discovery, ratings, comments, favorites, and copy flow.
|
||
6. Rich build guides and moderation.
|
||
7. Admin editors, validation, audit log, and data import/export.
|
||
# Administrator bootstrap
|
||
|
||
The first successfully authenticated user is promoted to `admin` if the
|
||
database does not contain an administrator. Promotion uses one conditional
|
||
database update so concurrent first sign-ins cannot both become administrators.
|
||
Once an administrator exists, later sign-ins never alter roles.
|
||
|
||
# Database backup and restore
|
||
|
||
Embedded Turso databases contain contributor-entered source data and must be
|
||
preserved across development changes.
|
||
|
||
Create a timestamped development backup:
|
||
|
||
```sh
|
||
npm run db:backup
|
||
```
|
||
|
||
Create a production backup:
|
||
|
||
```sh
|
||
npm run db:backup:production
|
||
```
|
||
|
||
The scripts write versioned JSON documents beneath `data/backups/` by default.
|
||
That directory is ignored by Git because backups contain private user data,
|
||
including password hashes.
|
||
|
||
To restore, first create an empty database and apply the schema. Then run:
|
||
|
||
```sh
|
||
npm run db:restore -- data/backups/<backup-file>.json
|
||
```
|
||
|
||
The restore operation refuses to import into any database containing records.
|
||
This guard prevents accidental merging or overwriting of contributor data.
|
||
Stop the application before backup or restore so the embedded database file is
|
||
not locked by another process.
|
||
|
||
# ISAC audit and compensating reversals
|
||
|
||
Every successful ISAC mutation appends an event to `audit_log` through the
|
||
shared server audit utility. Events identify the operator, operational category,
|
||
specific action, target entity, timestamp, and useful before/after evidence.
|
||
The audit registry is read-only and requires `audit.view`; the seeded Auditor
|
||
role grants that permission without granting mutation capabilities.
|
||
|
||
Audit history is immutable. “Revert” is implemented as a new compensating
|
||
mutation rather than deletion or modification of the original event:
|
||
|
||
1. The operator must independently hold `audit.revert`.
|
||
2. The original action must be explicitly supported and contain a usable
|
||
before-snapshot.
|
||
3. The UI requires a second, explicit confirmation.
|
||
4. The server reloads the target and compares it with the original
|
||
after-snapshot, ignoring bookkeeping timestamps. A mismatch means newer work
|
||
exists and blocks automatic compensation.
|
||
5. Referenced roles, permissions, and relationships are revalidated before
|
||
restoration. Restoring administrator authority additionally requires full
|
||
administrative access.
|
||
6. The server restores the previous state and appends an `audit.reverted` event
|
||
whose target is the original audit-event ID.
|
||
7. A successfully reverted event cannot be reverted again.
|
||
|
||
Unsupported destructive identity operations, account deletion, incomplete
|
||
snapshots, stale targets, and events whose dependencies no longer exist are
|
||
marked non-reversible or rejected. Recovery for those cases requires a
|
||
specialized workflow rather than inferred database writes.
|
||
|
||
# System metrics
|
||
|
||
The read-only `/admin/metrics` route requires `metrics.view` and collects a
|
||
point-in-time process-local health snapshot. Node and operating-system APIs
|
||
provide process CPU and memory, event-loop utilization and delay, host memory,
|
||
load average, core count, platform, and uptime. Embedded Turso pragmas provide
|
||
page allocation, page size, and free-page information; aggregate SQL queries
|
||
provide application record volumes.
|
||
|
||
The interface refreshes on demand and may poll every ten seconds while the
|
||
operator keeps auto-refresh enabled. CPU and event-loop values are deltas from
|
||
the previous server-side sample, so the first or infrequently requested sample
|
||
is directional rather than a monitoring-grade time series. Production alerting
|
||
and historical analysis should eventually use a dedicated external telemetry
|
||
system. This surface deliberately omits secrets, environment-variable values,
|
||
and full database paths.
|
||
|
||
# Weapon-owned exotic talents
|
||
|
||
Standard weapon talent pools remain globally reusable and classified by Talent
|
||
1 / Top or Talent 2 / Bottom. An exotic talent is different: one exotic weapon
|
||
owns one dedicated talent, referenced by `weapons.exotic_talent_id`.
|
||
|
||
The weapon editor creates and updates the underlying normalized `talents`,
|
||
`talent_tiers`, trigger links, effect links, description template, and Exotic
|
||
weapon scope together with the weapon. Server validation rejects an exotic
|
||
talent ID not already owned by the edited weapon and rejects any talent shared
|
||
by another weapon. Converting a weapon back to standard clears the relationship
|
||
and archives its now-unowned exotic talent. Exotic talent creation and editing
|
||
are rejected by the shared Talent Registry so ownership cannot be bypassed.
|
||
|
||
# OS Protocol progression
|
||
|
||
OS Protocols use dedicated normalized tables because their talent progression
|
||
has six calculated states (base plus five upgrades) rather than the shared
|
||
two-tier talent model. Each protocol owns one `os_protocol_primary_effects`
|
||
record and one `os_protocol_talents` record. Talent effects and reusable
|
||
triggers live in their respective join tables.
|
||
|
||
The three secondary attributes are normalized separately. One
|
||
`os_protocol_secondary_profiles` record exists for each configured quality +
|
||
attribute-type combination, with its three fixed stat/value pairs in
|
||
`os_protocol_secondary_effects`. Every matching protocol inherits that profile;
|
||
secondary values are not copied onto individual protocol records.
|
||
|
||
The unique primary value uses `base × (1 + (level − 1) / 49)` for levels 1–50, while
|
||
talent effects use `base × (1 + upgrades / 5)` for zero to five upgrades.
|
||
Server actions validate all relationships and append the full owned graph to
|
||
the existing `game_data` audit history.
|