0265afa054
Import the runnable game code, content, docs, scripts, and repo guidance while leaving local agent state, dependency installs, build output, and backup copies out of the published tree.
378 lines
16 KiB
Markdown
378 lines
16 KiB
Markdown
# SYSADMIN CHRONICLES — PROJECT MAP
|
||
> Living document. Update when files are added, moved, removed, or when architecture changes.
|
||
> Version 5.1 | Living document — update when files are added, moved, or removed.
|
||
|
||
---
|
||
|
||
## ROOT STRUCTURE
|
||
|
||
```
|
||
sysadmin-chronicles/
|
||
│
|
||
├── server/ ← NEW: Node.js game server
|
||
│ ├── src/
|
||
│ │ ├── index.js Entry point — Express + WebSocket
|
||
│ │ ├── routes/ auth, state, tickets, mail, docs, sage, vms
|
||
│ │ ├── services/ ContentLoader, QuestEngine, TicketService,
|
||
│ │ │ ValidationEngine, VMManager, TrustSystem,
|
||
│ │ │ ProgressionSystem, EmailService, SageService,
|
||
│ │ │ ShiftTimer, IncidentScheduler, ShiftReviewService,
|
||
│ │ │ CertificationService, SaveState
|
||
│ │ └── lib/ ssh.js, virsh.js, command.js, eventBus.js, session.js
|
||
│ └── package.json
|
||
│
|
||
├── frontend/ ← NEW: Svelte web HUD
|
||
│ ├── src/
|
||
│ │ ├── App.svelte Root component, WebSocket, panel routing
|
||
│ │ ├── components/ TicketsPanel, MailPanel, DocsPanel, SagePanel,
|
||
│ │ │ VmsPanel, ProfilePanel, HeaderBar, SidebarTabs
|
||
│ │ ├── lib/api.js REST API fetch wrapper
|
||
│ │ └── main.js
|
||
│ ├── dist/ Built output (served by game server)
|
||
│ └── package.json
|
||
│
|
||
├── scripts/
|
||
│ └── start-game.sh One-shot: start server + open SPICE workstation viewer
|
||
│
|
||
├── docs/
|
||
│ ├── ARCHITECTURE.md System architecture
|
||
│ ├── CHARACTERS.md All characters — bios, relationships, story hooks
|
||
│ ├── COMPANY_LORE.md World, company, products, tone guidelines
|
||
│ ├── INSTALLER_PLAN.md Installer design and packaging
|
||
│ ├── PRESSURE_PROFILES.md Time-pressure escalation schema and authoring guide
|
||
│ ├── PROJECT_MAP.md ← this file
|
||
│ ├── ROADMAP.md Development phases and content status
|
||
│ ├── RUNTIME_DEPENDENCIES.md Host dependencies and version requirements
|
||
│ ├── SAVE_SYSTEM.md Save model, VM persistence policy, recovery flows
|
||
│ ├── SNAPSHOT_CHAIN.md VM snapshot chain and baseline management
|
||
│ ├── STORY_DESIGN_CONTEXT.md How story works — narrative arc, quest model, design constraints
|
||
│ ├── VM_BUILD_SYSTEM.md VM build and provisioning system
|
||
│ ├── WORKSTATION_POLISH_BACKLOG.md Outstanding UX polish items
|
||
│ └── codex-specs/
|
||
│
|
||
├── content/ ← data-driven content loaded by Node.js server
|
||
│ ├── quests/ quest JSON files (being reworked — see STORY_DESIGN_CONTEXT.md)
|
||
│ ├── tickets/ ticket JSON files (being reworked)
|
||
│ ├── incidents/ incident JSON files (being reworked)
|
||
│ ├── pressure_profiles/ escalation profiles (schema in PRESSURE_PROFILES.md)
|
||
│ ├── dialogue/ character dialogue JSON files (being reworked)
|
||
│ ├── world_flags/ world_flags.json (central registry)
|
||
│ ├── docs/ onboarding, sage_content, internal_docs, etc.
|
||
│ ├── progression/ trust_unlocks.json, access_tiers.json
|
||
│ └── vm_profiles/ workstation.json, web_server.json, build_machine.json
|
||
│
|
||
├── tools/
|
||
│ ├── setup/ check-host.sh, seed-vms.sh, first-run-setup.sh, uninstall.sh
|
||
│ ├── vm/ build-vm.sh, build-*.sh, snapshot-all.sh, suppress-maintenance-noise.sh
|
||
│ │ ├── profiles/ workstation.sh, web-server.sh, build-machine.sh
|
||
│ │ └── quest-prep/ Q001–Q008 prep/post scripts
|
||
│ └── content/ validate-content.js (zero-error gate), verify-clue-fingerprints.js
|
||
│
|
||
├── company-website/ Axiom Works public website (static HTML/CSS)
|
||
│ ├── index.html Home — hero, product highlights, stats
|
||
│ ├── about.html Company story, values, contact
|
||
│ ├── people.html Team page — Dave, Marcus, Priya, Sarah + filler staff
|
||
│ ├── products.html AxiomFlow, AxiomDash, AxiomSync product pages
|
||
│ ├── style.css Shared corporate CSS (navy/blue scheme)
|
||
│ └── assets/ logo.png, portrait photos for each NPC
|
||
│
|
||
├── vm/ images/, snapshots/, cloud-init/, probes/
|
||
├── package.json
|
||
└── README.md
|
||
```
|
||
|
||
|
||
|
||
---
|
||
|
||
## COMPANY WEBSITE
|
||
|
||
Static HTML/CSS site serving as the public-facing Axiom Works company website, accessible from the workstation VM.
|
||
|
||
**URL inside the VM:** `http://www.axiomworks.corp/` (no port)
|
||
|
||
**How it works:**
|
||
- The game server serves `company-website/` at `/company/` (port 3000)
|
||
- nginx is installed in the workstation VM and proxies `axiomworks.io` and `www.axiomworks.io` (port 80) → game server port 3000 at `/company/`
|
||
- `/etc/hosts` in the workstation maps both hostnames to `127.0.0.1` (localhost → nginx)
|
||
- Result: the player sees a clean `http://www.axiomworks.io/` URL in Chromium with no port number
|
||
|
||
**Pages:** Home (`index.html`), About (`about.html`), Our Team (`people.html`), Products (`products.html`)
|
||
|
||
**Team page portraits:** NPC photos live in `company-website/assets/`. The player is not featured on the website.
|
||
|
||
**Domain note:** `axiomworks.corp` uses the IANA-reserved `.corp` TLD (reserved 2024, can never be publicly delegated). No registration needed — it will never resolve on the real internet. The in-VM `/etc/hosts` + nginx approach is sufficient for any build.
|
||
|
||
**Player portraits** (for the HUD profile panel) are separate from the website portraits. They live in `server/public/portraits/` and are served at `/public/portraits/`. The player selects one via the Profile panel; the choice persists in `save.json` as `player_portrait`.
|
||
|
||
---
|
||
|
||
## BOOT FLOW (Node.js Server)
|
||
|
||
```
|
||
bash scripts/start-game.sh
|
||
↓
|
||
node server/src/index.js
|
||
1. ContentLoader.load() — reads all content/**/*.json into memory
|
||
2. SaveState.load() — reads ~/.local/share/sysadmin-chronicles/save.json
|
||
or creates fresh save
|
||
3. TrustSystem.initialize() — hydrates trust score + unlock state
|
||
4. ProgressionSystem.initialize()
|
||
5. QuestEngine.initialize() — restores quest states from save
|
||
6. TicketService.initialize()
|
||
7. EmailService.initialize() — restores inbox, seeds T001 email on fresh save
|
||
8. ShiftTimer.start() — starts shift clock
|
||
9. IncidentScheduler.start() — begins pressure tick loop (every 30s)
|
||
10. VMManager.ensureWorkstationLive() — virsh start sc-workstation if needed
|
||
↓
|
||
Express + WebSocket listening on PORT (default 3000)
|
||
↓
|
||
remote-viewer opens SPICE connection to sc-workstation
|
||
Player sees XFCE desktop → Chromium opens HUD → game is live
|
||
```
|
||
|
||
---
|
||
|
||
## TICKET COMPLETION FLOW
|
||
|
||
```
|
||
Player clicks "Mark Complete" on ticket in HUD
|
||
↓
|
||
POST /api/tickets/:id/complete
|
||
↓
|
||
TicketService.markComplete(ticketId)
|
||
→ load ticket + linked quest JSON
|
||
→ for each solution_branch (sorted by priority DESC):
|
||
ValidationEngine.check(vmId, branch.validation.rules)
|
||
→ VMManager.getIP(vmId)
|
||
→ SSH as opsbridge using sc_host_key
|
||
→ run each rule check (file_exists, service_state, etc.)
|
||
if all rules pass → winning branch found
|
||
→ TrustSystem.adjust(branch.trust_delta)
|
||
→ WorldFlags.set(branch.world_flags)
|
||
→ QuestEngine.completeQuest(questId)
|
||
→ EmailService.send(follow-up NPC email if negative branch)
|
||
→ SaveState.write()
|
||
→ broadcast trust:changed, mail:new via WebSocket
|
||
↓
|
||
Response: { passed, branch, trust_delta, failures }
|
||
HUD shows success toast or failure details
|
||
```
|
||
|
||
---
|
||
|
||
## VM IDENTITY TABLE
|
||
|
||
| vm_id | SC constant | libvirt domain | hostname | distro | ssh_user | mgmt_user | always_live | Quests |
|
||
|-------|-------------|----------------|----------|--------|----------|-----------|-------------|--------|
|
||
| `workstation` | `SC.VM_WORKSTATION` | `sc-workstation` | `ares` | Debian 12 | `player` | `opsbridge` | yes | Q001 |
|
||
| `web_server` | `SC.VM_WEB_SERVER` | `sc-web-server` | `hermes` | Debian 12 | `player` | — | no | Q002–Q005, Q007 |
|
||
| `build_machine` | `SC.VM_BUILD_MACHINE` | `sc-build-machine` | `vulcan` | Arch Linux | `player` | — | no | Q006, Q008 |
|
||
|
||
See `docs/VM_BUILD_SYSTEM.md` for full build system documentation and profile authoring guide.
|
||
|
||
**SSH key**: all host→guest connections use `~/.ssh/sc_host_key` (BatchMode, no password).
|
||
|
||
**Baseline snapshots**:
|
||
- workstation: `baseline.day-one`
|
||
- web_server, build_machine: `baseline.clean`
|
||
|
||
---
|
||
|
||
## TERMINAL ARCHITECTURE
|
||
|
||
The player uses a real **Tilix** terminal inside the workstation VM (sc-workstation / ares).
|
||
No terminal simulation. SSH to target VMs is real SSH. There is no in-game terminal widget.
|
||
|
||
```
|
||
Player opens Tilix on the workstation XFCE desktop
|
||
→ types: ssh hermes
|
||
→ real SSH to sc-web-server using player's authorized_keys
|
||
→ works directly on the target VM
|
||
|
||
Host-side validation (triggered by "Mark Complete" in HUD):
|
||
ValidationEngine.js SSHes as 'opsbridge' → sudo -H -i -u player
|
||
Runs rule checks (file_exists, service_state, etc.)
|
||
Returns pass/fail to game server
|
||
```
|
||
|
||
Host SSH options (used by ValidationEngine.js and VMManager.js):
|
||
```
|
||
-o StrictHostKeyChecking=no
|
||
-o BatchMode=yes
|
||
-o ConnectTimeout=5
|
||
-o LogLevel=ERROR
|
||
-i ~/.ssh/sc_host_key
|
||
```
|
||
|
||
---
|
||
|
||
## SERVICE DEPENDENCY GRAPH (Node.js server)
|
||
|
||
```
|
||
eventBus.js (Node.js EventEmitter — no deps)
|
||
└─ consumed by: all services
|
||
|
||
ContentLoader
|
||
└─ consumed by: QuestEngine, TicketService, ValidationEngine, TrustSystem,
|
||
ProgressionSystem, IncidentScheduler, EmailService, SageService
|
||
|
||
VMManager
|
||
← wraps virsh.js + ssh.js
|
||
← called by QuestEngine (start required VMs on quest activation)
|
||
← called by ValidationEngine (get VM IP for SSH)
|
||
|
||
ValidationEngine
|
||
← calls VMManager.getIP(vmId)
|
||
← SSHes as opsbridge → runs rule checks (file_exists, service_state, etc.)
|
||
← called by TicketService on mark-complete
|
||
|
||
QuestEngine
|
||
← calls VMManager to start required VMs
|
||
← calls ValidationEngine via TicketService
|
||
← calls TrustSystem, WorldFlags, EmailService on resolution
|
||
→ emits via eventBus: quest:activated, quest:resolved, ticket:received
|
||
|
||
IncidentScheduler
|
||
← reads WorldFlags for trigger conditions
|
||
← tick drives escalation step advancement
|
||
→ emits via eventBus: incident:activated, incident:escalated, incident:resolved
|
||
|
||
TrustSystem
|
||
← called by QuestEngine on branch resolution
|
||
← called by IncidentScheduler for ignored incident penalties
|
||
→ emits via eventBus: trust:changed
|
||
|
||
SaveState
|
||
← called by QuestEngine, TrustSystem, ProgressionSystem
|
||
← reads/writes ~/.local/share/sysadmin-chronicles/save.json
|
||
```
|
||
|
||
---
|
||
|
||
## KEY MODULES
|
||
|
||
### Server (`server/src/`)
|
||
|
||
| Module | File | Responsibility |
|
||
|--------|------|----------------|
|
||
| Entry point | index.js | Express + WS, service wiring, static serving |
|
||
| ContentLoader | services/ContentLoader.js | Load all content/ JSON at startup |
|
||
| QuestEngine | services/QuestEngine.js | Quest state machine |
|
||
| TicketService | services/TicketService.js | Ticket state, mark-complete, branch resolution |
|
||
| ValidationEngine | services/ValidationEngine.js | SSH rule evaluation (all rule types) |
|
||
| VMManager | services/VMManager.js | virsh wrappers, IP resolution |
|
||
| TrustSystem | services/TrustSystem.js | Score, unlocks, revocation |
|
||
| ProgressionSystem | services/ProgressionSystem.js | Unlocked VMs, docs, access |
|
||
| EmailService | services/EmailService.js | Inbox, follow-ups, reply options |
|
||
| SageService | services/SageService.js | Rule-based dialogue / KB |
|
||
| ShiftTimer | services/ShiftTimer.js | Shift clock, 30s tick broadcasts |
|
||
| IncidentScheduler | services/IncidentScheduler.js | Pressure tick, incident injection |
|
||
| ShiftReviewService | services/ShiftReviewService.js | End-of-shift review email |
|
||
| CertificationService | services/CertificationService.js | Cert awards after quest chains |
|
||
| SaveState | services/SaveState.js | Read/write save.json |
|
||
| ssh.js | lib/ssh.js | Promisified SSH execution |
|
||
| virsh.js | lib/virsh.js | virsh command wrappers |
|
||
| eventBus.js | lib/eventBus.js | Node.js EventEmitter for service coordination |
|
||
|
||
### Frontend (`frontend/src/`)
|
||
|
||
| Component | File | Responsibility |
|
||
|-----------|------|----------------|
|
||
| Root | App.svelte | Panel routing, WebSocket connection |
|
||
| Tickets | TicketsPanel.svelte | List, detail, mark-complete |
|
||
| Mail | MailPanel.svelte | Inbox, message, reply buttons |
|
||
| Docs | DocsPanel.svelte | Trust-gated doc viewer |
|
||
| Sage | SagePanel.svelte | Chat / KB search |
|
||
| VMs | VmsPanel.svelte | Live VM status indicators |
|
||
| Header | HeaderBar.svelte | Trust, shift timer, mail badge |
|
||
| API | lib/api.js | REST fetch wrapper |
|
||
|
||
---
|
||
|
||
## CONTENT DOMAINS
|
||
|
||
| Domain | Purpose |
|
||
|--------|---------|
|
||
| `quests/` | Objective chains, clue fingerprints, validation rules, branch priorities |
|
||
| `tickets/` | Player-facing problem statements with initial/current priority |
|
||
| `incidents/` | Dynamic pressure events with blast_radius and escalation steps |
|
||
| `dialogue/` | Workplace messages, hints, follow-ups, series threads |
|
||
| `pressure_profiles/` | Reusable escalation templates referenced by quest branches |
|
||
| `world_flags/` | Central registry — all world state flags declared here |
|
||
| `docs/` | Internal documentation + Sage/help content (trust-gated) |
|
||
| `progression/` | Trust thresholds, unlocks, revocation rules, access tiers |
|
||
| `vm_profiles/` | Domain names, hostnames, snapshots, networks, resource budgets |
|
||
|
||
---
|
||
|
||
## FILE NAMING CONVENTIONS
|
||
|
||
- Quest files: `Q{NNN}-{kebab-case-title}.json`
|
||
- Ticket files: `T{NNN}.json`
|
||
- Incident files: `I{NNN}-{kebab-case-title}.json`
|
||
- Dialogue files: `{character}-Q{NNN}.json` or `{character}-Q{NNN}-{variant}.json`
|
||
- Quest prep scripts: `Q{NNN}-prep.sh`
|
||
- VM profiles: `{snake_case}.json`
|
||
|
||
---
|
||
|
||
## CONTENT VALIDATION CHECKS
|
||
|
||
Run: `node tools/content/validate-content.js` — must exit 0 (zero errors).
|
||
|
||
| Check | Rule |
|
||
|-------|------|
|
||
| JSON well-formed | All content files parse without error |
|
||
| No duplicate IDs | Unique across quests, tickets, incidents, pressure profiles, dialogue |
|
||
| World flags | Every referenced flag exists in `world_flags/world_flags.json` |
|
||
| required_vms | Every entry maps to a valid VM profile |
|
||
| blast_radius | Every entry maps to an existing incident file |
|
||
| linked_quest | Every ticket's linked_quest maps to an existing quest |
|
||
| ticket_id | Every quest's ticket_id maps to an existing ticket |
|
||
| Branch priority | Priorities unique per quest (no ties) |
|
||
| follow_up_incident | Maps to an existing incident file |
|
||
| pressure_profile | Maps to an existing pressure profile file |
|
||
| series_id | Every series_id has at least two dialogue members |
|
||
| revokes | Trust unlock revoke entries reference valid unlock strings |
|
||
| clue_fingerprint | Evidence rule types are valid |
|
||
|
||
---
|
||
|
||
## KNOWN GAPS (Post-Redesign)
|
||
|
||
These are gaps in the v4.0 Node.js + Svelte implementation.
|
||
All content is authored, validator-clean, and reused unchanged.
|
||
|
||
### P0 — Blocking for first playable shift
|
||
|
||
| Gap | Notes |
|
||
|-----|-------|
|
||
| Phase 7 workstation VM verification | Confirm SPICE display, Chromium autostart, Tilix as default work end-to-end on a freshly seeded VM |
|
||
| Phase 10 full playtest | Boot all VMs, play Q001→Q002, validate full server→SSH→HUD loop |
|
||
|
||
### P1 — Required before broader testing
|
||
|
||
| Gap | Notes |
|
||
|-----|-------|
|
||
| Clue quality as system degrades | Evidence should remain legible as incidents escalate (I001/I002/I003 escalation pass) |
|
||
| Viewer smoothness | `remote-viewer` SPICE path is functional but not final-UX smooth; lower priority with real XFCE desktop |
|
||
|
||
### P2 — Polish / completeness
|
||
|
||
| Gap | Notes |
|
||
|-----|-------|
|
||
| WORKSTATION_POLISH_BACKLOG.md items | See that file for outstanding desktop UX polish |
|
||
|
||
---
|
||
|
||
## GENERATED / LARGE ASSETS
|
||
|
||
Created by CLI tooling, not hand-managed:
|
||
|
||
- `vm/images/*.qcow2`
|
||
- Imported libvirt domain XML
|
||
- Baseline snapshot exports or manifests
|
||
- Shift checkpoint snapshots
|
||
- Packaged Linux build artifacts
|