feat: add community publishing and news syndication
Validate and deploy production / validate (push) Successful in 23s
Validate and deploy staging / validate (push) Successful in 50s
Validate and deploy production / deploy (push) Failing after 40s
Validate and deploy staging / deploy (push) Failing after 7s

This commit is contained in:
jb
2026-08-01 21:42:09 -04:00
parent b47e2588e9
commit dd84dff055
288 changed files with 83254 additions and 38351 deletions
+44 -27
View File
@@ -1,14 +1,20 @@
# Community content moderation
SHD.build applies one moderation lifecycle to public comments, profile
biographies, custom avatars, guides, and build descriptions. The shared server
service lives in `src/lib/server/community-moderation.ts`. Content save actions
must use that service before publication.
biographies, custom avatars, and Guides. The shared server service lives in
`src/lib/server/community-moderation.ts`. Content save actions must use that
service before publication.
Public database records also host one moderated discussion thread. The thread
uses the existing `comment` content type and accepts only visible comments from
authenticated Agents. Community moderators may pin one visible comment as the
record's Community Note. Pinning and unpinning append `community_note.pinned`
and `community_note.unpinned` audit events; a pinned comment remains ordinary
community text and follows the same removal rules as every other comment.
## Publication decisions
The moderation service combines local rules with the configured provider
result:
The moderation service combines local rules with the configured provider result:
- Clean content publishes without human review when automatic publication is
enabled.
@@ -18,8 +24,8 @@ result:
enabled.
- An allow term can suppress the matching local term rule. It cannot override a
provider flag or threshold.
- A user report sends visible content to Oversight. One report does not hide
the content because reports can be mistaken or abusive.
- A user report sends visible content to Oversight. One report does not hide the
content because reports can be mistaken or abusive.
The service records every automated scan in `content_moderation_scans`.
Automated scans do not write to the human ISAC audit log. Human policy changes
@@ -33,13 +39,20 @@ Comments use an explicit status:
- `pending_review`: hidden while Oversight decides
- `removed`: unavailable after author removal or an Oversight decision
Long-form content uses immutable pending revisions. Profile and guide edits that
need review go into `moderated_content_revisions`; the public row keeps its last
approved text. Approval copies the reviewed revision into the public row.
Rejection preserves the revision and review evidence without publishing it.
Long-form content uses immutable pending revisions. Profile biographies keep
text candidates in `moderated_content_revisions`. Guides use the complete
structured snapshot in `article_revisions`, including title, deck, document,
tags, media references, and Build snapshots. The public article keeps its last
approved revision while another revision waits for review. Rejection retains the
submitted revision and evidence without publishing it.
Custom avatar uploads remain active after a clean image scan. A flagged image
or fail-closed scan error creates an `avatar_reviews` item and is unavailable
News articles do not use automated content moderation. The `news.manage`
permission gates News creation, editing, publication, and deletion. Comments on
News articles remain community content and use the standard comment moderation
lifecycle.
Custom avatar uploads remain active after a clean image scan. A flagged image or
fail-closed scan error creates an `avatar_reviews` item and is unavailable
through the public avatar route until approval. An Agent report also creates a
review item, but leaves an already-published avatar visible while Oversight
decides.
@@ -69,8 +82,8 @@ applicable, and records a specific ISAC audit event with before and after
evidence. Unsaved changes in other categories remain local drafts.
Site Controls requires `site.settings.manage`. Community Oversight handles text
decisions through the existing comment and profile permissions. Avatar
Oversight handles custom images through `avatars.review`.
decisions through the existing comment and profile permissions. Avatar Oversight
handles custom images through `avatars.review`.
Moderators must record a reason when they approve, reject, remove, restore,
dismiss, or resolve a queued item. The action must append an audit event after
@@ -78,10 +91,14 @@ the content and review records change.
## User reports
Authenticated Agents can report visible content they do not own. The report
control must identify the content type and stable content ID, validate that the
content remains public, and create a pending `user_content_reports` row.
Duplicate pending reports from the same Agent for the same content are rejected.
Authenticated Agents can report visible comments, biographies, and avatars they
do not own. Registered and anonymous visitors can report visible Guides. A Guide
report records the exact published `article_revision_id`; a decision cannot hide
a newer revision. Anonymous reports use a random HTTP-only browser token whose
domain-separated SHA-256 hash supplies duplicate suppression. The server applies
rate limits and requests Turnstile verification after repeated anonymous
activity. SHD.build does not use device fingerprinting or retain a raw IP
address as the reporter identity.
A reviewer either dismisses the report and keeps the content or resolves the
report through a moderation action. Report details must not copy authentication
@@ -89,8 +106,7 @@ secrets or unnecessary personal information into the audit log.
## Adding a public UGC field
Guide authoring, build descriptions, and later public text fields must complete
this checklist:
Guide authoring and later public text fields must complete this checklist:
1. Normalize and validate the submitted text before scanning.
2. Call `moderateCommunityText` with a supported content type.
@@ -98,14 +114,15 @@ this checklist:
4. Publish only an `approved` result.
5. Store held long-form text as an immutable revision so the last approved
version remains public.
6. Call `recordCommunityModerationScan` with the stable content ID, author, hash,
policy version, provider evidence, and local-rule matches.
6. Call `recordCommunityModerationScan` with the stable content ID, exact
article revision, author, hash, policy version, provider evidence, and
local-rule matches.
7. Add the content type to the Community Oversight queue and permission checks.
8. Add the shared report control to the public rendering.
9. Delay follower, owner, or reply notifications until the content becomes
public.
10. Add approval, rejection, report dismissal, author notification, and audit
tests.
tests.
11. Confirm every public query selects approved content explicitly.
Do not add a route-specific word filter, provider client, threshold table, or
@@ -127,9 +144,9 @@ option, update the legal copy before sending user content under that setting.
Threshold changes increment the policy version. Stored scans retain their model
and policy version so reviewers can interpret older decisions. Test threshold
changes against representative Division terminology before activation,
including weapon names, damage descriptions, kills, status effects, and build
strategy language.
changes against representative Division terminology before activation, including
weapon names, damage descriptions, kills, status effects, and build strategy
language.
Database migrations that add moderation states or revision storage follow the
repository database-preservation procedure: stop this project's development
+102 -51
View File
@@ -2,8 +2,8 @@
SHD.build runs in a Node 24 container through the official SvelteKit Node
adapter. The image includes application code, migration and notification
scripts, and native runtime dependencies. Environment files, databases,
backups, and user uploads remain outside the image.
scripts, and native runtime dependencies. Environment files, databases, backups,
and user uploads remain outside the image.
## Persistent data
@@ -16,8 +16,8 @@ mounted at `/app/data`. It contains:
- uploaded game-data icons under `game-icons/`.
Keep this volume during routine deployments. `docker compose down` without
`--volumes` preserves it. Before a manual backup or restore, stop the `app`
and `scheduler` services because both access the embedded database.
`--volumes` preserves it. Before a manual backup or restore, stop the `app` and
`scheduler` services because both access the embedded database.
## Staging environment
@@ -29,6 +29,21 @@ Staging defaults to:
- Passkey relying-party ID: `shd.build`
- Adapter canonical origin: `https://stage.shd.build`
Guide ratings and reports use Cloudflare Turnstile after repeated anonymous
activity. Configure both values in each encrypted environment:
```text
TURNSTILE_SITE_KEY=<public site key>
TURNSTILE_SECRET_KEY=<server verification secret>
```
Register the exact staging and production hostnames in the Turnstile widget. The
browser receives only `TURNSTILE_SITE_KEY`. The server sends a challenge token
and the request address to Cloudflare for verification; it never exposes
`TURNSTILE_SECRET_KEY` or stores the raw address as visitor identity. Without a
valid secret, a visitor who reaches the challenge threshold cannot submit more
anonymous ratings or Guide reports.
Edit its encrypted configuration locally:
```sh
@@ -38,12 +53,11 @@ npm run env:encrypt:staging
```
Add `https://stage.shd.build/auth/discord/callback` to the Discord application's
allowed OAuth redirect URIs. Point the staging DNS record at the deployment
host and configure the host's TLS reverse proxy to forward
`stage.shd.build` to port `5569` on the staging host. Docker publishes this
port on the host interfaces so a containerized reverse proxy or tunnel can
reach it; firewall the port to trusted LAN/tunnel sources when the host is
reachable from other networks.
allowed OAuth redirect URIs. Point the staging DNS record at the deployment host
and configure the host's TLS reverse proxy to forward `stage.shd.build` to port
`5569` on the staging host. Docker publishes this port on the host interfaces so
a containerized reverse proxy or tunnel can reach it; firewall the port to
trusted LAN/tunnel sources when the host is reachable from other networks.
The trusted staging runner decrypts `.env.staging` into a mode-`0600` runtime
file. Gitea Actions holds the decryption key; Git and the container image do
@@ -55,10 +69,10 @@ that signs in instead of writing generated credentials into deployment logs.
## Gitea Actions runner
Gitea Actions requires an `act_runner`. Register a trusted runner on the
staging host from the repository's **Settings → Actions → Runners** page. The
host needs Node/npm, Docker Engine, and Docker Compose v2. The runner account
must be able to use Docker and write to the deployment directory.
Gitea Actions requires an `act_runner`. Register a trusted runner on the staging
host from the repository's **Settings → Actions → Runners** page. The host needs
Node/npm, Docker Engine, and Docker Compose v2. The runner account must be able
to use Docker and write to the deployment directory.
Use runner labels that provide an isolated validation job and a host deployment
job:
@@ -94,9 +108,9 @@ In `https://git.jb.codes/jb/shd.build/settings/actions`:
`.gitea/workflows/deploy-staging.yml` runs for pushes to `main` and manual
dispatches. The trusted staging runner builds each commit-tagged image locally,
so the workflow needs no registry credentials. Gitea assigns
`gitea.run_number` to `SHD_BUILD_NUMBER`; each new workflow run receives the
next integer, while retries of the same run retain their number.
so the workflow needs no registry credentials. Gitea assigns `gitea.run_number`
to `SHD_BUILD_NUMBER`; each new workflow run receives the next integer, while
retries of the same run retain their number.
## Deployment lifecycle
@@ -114,12 +128,28 @@ The workflow:
The outage covers backup, migration, and container startup. Migrations may also
remove records that use retired data formats when a release explicitly ends
compatibility. The deployment backup remains the recovery source for those
records; the application does not repair or reinterpret them at runtime.
The scheduler calls a bearer-authenticated endpoint on the application service
once per minute. Scheduled message delivery therefore uses the web process's
existing embedded-database connection instead of opening the locked database
from a second process. The endpoint runs automated system messages, Oversight
consolidation, and email digest checks and is not published through a host port.
records; the application does not repair or reinterpret them at runtime. The
scheduler calls a bearer-authenticated endpoint on the application service once
per minute. Scheduled message delivery therefore uses the web process's existing
embedded-database connection instead of opening the locked database from a
second process. The endpoint runs automated system messages, Oversight
consolidation, email digest checks, and the Ubisoft News importer. Docker does
not publish the endpoint through a host port.
When enabled in production, the Ubisoft importer keeps its own persisted due
time, so the one-minute tick starts a feed synchronization no more than once per
hour after a successful run. When a synchronization fails, the importer stores
the last error and consecutive failure count, then retries after 15 minutes with
exponential backoff capped at six hours. Source items that disappear from the
feed remain published; the importer performs no automatic removal. The scheduler
runs notification delivery before it starts the bounded Ubisoft request, so a
slow provider does not postpone notifications for the current tick.
`deploy/compose.production.yml` declares `UBISOFT_NEWS_IMPORT_ENABLED=true`,
while the staging Compose file declares `false`. Development does not set the
flag and remains disabled because the importer requires the exact value `true`.
The application process evaluates the flag when it handles
`/api/internal/scheduler`.
## Prelaunch operation
@@ -131,8 +161,8 @@ for a database that does not contain the row:
- News, Database, Builds, Guides, and Builder: enabled
The migration uses an insert-if-missing seed. Later deployments preserve the
operator's settings, and configuration changes take effect without a restart.
A full environment bootstrap copies the source row. Canonical ISAC game-data
operator's settings, and configuration changes take effect without a restart. A
full environment bootstrap copies the source row. Canonical ISAC game-data
imports leave the target environment's launch settings unchanged.
Use this operator flow after the first deployment:
@@ -149,13 +179,13 @@ Use this operator flow after the first deployment:
session before public release. Sign in and verify the same disabled routes
remain unavailable.
Keep Coming Soon enabled while preparing production. Section controls can be
set in advance because the launch gate takes precedence for signed-out traffic.
Set Account Creation to Disabled if prelaunch access must remain limited to
existing identities. Switch the Launch Gate to Public Launch after verification.
Disabled sections remain absent from navigation and unavailable by direct URL
for every user, including authenticated agents. Authentication bypasses only
the Coming Soon gate.
Keep Coming Soon enabled while preparing production. Section controls can be set
in advance because the launch gate takes precedence for signed-out traffic. Set
Account Creation to Disabled if prelaunch access must remain limited to existing
identities. Switch the Launch Gate to Public Launch after verification. Disabled
sections remain absent from navigation and unavailable by direct URL for every
user, including authenticated agents. Authentication bypasses only the Coming
Soon gate.
Turn Coming Soon on to restore the promotional gate. Site-control changes append
`system_configuration / site_access.updated` audit events, including the
@@ -177,9 +207,9 @@ open while its complete transfer bundle is created:
npm run stage:bootstrap:export
```
The command writes a timestamped, ignored JSON bundle under `data/backups/`.
The bundle includes user records and password hashes. Transfer it over an
encrypted channel, keep it out of Git, then copy it to the staging host and run:
The command writes a timestamped, ignored JSON bundle under `data/backups/`. The
bundle includes user records and password hashes. Transfer it over an encrypted
channel, keep it out of Git, then copy it to the staging host and run:
```sh
/opt/shd-build-stage/bootstrap-staging.sh \
@@ -187,11 +217,10 @@ encrypted channel, keep it out of Git, then copy it to the staging host and run:
```
The bootstrap flow stops the app and scheduler, creates a safety backup of the
target, verifies that staging is unused, imports the entire development
database and uploads, and restarts both services. Delete the transfer copy from
the staging host after verifying the imported record counts and critical
entities. The copy under the local ignored backup directory remains the
recovery source.
target, verifies that staging is unused, imports the entire development database
and uploads, and restarts both services. Delete the transfer copy from the
staging host after verifying the imported record counts and critical entities.
The copy under the local ignored backup directory remains the recovery source.
Because this is a full initial clone, development session tokens and account
data also enter staging. Staging and development remain independent from that
@@ -293,9 +322,31 @@ Copy the resulting ignored JSON bundle to the deployment host over SSH, then:
/secure/path/shd-production-bootstrap-YYYY-MM-DDTHH-MM-SSZ.json
```
The importer verifies the bundle target, creates a production safety backup,
and refuses to replace a production database containing users, builds, audit
events, or proposals. Remove the remote transfer copy after verification.
The importer verifies the bundle target, creates a production safety backup, and
refuses to replace a production database containing users, builds, audit events,
or proposals. Remove the remote transfer copy after verification.
### Promote one News article
Export a locally authored News article by ID or slug while the development
server is stopped:
```sh
npm run article:export -- <article-id-or-slug>
```
The ignored bundle contains the article draft, immutable revisions, search row,
managed-image metadata, and image bytes. It excludes comments, reports, user
records, Builds, and ISAC data.
After deploying the matching application schema, stop the production app and
scheduler and mount the transferred bundle into a one-off application container.
Run `scripts/article-transfer.mjs import` with `ARTICLE_IMPORT_ENABLED=true` and
`--confirm-article-import`. The importer creates a production database backup
before opening the database, resolves the article owner by stable ID or
username, rejects ID and slug conflicts, verifies image checksums, and appends
`editorial_content / news.promoted`. Restart and health-check both services,
then remove the transferred bundle from the host.
### Pull production ISAC data into development
@@ -312,10 +363,10 @@ notifications, settings, and audit history untouched.
### Pull production ISAC data into staging
Manually run **Replace staging ISAC data from production** in Gitea Actions.
The workflow exports a consistent production snapshot, restarts production,
then stops staging and imports it. The staging importer creates full and
ISAC-only safety backups before replacement. Deployments do not run this flow.
Manually run **Replace staging ISAC data from production** in Gitea Actions. The
workflow exports a consistent production snapshot, restarts production, then
stops staging and imports it. The staging importer creates full and ISAC-only
safety backups before replacement. Deployments do not run this flow.
### Promote managed images without replacing other data
@@ -333,8 +384,8 @@ internal scheduler. `REMOVE_BG_CREDIT_CHECK_HOURS` controls the account lookup
interval and defaults to six hours. Low-capacity alerts require both remaining
pools to cross their thresholds: `REMOVE_BG_LOW_CREDIT_THRESHOLD` defaults to
five paid credits, and `REMOVE_BG_LOW_FREE_CALL_THRESHOLD` defaults to ten free
calls. Alerts are mandatory ISAC notifications for active Administrators and
are deduplicated for each warning or critical episode.
calls. Alerts are mandatory ISAC notifications for active Administrators and are
deduplicated for each warning or critical episode.
```sh
npm run images:export
@@ -368,8 +419,8 @@ adds new stable IDs, skips identical assets, and blocks conflicting records.
After reviewing an intentional metadata or file replacement, add
`--update-existing`. Imported changes append `image.imported` or
`image.import_updated` audit events. Remove the transferred bundle after
verifying both environments because it may contain unpublished visual assets
and creator identifiers.
verifying both environments because it may contain unpublished visual assets and
creator identifiers.
The `.env.staging` and `.env.production` npm import commands are intended for
maintenance performed directly in an environment where those database and
+942 -858
View File
File diff suppressed because it is too large Load Diff
+289 -207
View File
@@ -5,24 +5,28 @@ The team builds the project in vertical slices.
## Application 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
- 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.
- Authenticated: publish, rate, comment, favorite, copy, and manage owned
builds.
- Editor: manage builder source data.
- Admin: manage users, roles, source data, and audit history.
Server code checks authorization for each mutation. UI visibility has no effect
on server authorization.
Weapon and gear range calculations must preserve decimal endpoints
and follow the tested progression rules in
Weapon and gear range calculations must preserve decimal endpoints and follow
the tested progression rules in
[`STAT_LEVEL_SCALING.md`](STAT_LEVEL_SCALING.md). Leveling is linear rather than
compounded. The presentation layer truncates game-matching integer values.
@@ -49,9 +53,9 @@ Builder. Schema migration creates the row with Coming Soon enabled, account
creation enabled, and all five sections enabled. The application caches the row
for five seconds and clears the process cache after an operator saves changes.
Agents with `site.settings.manage` edit the row from ISAC → System
Configuration → Site Controls. The save action requires an explicit value for
all seven controls and a confirmation token. A successful update records
Agents with `site.settings.manage` edit the row from ISAC → System Configuration
→ Site Controls. The save action requires an explicit value for all seven
controls and a confirmation token. A successful update records
`system_configuration / site_access.updated` with the operator, target row, and
complete before/after snapshots.
@@ -84,22 +88,102 @@ closed.
## Community content moderation
Comments, profile biographies, custom avatars, guides, and build descriptions
share the moderation lifecycle documented in
[`CONTENT_MODERATION.md`](CONTENT_MODERATION.md). Local rules and the configured
provider classify each submission before publication. Clean submissions can
publish without human work. Flagged submissions and fail-closed provider errors
enter the appropriate Oversight queue.
Comments, profile biographies, custom avatars, and Guides share the moderation
lifecycle documented in [`CONTENT_MODERATION.md`](CONTENT_MODERATION.md). Local
rules and the configured provider classify each submission before publication.
Clean submissions can publish without human work. Flagged submissions and
fail-closed provider errors enter the appropriate Oversight queue.
Agents may report visible content they do not own. Reports enter Oversight
without hiding the content. Human decisions require a reason and append an
audit event. Long-form edits that need review use immutable pending revisions
so the last approved biography, guide, or description remains public.
without hiding the content. Human decisions require a reason and append an audit
event. Long-form edits that need review use immutable pending revisions so the
last approved biography or Guide remains public.
ISAC Site Controls owns provider behavior, content scopes, thresholds, link and
PII checks, and allowed or banned terms. API credentials remain in server
environment configuration.
## Guides and News articles
`articles` stores the stable identity, kind, owner, immutable slug, visibility,
and published or pending revision pointers for Guides and News. Authors edit one
mutable `article_drafts` row with optimistic version checks. Publication creates
an immutable `article_revisions` snapshot. Public readers and search load only
the revision named by `articles.published_revision_id`.
Guides require OpenAI moderation before an unlisted or public revision can
publish. The moderation request includes the title, deck, document text, image
alternative text and captions, cover image, and inline images. A failed provider
request stays in Guide Oversight. Clean revisions may publish automatically;
flagged revisions preserve the previous public revision while Oversight decides.
News bypasses this lifecycle because `news.manage` is the editorial gate. News
comments still use community moderation.
### Ubisoft News syndication
The Ubisoft News importer reads the English-language Division Resurgence feed
from Ubisoft Nimbus. It stores each Nimbus item ID as the stable external ID and
publishes only the source title, excerpt, and canonical Ubisoft link. It does
not copy the source article body or images. Imported articles use Ubisoft's
original publication timestamp rather than the import time.
`external_news_sources` maps each Nimbus ID to one SHD.build article. A SHA-256
content hash covers the normalized title, excerpt, canonical URL, and source
publication timestamp. An unchanged hash updates `last_seen_at` without creating
a revision. A changed hash creates and publishes an immutable revision. The
importer does not delete or unpublish an article when Nimbus omits it from a
later feed, and it does not restore an article that an editor deleted. The
Newsroom treats source-linked articles as read-only so local edits cannot retain
Ubisoft attribution. The importer defers future-dated items until their source
publication time.
The seeded, non-public `Ubisoft Official News` service identity owns imported
articles and acts on their audit events. A first import records
`editorial_content / news.imported`; a later content sync records
`editorial_content / news.import_synced`. Audit snapshots identify the provider,
Nimbus ID, canonical URL, source timestamp, and resulting revision.
The existing bearer-authenticated scheduler endpoint invokes the importer. A
successful run sets the next check one hour later. The importer stores the last
attempt, last success, next check, consecutive failure count, and bounded last
error in provider state. Failures retry after 15 minutes with exponential
backoff capped at six hours. A successful run clears the error and failure
count.
The article document is a versioned, strictly validated Tiptap JSON envelope.
The application stores no author-supplied HTML and renders accepted nodes with
Svelte components. `article_revision_build_embeds` supports several Builds per
Guide and several Guides per Build. Each relation records a bounded publication
snapshot. Authors may embed any Build they own; another Agent's Build must be
public and becomes unavailable in the Guide if its source stops being public.
Guide taxonomy combines fixed activity and difficulty terms with active
Specializations, Focuses, and weapon categories. `article_search` stores the
published title, deck, body text, and tags. Weighted token scoring prioritizes
title, deck, tags, then body because the embedded Turso engine does not ship the
FTS5 module. Search rows exist only for current public revisions.
Registered and anonymous visitors may rate a Guide from 1 through 5. Anonymous
ratings and Guide reports use a random HTTP-only browser token; the database
stores only its domain-separated hash. Repeated anonymous activity requires a
Turnstile result. Guide reports reference the exact published revision so a
report against an older revision cannot hide newer work.
The Builder stores loadout data only. It has no summary or Guide body. An owned
Build page links to `/guides/new?build=<id>`, where the server verifies
ownership and initializes a removable Build block in a new private Guide.
## Public data corrections and discussion
Every public database card links to its canonical ISAC record with an exact
`item` ID and offers a moderated Community Discussion. Authenticated Agents can
post notes and replies; Community Oversight may pin one visible comment as the
record's Community Note. A public correction opens the same ISAC editor used by
data operators. Contributor-mode submissions create a proposal rather than
changing canonical data. Contributors can attach an optional bounded rationale;
the rationale is retained with the proposal and visible to Data Oversight, but
does not become part of the canonical action replay.
## Increment plan
1. Product shell and anonymous local-draft builder.
@@ -109,10 +193,11 @@ environment configuration.
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 authentication service promotes the first authenticated user to `admin` if the
database does not contain an administrator. Promotion uses one conditional
The authentication service promotes the first authenticated user 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.
@@ -134,8 +219,8 @@ npm run db:backup:production
```
The scripts write versioned JSON documents beneath `data/backups/` by default.
Git ignores that directory because backups contain private user data,
including password hashes.
Git ignores that directory because backups contain private user data, including
password hashes.
To restore, first create an empty database and apply the schema. Then run:
@@ -144,18 +229,18 @@ 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.
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.
## Development database maintenance
Treat the development database as persistent data. Stop this project's
development server before backup, export, restore, migration, or other work
requiring exclusive access. Create and verify a JSON backup, perform the
prepared operation, verify schema state and critical record counts, then
restart and health-check the application. Prefer additive, repeatable
migrations and retain the verified backup if maintenance fails.
prepared operation, verify schema state and critical record counts, then restart
and health-check the application. Prefer additive, repeatable migrations and
retain the verified backup if maintenance fails.
Production and staging remain shared persistent environments and retain the
deployment, backup, synchronization, and maintenance safeguards documented in
@@ -165,22 +250,22 @@ deployment, backup, synchronization, and maintenance safeguards documented in
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.
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. The server implements “Revert” as a new compensating
mutation rather than deletion or modification of the original event:
1. The operator must hold `audit.revert` in addition to any other access.
2. The reversion registry must support the original action and the event must contain a usable
before-snapshot.
2. The reversion registry must support the original action and the event must
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 indicates newer
work and blocks automatic compensation.
5. The server revalidates referenced roles, permissions, and relationships before
restoration. Restoring administrator authority also requires full
5. The server revalidates referenced roles, permissions, and relationships
before restoration. Restoring administrator authority also 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.
@@ -189,47 +274,48 @@ mutation rather than deletion or modification of the original event:
# Moderated game-data contributions
ISAC data access uses `data.view`, `data.propose`, `data.manage`, and
`data.approve`. Data Viewers can inspect registries. Data Contributors can submit
the same editor forms, but the server serializes each request into
`data.approve`. Data Viewers can inspect registries. Data Contributors can
submit the same editor forms, but the server serializes each request into
`data_change_proposals` instead of mutating canonical tables. Data Oversight
reviews the queue and must confirm approval or rejection.
Approval replays the stored form through its original SvelteKit server action.
This preserves canonical validation, relational updates, icon processing, and the
normal game-data audit event. Proposal creation and review decisions also produce
separate `data_moderation` audit events. Failed canonical validation marks a
proposal as failed without changing live data. Pending counts appear alongside
each data registry in ISAC so collaborators can see concurrent suggestions.
This preserves canonical validation, relational updates, icon processing, and
the normal game-data audit event. Proposal creation and review decisions also
produce separate `data_moderation` audit events. Failed canonical validation
marks a proposal as failed without changing live data. Pending counts appear
alongside each data registry in ISAC so collaborators can see concurrent
suggestions.
The protected seeded roles are Data Viewer, Data Contributor, Data Editor, and
Data Oversight. Data Editor retains direct-write access for trusted operators;
Data Oversight combines direct-write access with queue review. New ISAC data
modules must use the shared moderation interception path and audit both the final
mutation and proposal lifecycle.
modules must use the shared moderation interception path and audit both the
final mutation and proposal lifecycle.
Mechanic programs remain part of their owning source record. Talent, OS
Protocol, Skill Chip, and Specialization proposals carry the complete typed
program with the rest of the source payload. Oversight reviews and approves
that program through the source module's canonical validation path. There is
no standalone Trigger proposal module or pending Trigger dependency.
program with the rest of the source payload. Oversight reviews and approves that
program through the source module's canonical validation path. There is no
standalone Trigger proposal module or pending Trigger dependency.
Each authenticated, active registered agent receives `isac.access`,
`data.view`, and `data.propose` as baseline permissions in the authorization
layer. The authorization layer grants this baseline without a removable role
assignment, so existing users and each account-creation method receive the same
access. Registered agents can inspect live source data and submit
moderated proposals, but cannot mutate canonical records, review the
queue, or access unrelated ISAC operations without additional permissions.
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.
Each authenticated, active registered agent receives `isac.access`, `data.view`,
and `data.propose` as baseline permissions in the authorization layer. The
authorization layer grants this baseline without a removable role assignment, so
existing users and each account-creation method receive the same access.
Registered agents can inspect live source data and submit moderated proposals,
but cannot mutate canonical records, review the queue, or access unrelated ISAC
operations without additional permissions. 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
ISAC uses `/isac` as its canonical route namespace. The server
redirects legacy `/admin` URLs to their matching `/isac` location so old
notifications and external bookmarks remain usable.
ISAC uses `/isac` as its canonical route namespace. The server redirects legacy
`/admin` URLs to their matching `/isac` location so old notifications and
external bookmarks remain usable.
The read-only `/isac/metrics` route requires `metrics.view` and collects a
point-in-time process-local health snapshot. Node and operating-system APIs
@@ -241,10 +327,10 @@ 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 sample and samples separated by
long gaps provide direction rather than a monitoring-grade time series. Production alerting
and historical analysis should use a dedicated external telemetry
system. This surface omits secrets, environment-variable values,
and full database paths.
long gaps provide direction rather than a monitoring-grade time series.
Production alerting and historical analysis should use a dedicated external
telemetry system. This surface omits secrets, environment-variable values, and
full database paths.
The same surface reports remove.bg paid credits and free API calls as separate
capacity pools. The scheduler reconciles the account balance no more often than
@@ -252,8 +338,8 @@ the configured interval, while successful image processing applies the
provider's `X-Credits-Charged` value to the persisted estimate. Warning and
critical transitions create one alert episode with a unique delivery for each
active Administrator. A later critical transition may alert again; repeated
scheduler runs within the same episode do not. Provider errors preserve the
last known balance and never expose the API key or raw response body.
scheduler runs within the same episode do not. Provider errors preserve the last
known balance and never expose the API key or raw response body.
# Weapon category bonuses
@@ -266,8 +352,8 @@ Weapon records do not own copies of these rows. Both builder catalog loaders
attach the matching category profile when they hydrate a weapon. Exotic weapons
retain an underlying category and inherit the same profile as a standard weapon
of that type. The combat summary preserves the existing equipped-weapon
semantics: each equipped weapon contributes its inherited profile to the
Agent's accumulated Attributes.
semantics: each equipped weapon contributes its inherited profile to the Agent's
accumulated Attributes.
ISAC exposes one category-profile editor above the weapon registry. Operators
may change the three positive values, while the Attribute identities and order
@@ -286,14 +372,14 @@ An exotic weapon references one active exotic talent through
`weapons.exotic_talent_id`. The weapon editor lists registered exotic talents
and identifies talents assigned to another weapon. Server validation rejects
standard talents, inactive talents, and talents already assigned elsewhere.
Changing or clearing a weapon relationship leaves the previous talent active
and available for another assignment.
Changing or clearing a weapon relationship leaves the previous talent active and
available for another assignment.
Tier I and its modeled mechanic are required when a contributor saves an
exotic talent. Tier II may remain undocumented. An undocumented Tier II has no
Tier I and its modeled mechanic are required when a contributor saves an exotic
talent. Tier II may remain undocumented. An undocumented Tier II has no
`talent_tiers` row or conditional-effect value variant. A later Talent Registry
edit adds the Tier II projection and mechanic values without changing the
weapon reference.
edit adds the Tier II projection and mechanic values without changing the weapon
reference.
# OS Protocol progression
@@ -309,22 +395,22 @@ 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.
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.
### Weapon Mods and tiered weapon slots
`weapon_mod_slots` records each weapon's available slot vocabulary per tier.
The editor captures one shared set for T1/T2 and the single shared slot omitted
at T0, then expands that input into the normalized per-tier rows. Operators may
`weapon_mod_slots` records each weapon's available slot vocabulary per tier. The
editor captures one shared set for T1/T2 and the single shared slot omitted at
T0, then expands that input into the normalized per-tier rows. Operators may
save partial rows when the in-game configuration is unavailable. ISAC derives
`CONFIG REQUIRED` until every available tier contains four unique slots at T0
or five at T1/T2. Regular Weapon Mods declare a single slot type and one or more
compatible base weapon categories through
`weapon_mod_compatible_categories`; eligibility requires both a category match
and the slot to exist on the equipped weapon at its selected tier.
`CONFIG REQUIRED` until every available tier contains four unique slots at T0 or
five at T1/T2. Regular Weapon Mods declare a single slot type and one or more
compatible base weapon categories through `weapon_mod_compatible_categories`;
eligibility requires both a category match and the slot to exist on the equipped
weapon at its selected tier.
Exotic Weapon Mods instead reference one `exotic_weapon_id`. They do not carry
regular category compatibility. The builder treats them as permanent equipment
@@ -336,62 +422,60 @@ interpolation.
All regular Weapon Mods permit Firepower, Toughness, or Engineering as a
builder-time primary choice. `weapon_mod_quality_values` stores the shared
level-1 and level-20 primary roll endpoints for each quality; primary values
use linear interpolation from levels 1–20.
level-1 and level-20 primary roll endpoints for each quality; primary values use
linear interpolation from levels 1–20.
`weapon_mod_secondary_options` is the shared pool from which a build selects
two distinct secondary attributes. Every option owns a minimum and maximum at
each quality through `weapon_mod_secondary_quality_values`; secondary
availability is not tied to a numbered slot in canonical data. Mod Effects are
triggerless, hold one or two attribute references, and store fixed per-quality
values in `weapon_mod_effect_quality_values`.
`weapon_mod_secondary_options` is the shared pool from which a build selects two
distinct secondary attributes. Every option owns a minimum and maximum at each
quality through `weapon_mod_secondary_quality_values`; secondary availability is
not tied to a numbered slot in canonical data. Mod Effects are triggerless, hold
one or two attribute references, and store fixed per-quality values in
`weapon_mod_effect_quality_values`.
### Gear Mods and tier slot capacity
Gear Mod definitions are standard-quality records only; there is no exotic
Gear Mod branch. Every definition supplies primary roll ranges for Standard,
Gear Mod definitions are standard-quality records only; there is no exotic Gear
Mod branch. Every definition supplies primary roll ranges for Standard,
Specialized, Superior, and High-end quality at Levels 1 and 20. Values between
those endpoints use linear interpolation. A build instance selects one primary attribute
from Firepower, Toughness, Engineering, Health, or Armor.
those endpoints use linear interpolation. A build instance selects one primary
attribute from Firepower, Toughness, Engineering, Health, or Armor.
The two secondary slots draw distinct attributes from one shared option pool.
Each available secondary attribute stores its own minimum and maximum for all
four qualities. Gear tier determines capacity without per-item configuration:
T0 gear has one Gear Mod slot; T1 and T2 gear have two.
four qualities. Gear tier determines capacity without per-item configuration: T0
gear has one Gear Mod slot; T1 and T2 gear have two.
The `gear_mods`, `gear_mod_quality_values`, `gear_mod_secondary_options`, and
`gear_mod_secondary_quality_values` tables store canonical data.
Successful mutations use `gear_mod.created`, `gear_mod.updated`,
`gear_mod.archived`, and `gear_mod.unarchived` audit actions. Contributor
submissions use the `gearmods` proposal module and remain non-canonical until
approved by Oversight.
`gear_mod_secondary_quality_values` tables store canonical data. Successful
mutations use `gear_mod.created`, `gear_mod.updated`, `gear_mod.archived`, and
`gear_mod.unarchived` audit actions. Contributor submissions use the `gearmods`
proposal module and remain non-canonical until approved by Oversight.
# Skill Chip quality scaling
Skill Chip sets support Superior and High-end chip instances. Each set owns one
2-piece bonus, distinct primary and secondary stat-option pools, and one
3-piece Talent with a typed conditional rule. Primary and secondary options
reference active Attributes that define Skill Chip minimum and maximum roll
values. A configured chip rolls one primary and two distinct secondary stats
from those pools.
2-piece bonus, distinct primary and secondary stat-option pools, and one 3-piece
Talent with a typed conditional rule. Primary and secondary options reference
active Attributes that define Skill Chip minimum and maximum roll values. A
configured chip rolls one primary and two distinct secondary stats from those
pools.
Each active Skill Chip definition belongs to one active specialization ability
through `skill_chip_sets.specialization_ability_id`. An ability has capacity for
three active definitions. The save and restore actions enforce that capacity,
and the schema requires the foreign key for each row. A specialization focus is
builder-ready only when each of its three
assigned abilities has all three active Skill Chip definitions. Skill Chip
mutations refresh readiness for focuses that reference both the previous and
resulting ability.
builder-ready only when each of its three assigned abilities has all three
active Skill Chip definitions. Skill Chip mutations refresh readiness for
focuses that reference both the previous and resulting ability.
Both set-bonus progression systems use the number of equipped High-end chips as
the interpolation step. For `h` High-end chips, where `h` is zero through
three, a configured minimum and maximum resolve as
the interpolation step. For `h` High-end chips, where `h` is zero through three,
a configured minimum and maximum resolve as
`minimum + (maximum − minimum) × h / 3`. Zero High-end chips therefore produce
the minimum, while three produce the maximum. The editor stores endpoints and
previews all four states, preventing duplicated tier data from diverging.
The `game_data` audit stream captures Skill Chip mutations, owned relationships,
and safe icon metadata.
previews all four states, preventing duplicated tier data from diverging. The
`game_data` audit stream captures Skill Chip mutations, owned relationships, and
safe icon metadata.
# Mechanic programs and loadout state
@@ -413,8 +497,8 @@ Each source supplies a fixed value-variant contract:
- Standard Talents require Tier I and Tier II values. Exotic Talents require
Tier I and may omit Tier II until contributors document its game values.
Builder calculations use Tier I while an equipped exotic Talent has no Tier
II mechanic variant.
Builder calculations use Tier I while an equipped exotic Talent has no Tier II
mechanic variant.
- OS Protocol Talents store a base value. An outcome may use an expression that
reads `equipment.protocolTalentUpgrades`, from zero through five, so selected
values can scale to twice the base at upgrade five.
@@ -457,24 +541,23 @@ separate event from changes to the Agent's current Extra Health state.
The optional Action Family catalog provides authoring recipes for common
mechanics. Applying a recipe copies its typed scenario inputs, state pools, and
clauses into the source-owned program. Those branches become editable
authoritative data; the recipe key remains provenance, not a runtime
dependency. Adding another recipe appends branches. Replacing a program with a
recipe requires confirmation because replacement discards the current branch
graph.
authoritative data; the recipe key remains provenance, not a runtime dependency.
Adding another recipe appends branches. Replacing a program with a recipe
requires confirmation because replacement discards the current branch graph.
A Talent may save an empty `captured_only` program while an author records its
name, category, image, and source description. The registry marks that Talent
`CONFIG REQUIRED`, and calculations exclude it. Changing readiness to `ready`
or `requires_assumptions` restores strict branch, numbered-outcome, and
`CONFIG REQUIRED`, and calculations exclude it. Changing readiness to `ready` or
`requires_assumptions` restores strict branch, numbered-outcome, and
description-placeholder validation.
The program graph uses three child tables:
- `conditional_effect_state_pools` defines agent- or target-scoped flags,
counts, stacks, durations, scalar values, marks, and resources. Each pool
owns its bounds, expiry policy, refresh policy, and best-case value.
- `conditional_effect_clauses` listens for one event from the registry. A
clause owns a typed predicate, reducer, optional cooldown, optional weight
counts, stacks, durations, scalar values, marks, and resources. Each pool owns
its bounds, expiry policy, refresh policy, and best-case value.
- `conditional_effect_clauses` listens for one event from the registry. A clause
owns a typed predicate, reducer, optional cooldown, optional weight
expression, state mutations, and optional recipe provenance. Reducers convert
event streams into counts, distinct or consecutive matches, sliding windows,
durations, intervals, or derived values. Mutations can set, add, consume,
@@ -486,17 +569,17 @@ The program graph uses three child tables:
value basis, optional resource or Attribute, combat timing, optional state
band, and phase: `on_event`, `after_event`, `on_enter`, `on_exit`, or
`while_active`. One typed value or expression exists for every variant
required by the owning source. The common value models are static values,
live Attribute values, percentage-normalized Attribute values, event-field values, reducer output,
source-defined scenario values, and OS Protocol upgrade scaling. Restoration
outcomes may deliver their total immediately or continuously over an explicit
duration.
required by the owning source. The common value models are static values, live
Attribute values, percentage-normalized Attribute values, event-field values,
reducer output, source-defined scenario values, and OS Protocol upgrade
scaling. Restoration outcomes may deliver their total immediately or
continuously over an explicit duration.
The generic runtime in `src/lib/calculations/mechanic.ts` processes combat events
in timestamp order. Predicates decide whether an event enters a clause. Reducers
convert event streams into activations or derived values. The runtime applies
state mutations, enforces pool bounds and expiry, emits outcomes in phase order,
and records telemetry for each activation.
The generic runtime in `src/lib/calculations/mechanic.ts` processes combat
events in timestamp order. Predicates decide whether an event enters a clause.
Reducers convert event streams into activations or derived values. The runtime
applies state mutations, enforces pool bounds and expiry, emits outcomes in
phase order, and records telemetry for each activation.
Each write to an expiring pool refreshes its timer. When a pool expires,
`on_exit` outcomes evaluate stack thresholds and per-stack expressions against
@@ -508,9 +591,9 @@ intermediate ticks.
Live Attribute references use a stable Attribute ID and read the authoritative
loadout total that the builder captures for the evaluation pass. Percentage
Attributes retain their displayed units, so an expression that applies a
coefficient to a percentage divides the Attribute value by 100. Evaluation
fails when the loadout does not supply a referenced Attribute; the runtime does
not substitute zero.
coefficient to a percentage divides the Attribute value by 100. Evaluation fails
when the loadout does not supply a referenced Attribute; the runtime does not
substitute zero.
The read-only `combat.currentHealthPercent` reference divides resolved current
Health by resolved maximum Health and returns a value from 0 through 100. The
@@ -543,8 +626,8 @@ formatter regenerates the listing after each change.
Manual mechanic authoring remains the complete authoring path. The AI assistant
produces an optional draft for the existing structured editor. It cannot save,
publish, approve, or submit game data, and it cannot alter application code.
An author may ignore, revise, or replace any generated field through the manual
publish, approve, or submit game data, and it cannot alter application code. An
author may ignore, revise, or replace any generated field through the manual
controls.
The shared mechanic editor exposes the mechanic assistant for standard and
@@ -569,12 +652,12 @@ the parser, so CI blocks a prompt that advertises unsupported fields. A mechanic
grammar change must update the manifest and example, pass the round-trip test,
and increment the prompt contract version before release.
The OpenAI integration uses the Responses API with Structured Outputs, no
tools, and `store: false`. The application stores the bounded clarification
history needed for the current drafting session. Requests use a fixed
server-configured model, a privacy-preserving user safety identifier, input and
output limits, timeouts, duplicate-request protection, and per-agent rate
limits. The API key remains on the server.
The OpenAI integration uses the Responses API with Structured Outputs, no tools,
and `store: false`. The application stores the bounded clarification history
needed for the current drafting session. Requests use a fixed server-configured
model, a privacy-preserving user safety identifier, input and output limits,
timeouts, duplicate-request protection, and per-agent rate limits. The API key
remains on the server.
The model returns one structured result:
@@ -587,15 +670,15 @@ The model returns one structured result:
reference, and test changes a developer would need to add support.
For a mechanic capability gap, the client builds a developer prompt from the
exact analyzed wording, clarification transcript, source profile, and
structured gap response. The author can copy that prompt without another model
request. The author can also submit the gap to active full administrators.
The submission endpoint validates bounded fields, confirms that the request ID
belongs to the author's audited `capability_gap` generation, suppresses repeat
submissions, sends mandatory ISAC notifications, and appends a
`mechanic_gap.submitted` event to the `ai_assistance` audit category. The
notification links to the filtered audit evidence. Submission does not mutate
the mechanic draft or parent game-data record.
exact analyzed wording, clarification transcript, source profile, and structured
gap response. The author can copy that prompt without another model request. The
author can also submit the gap to active full administrators. The submission
endpoint validates bounded fields, confirms that the request ID belongs to the
author's audited `capability_gap` generation, suppresses repeat submissions,
sends mandatory ISAC notifications, and appends a `mechanic_gap.submitted` event
to the `ai_assistance` audit category. The notification links to the filtered
audit evidence. Submission does not mutate the mechanic draft or parent
game-data record.
The server validates `draft_ready` output against the response schema and the
same deterministic mechanic parser used by manual authoring. Unknown events,
@@ -616,11 +699,11 @@ allowed by their data role.
Successful generation operations append sanitized events under the
`ai_assistance` audit category. Events identify the operator, source type,
prompt-context version, model, result type, token usage, request ID, and
validation result. Audit snapshots exclude the API key,
environment configuration, hidden model reasoning, authentication data, and
unbounded prompt content. Applying a draft changes client state and creates no
server event. The owning module's game-data audit event remains the record of
any saved mechanic change.
validation result. Audit snapshots exclude the API key, environment
configuration, hidden model reasoning, authentication data, and unbounded prompt
content. Applying a draft changes client state and creates no server event. The
owning module's game-data audit event remains the record of any saved mechanic
change.
### AI-assisted calculation-revision drafting
@@ -628,16 +711,16 @@ The calculation assistant converts an operator's plain-language combat-model
request into an optional draft revision. It receives the current active or
edited revision, the calculation-stage vocabulary, weapon and Skill profile
contracts, and the active ISAC Attribute catalog. Its versioned prompt,
structured response schema, validation, and application adapter remain
separate from mechanic drafting.
structured response schema, validation, and application adapter remain separate
from mechanic drafting.
The assistant returns clarification questions, a validated revision draft, or
a calculation capability gap. A capability gap identifies the missing stage,
The assistant returns clarification questions, a validated revision draft, or a
calculation capability gap. A capability gap identifies the missing stage,
operator, curve, binding, runtime behavior, or validation rule and lists the
application and test changes needed to support it. The server rejects unknown
stage keys, locked-position changes, duplicate bindings, invalid Attribute
references, unsupported operators, and malformed curve or cap values through
the deterministic calculation-revision parser.
references, unsupported operators, and malformed curve or cap values through the
deterministic calculation-revision parser.
The `ai.calculation.generate` permission authorizes calculation drafting and
clarification requests. It does not grant revision mutation or publication.
@@ -657,8 +740,8 @@ Source authors define the builder controls required by their mechanic as typed
toggle, slider, or numeric scenario inputs. Each input records its label,
guidance, bounds when applicable, default value, and best-case value. These
controls appear only in advanced scenario configuration. Maximum mode supplies
best-case values. Custom mode uses the values saved with the loadout. A
mechanic with an undefined required assumption remains excluded and reports
best-case values. Custom mode uses the values saved with the loadout. A mechanic
with an undefined required assumption remains excluded and reports
`REQUIRES_ASSUMPTIONS`.
Loadout schema version 4 stores one focus, selected tree ranks, nine Skill
@@ -673,8 +756,8 @@ The builder offers `maximum` and `custom` modes.
- Maximum mode supplies each scenario input's best-case value, selects peak
conditional-effect state, and runs selected abilities on cooldown against
their configured target caps. The interface hides granular assumptions in
this mode.
their configured target caps. The interface hides granular assumptions in this
mode.
- Custom mode uses the agent's encounter inputs, effect states, activation
rates, target counts, and other supported assumptions. A missing required
input produces `REQUIRES_ASSUMPTIONS` rather than a fabricated value.
@@ -689,8 +772,8 @@ estimate.
Shredding damage, direct or periodic delivery, hit or tick counts, cast time,
target cap, and charge behavior. `skill_attribute_calculation_profiles` binds
Attributes to the versioned Skill damage and cadence stages. Cooldown Recovery
changes charge and activation readiness. Duration changes the number of
periodic ticks.
changes charge and activation readiness. Duration changes the number of periodic
ticks.
Periodic delivery waits one configured tick interval before its first tick and
excludes ticks at or after the encounter boundary. The Skill engine reevaluates
@@ -734,11 +817,10 @@ state. Custom mode can set an effect to Peak, Encounter, or Off.
The Skill engine uses a dedicated intrinsic ability-damage model. It supports
direct and periodic delivery, critical and multi-shot expectation, target caps,
per-ability activation rates, and charge recovery.
Skills do not headshot. Dynamic Talent and tree-effect windows apply to each
Skill hit at its delivery time. Encounter-triggered Skill modifiers use the
primary weapon timeline until the builder supports a combined weapon-rotation
model.
per-ability activation rates, and charge recovery. Skills do not headshot.
Dynamic Talent and tree-effect windows apply to each Skill hit at its delivery
time. Encounter-triggered Skill modifiers use the primary weapon timeline until
the builder supports a combined weapon-rotation model.
The engine returns `INSUFFICIENT_DATA` with missing requirement keys when a
calculation lacks required inputs or an Attribute binding. Callers must render
@@ -751,12 +833,12 @@ Ready cases require the four hit values plus magazine and reload times. The
assisted suggestion action compares ready observations with their predictions
and creates a draft revision for operator review.
Rule activation requires `calculation.publish` and a confirmation.
ISAC records draft creation, draft edits, publication, calibration changes, and
assisted suggestions under the `calculation_configuration` audit category.
ISAC data export and environment synchronization include calculation rules,
typed mechanic programs, ability attacks, calibration cases, and their
relationships in dependency order.
Rule activation requires `calculation.publish` and a confirmation. ISAC records
draft creation, draft edits, publication, calibration changes, and assisted
suggestions under the `calculation_configuration` audit category. ISAC data
export and environment synchronization include calculation rules, typed mechanic
programs, ability attacks, calibration cases, and their relationships in
dependency order.
## OS Protocol upgrade expressions and health timing
@@ -793,9 +875,9 @@ rasterize and normalize each accepted icon to a transparent 256 × 256 WebP.
Normalized files live beneath the ignored `data/game-icons/` directory. The
validated `/icons/game-data/[type]/[id]` endpoint serves them with immutable
cache headers and disables MIME sniffing. ISAC save actions process files
before mutation, apply upload or removal after record validation, and include
safe icon metadata, rather than image bytes, in audit snapshots.
cache headers and disables MIME sniffing. ISAC save actions process files before
mutation, apply upload or removal after record validation, and include safe icon
metadata, rather than image bytes, in audit snapshots.
Icon metadata is optional. Canonical records and contributor proposals may be
created without an upload, which lets contributors submit values and artwork in
@@ -820,8 +902,8 @@ keeps the library metadata and one copy of each file.
The notification router sends Data Change review items to agents who hold
`data.approve` and Avatar Oversight review items to agents who hold
`avatars.review`. Each reviewer chooses Immediate, 15-minute, one-hour, or
daily delivery for each authorized queue. Submission creates Immediate alerts;
`avatars.review`. Each reviewer chooses Immediate, 15-minute, one-hour, or daily
delivery for each authorized queue. Submission creates Immediate alerts;
`oversight_notification_queue` stores consolidated alerts.
Production must run the idempotent dispatcher once per minute from one scheduler
@@ -831,12 +913,12 @@ instance:
* * * * * cd /path/to/shd.build && npm run notifications:oversight:production
```
The dispatcher groups due rows by reviewer, queue, cadence, and delivery
window. Daily rows use the next 10:00 AM wall-clock boundary in
`America/New_York`, including daylight-saving transitions. Dispatched rows
retain `delivered_at` evidence, and later queries exclude them. Existing
ISAC notification delivery preferences determine optional browser and email
fan-out after the dispatcher creates the consolidated inbox message.
The dispatcher groups due rows by reviewer, queue, cadence, and delivery window.
Daily rows use the next 10:00 AM wall-clock boundary in `America/New_York`,
including daylight-saving transitions. Dispatched rows retain `delivered_at`
evidence, and later queries exclude them. Existing ISAC notification delivery
preferences determine optional browser and email fan-out after the dispatcher
creates the consolidated inbox message.
## Automated ISAC system messages
@@ -847,15 +929,15 @@ managed through ISAC → System Configuration → Automated Messages by agents w
enabled state, delay in minutes, message content, primary destination, and
structured action callouts.
Password, magic-link, and Discord registration emit `account.created`.
The delivery table has a unique message/user/event/event-ID constraint, so the
same account creation cannot deliver the Welcome Agent message more than once.
The default welcome introduces News, Database, Builds, Guides, Builder, ISAC
Password, magic-link, and Discord registration emit `account.created`. The
delivery table has a unique message/user/event/event-ID constraint, so the same
account creation cannot deliver the Welcome Agent message more than once. The
default welcome introduces News, Database, Builds, Guides, Builder, ISAC
Contributor Mode, and the SHD Discord community.
The registration request delivers Immediate definitions.
`automated_system_message_deliveries` stores definitions with a delay. Production
must run the delayed dispatcher once per minute:
`automated_system_message_deliveries` stores definitions with a delay.
Production must run the delayed dispatcher once per minute:
```text
* * * * * cd /path/to/shd.build && npm run notifications:system:production