chore: bootstrap lean sysadmin-chronicles repo
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.
This commit is contained in:
@@ -0,0 +1,459 @@
|
||||
# Characters — Sysadmin Chronicles
|
||||
|
||||
Story design reference. All characters, bios, relationships, and open story hooks.
|
||||
For company/world context see `COMPANY_LORE.md`. This file focuses on the people.
|
||||
|
||||
---
|
||||
|
||||
## Active Characters
|
||||
|
||||
These characters have an established in-game voice and presence. Any new quest work
|
||||
should treat their characterization here as canonical.
|
||||
|
||||
---
|
||||
|
||||
### The Player
|
||||
**Role:** New junior sysadmin hire, day one
|
||||
**Identity:** Unnamed. Player-selected portrait (5 options).
|
||||
|
||||
Hired to replace Dale. Nobody will explain what Dale did. Badge number is still
|
||||
pending — temp credentials were handled by someone in Finance on their first day.
|
||||
The player is a competent professional, not a bumbling intern. They may not know
|
||||
every answer but they know how to look.
|
||||
|
||||
The player has no spoken lines. Their character is expressed entirely through the
|
||||
choices they make when fixing things — whether they understand root causes or just
|
||||
clear symptoms, whether they leave systems better or just less broken.
|
||||
|
||||
---
|
||||
|
||||
### Marcus Webb
|
||||
**Role:** Senior Systems Administrator
|
||||
**Email:** `m.webb@axiomworks.internal`
|
||||
**Reports to:** Dave Kowalski (Director of IT)
|
||||
|
||||
Six years at Axiom Works. Hired by Kowalski. Knows where everything is, why it's
|
||||
there, and which parts were a mistake. Communicates in short, precise messages.
|
||||
Does not explain things twice. Trusts competence over credentials — he will give
|
||||
the player more rope as they demonstrate they know what to do with it. If they
|
||||
don't, the rope gets shorter.
|
||||
|
||||
He was the one who onboarded the player. He assigned their first ticket. He will
|
||||
assign most of the tickets that follow. His messages range from brief task
|
||||
assignments to late-night observations about something that's been on his mind —
|
||||
the latter usually mean something is about to become a problem.
|
||||
|
||||
He knows what Dale did. He has decided not to discuss it.
|
||||
|
||||
**Personality:** Dry. Technically precise. Does not perform enthusiasm. Occasionally
|
||||
wry but never jokey. Respects players who fix root causes. Mildly annoyed by
|
||||
players who fix symptoms and call it done.
|
||||
|
||||
**Relationships:**
|
||||
- Kowalski: reports to him; respectful but not deferential
|
||||
- Sarah: professional; takes her tickets seriously, occasionally says quiet things when she's wrong
|
||||
- Priya: mutual professional respect; they operate in the same zone of "things that matter when they go wrong"
|
||||
- Phil Ruiz (Sales VP): warm; Phil owes Marcus for saving a demo once and Marcus has never mentioned it
|
||||
|
||||
---
|
||||
|
||||
### Sarah Chen
|
||||
**Role:** Product Manager, AxiomFlow
|
||||
**Email:** `s.chen@axiomworks.internal`
|
||||
|
||||
Owns the AxiomFlow product roadmap. Coordinates between sales, engineering, and
|
||||
customers. Emails Monday mornings. Cares intensely about the demo and staging
|
||||
environments because those are the product she can actually see and touch. Not wrong
|
||||
about their importance.
|
||||
|
||||
She files tickets when things break on the product-facing side. Her descriptions of
|
||||
problems are accurate about symptoms and often wrong about causes — she will
|
||||
confidently diagnose a permissions issue as a script bug, or a package problem as a
|
||||
config error. She is not incompetent; she just doesn't have the full picture. When
|
||||
the player fixes the underlying cause rather than the surface symptom, she notices.
|
||||
|
||||
She has a sharp edge when things get worse after someone touches them. She will say
|
||||
so, clearly, without being melodramatic about it.
|
||||
|
||||
**Personality:** Direct. Metric-oriented. Not patient with vague timelines or "we're
|
||||
looking into it." Appreciates being told what the actual problem was, not just that
|
||||
it's fixed.
|
||||
|
||||
**Relationships:**
|
||||
- Marcus: professional; trusts that her tickets will be handled, doesn't ask for much
|
||||
- Player: initially impersonal (they're new); warms or cools based on outcomes
|
||||
- Nikhil Sharma: upstream dependency — his build pipeline affects her deployments
|
||||
|
||||
---
|
||||
|
||||
### Priya Nair
|
||||
**Role:** Head of Security & Compliance
|
||||
**Email:** `p.nair@axiomworks.internal`
|
||||
**Direct report:** James Osei (Security Analyst)
|
||||
|
||||
Leads all security reviews, access audits, and compliance programmes. Has a standing
|
||||
Thursday meeting with David Park (CTO) that has existed since 2017. Was brought in
|
||||
after an incident nobody discusses in public. Has been building the security function
|
||||
from something informal into something that can survive a SOC 2 audit.
|
||||
|
||||
She frames everything in terms of what happens when things go wrong, not whether they
|
||||
will. She assumes breach. She assumes misconfiguration. She is often right. She is
|
||||
not someone who appreciates hearing about a production change after it has already
|
||||
happened.
|
||||
|
||||
She will tell the player when a fix is correct and why. She will also tell them when
|
||||
a fix works but leaves the environment in a worse position than before. She is not
|
||||
punitive about this — she just states it.
|
||||
|
||||
She does shift reviews at end-of-shift and grades the player's overall performance.
|
||||
Her criteria: did the work move forward, did the environment stay stable, did the
|
||||
player create extra problems.
|
||||
|
||||
**Personality:** Precise. Consequence-focused. Calm in tone even when the content
|
||||
is not calm. Economical with words. Does not use exclamation marks.
|
||||
|
||||
**Relationships:**
|
||||
- Player: evaluative; her trust is earned by demonstrating that security is a
|
||||
consideration, not an afterthought
|
||||
- Marcus: peer respect; they operate in different domains with overlapping concerns
|
||||
- Dave Kowalski: reports indirectly up through him for infrastructure decisions
|
||||
- David Park: standing Thursday meeting; she has the CTO's ear
|
||||
|
||||
> **Name note for developers:** The in-game email service and some ticket files
|
||||
> previously used "Priya Kapoor" and the onboarding doc used "Priya Singh."
|
||||
> These are all the same character. **Priya Nair** is the canonical name.
|
||||
> Email should be `p.nair@axiomworks.internal`. Update references in
|
||||
> `server/src/services/EmailService.js`, `content/tickets/T007.json`, and
|
||||
> `content/docs/onboarding.json`.
|
||||
|
||||
---
|
||||
|
||||
### Dave Okonkwo
|
||||
**Role:** Internal employee, non-technical
|
||||
**Email:** `d.okonkwo@axiomworks.internal`
|
||||
|
||||
A regular Axiom Works employee who notices when things aren't working and files
|
||||
tickets about it. He doesn't know enough to diagnose the problem — he reports
|
||||
symptoms accurately and assumes the wrong cause. His reports are useful precisely
|
||||
because they represent what a non-technical user actually experiences.
|
||||
|
||||
He is not on the company website (280 employees, most of them aren't). He's
|
||||
somewhere in operations or general staff. He's not in Finance, not in IT.
|
||||
|
||||
> **Open decision:** Dave Okonkwo is currently the only employee-level character who
|
||||
> submits tickets. The company website has Dave Kowalski as Director of IT Operations
|
||||
> (Marcus's boss), which is a completely different person. This is not a naming
|
||||
> inconsistency — they're two different people. However: if the story wants Kowalski
|
||||
> to become an active character who also files tickets or escalates issues, that's a
|
||||
> separate thread. Okonkwo and Kowalski coexist.
|
||||
|
||||
---
|
||||
|
||||
## Named Background Characters
|
||||
|
||||
On the company website. No current in-game presence. Available for story use —
|
||||
they can send emails, appear on CC lines, be referenced in dialogue, or become
|
||||
active characters in new quests.
|
||||
|
||||
Listed in rough order of story relevance to the IT/sysadmin context.
|
||||
|
||||
---
|
||||
|
||||
### Dave Kowalski — Director of IT Operations
|
||||
Marcus's manager. The player's skip-level. Background is network engineering —
|
||||
has Cisco certifications he will not volunteer unless provoked. Oversees systems
|
||||
(Marcus's domain), networking (Tom Malaney), and IT support. Has been at Axiom
|
||||
Works since 2015. Describes the infrastructure as "mature." Sends weekly status
|
||||
emails in bullet points that never quite answer the question. When things go wrong
|
||||
he schedules a meeting to "talk through the situation," which everyone has learned
|
||||
is worse than a direct message.
|
||||
|
||||
Has said "we should really document that" more times than he can count. Has
|
||||
documented very little personally. Maintains a mysterious Tuesday 2–3pm calendar
|
||||
block.
|
||||
|
||||
Story use: source of policy pressure, indirect escalation, the person who asks
|
||||
questions that reveal Marcus hasn't told the player everything.
|
||||
|
||||
---
|
||||
|
||||
### Nikhil Sharma — Platform Engineer
|
||||
Owns the internal build and release pipeline, the CI infrastructure, and the
|
||||
parts of deployment that nobody else wants to think about. Strong opinions about
|
||||
reproducible builds. Sends Slack messages at 6am. Occasionally at 11pm.
|
||||
|
||||
He is the engineer most directly connected to what happens on vulcan — if a build
|
||||
is broken, it's probably something Nikhil built or maintains. He has never met the
|
||||
player. He almost certainly doesn't know the player exists.
|
||||
|
||||
Story use: the author of broken packages the player has to debug; a character who
|
||||
can explain (or fail to explain) what went wrong upstream; an escalation path when
|
||||
a build problem is genuinely his fault.
|
||||
|
||||
---
|
||||
|
||||
### Tanya Okafor — Head of Customer Success
|
||||
Manages post-sale relationships for all AxiomFlow customers and the twelve legacy
|
||||
AxiomSync accounts that haven't migrated. Uses the word "partnership" a lot.
|
||||
|
||||
Usually the first person to know when something is wrong in production, because a
|
||||
customer has already called her before IT knows there's a problem. Her call log
|
||||
is an early warning system. She is not hostile to IT but she has learned that
|
||||
"we're looking into it" is not an answer she can give a customer.
|
||||
|
||||
Story use: pressure vector from the customer direction; source of urgency that
|
||||
doesn't come from Marcus or the ticket queue; demonstrates real-world stakes when
|
||||
things go down.
|
||||
|
||||
---
|
||||
|
||||
### Phil Ruiz — VP of Sales
|
||||
Has been promising features to prospects since 2016. Maintains a warm relationship
|
||||
with the infrastructure team because Marcus once fixed the staging environment with
|
||||
twenty minutes to spare before a major demo — Phil has never forgotten this. Travels
|
||||
frequently. Expense reports submitted promptly, which Marcus has noted approvingly.
|
||||
|
||||
Story use: indirect beneficiary when demos work; pressure source when a sales demo
|
||||
is scheduled and something is broken; the person who will tell the CTO what IT did
|
||||
right in a room the player will never be in.
|
||||
|
||||
---
|
||||
|
||||
### Yusuf Halabi — Engineering Manager
|
||||
Reports to David Park (CTO). Manages the core AxiomFlow platform team. Runs the
|
||||
Thursday architecture review. Has opinions about test coverage. Leaves pull request
|
||||
comments that are technically correct and diplomatically suboptimal.
|
||||
|
||||
Story use: engineering-side escalation; source of tickets about internal tooling;
|
||||
the person who will ask why a config change broke a downstream process.
|
||||
|
||||
---
|
||||
|
||||
### Derek Ashford — Financial Controller
|
||||
Does not appear at team meetings. Does appear on CC lines of every email that
|
||||
mentions cloud costs, hardware procurement, or infrastructure budget. Always
|
||||
replies-all. His manager is Rachel Brandt (CFO).
|
||||
|
||||
Story use: background texture on procurement requests; the voice that makes any
|
||||
infrastructure spending feel like a negotiation.
|
||||
|
||||
> **Note on "Dave from Finance":** Marcus's day-one message references "Dave from
|
||||
> Finance" as the person holding the player's temp credentials. This is almost
|
||||
> certainly Derek Ashford — Marcus using his first name informally, or a
|
||||
> continuity error. Derek Ashford is the only Finance character plausibly holding
|
||||
> IT credentials. His first name is Derek, not Dave — either the message should
|
||||
> be corrected, or "Dave from Finance" is a third unnamed Finance employee.
|
||||
|
||||
---
|
||||
|
||||
### Rachel Huang — Systems Administrator
|
||||
Marcus's peer on the IT team. Handles provisioning, patch cycles, and the ongoing
|
||||
negotiation with Finance over cloud consolidation. Came from a managed services
|
||||
background. Has strong opinions about monitoring dashboards, most of which are
|
||||
correct.
|
||||
|
||||
Story use: the person who set something up that the player now has to maintain;
|
||||
a colleague who can provide context Marcus won't; someone whose provisioning
|
||||
decisions the player will encounter as infrastructure.
|
||||
|
||||
---
|
||||
|
||||
### Tom Malaney — Network Engineer
|
||||
Responsible for network infrastructure across the office and hosted environments.
|
||||
On-call for more holiday weekends than he would like. Thorough in documentation
|
||||
when he finds time for it.
|
||||
|
||||
Story use: DNS, firewall, or routing problems that are not the player's fault
|
||||
but become the player's problem; someone who can be reached but is slow to
|
||||
respond.
|
||||
|
||||
---
|
||||
|
||||
### James Osei — Security Analyst
|
||||
Priya's direct report. Handles vulnerability assessments, access reviews, and
|
||||
quarterly compliance reporting. Methodical. Has a spreadsheet for everything,
|
||||
which is not a criticism.
|
||||
|
||||
Story use: the person who runs the actual audit that Priya will summarize to the
|
||||
player; a source of detailed (sometimes overwhelming) security findings.
|
||||
|
||||
---
|
||||
|
||||
### Ellen Marsh — CEO & Co-Founder
|
||||
Built the first version of AxiomFlow after a decade in operations. No CS background.
|
||||
Attends all-hands twice a year. Does not use Slack. Has final say on pricing and
|
||||
major customer commitments.
|
||||
|
||||
Story use: the distant authority whose priorities shape everything; never interacts
|
||||
with the player directly, but her decisions land as constraints.
|
||||
|
||||
---
|
||||
|
||||
### David Park — CTO & Co-Founder
|
||||
Wrote the original rules engine in 2011. Now manages engineering managers. Still has
|
||||
opinions about the data model. Has a standing Thursday meeting with Priya that hasn't
|
||||
moved since 2017.
|
||||
|
||||
Story use: architectural decisions from above; the person Priya reports significant
|
||||
security findings to.
|
||||
|
||||
---
|
||||
|
||||
### Karen Volkov — COO
|
||||
Joined 2014. Responsible for the fact that the company has documented processes for
|
||||
anything at all. Has opinions about infrastructure costs that surface in IT's world
|
||||
via Finance. Prefers decisions with clear owners and deadlines.
|
||||
|
||||
---
|
||||
|
||||
### Rachel Brandt — CFO
|
||||
Joined 2016. Approves all capital expenditure over $5,000. Working to consolidate
|
||||
cloud spend. Does not enjoy surprises in the infrastructure budget. Derek Ashford
|
||||
reports to her.
|
||||
|
||||
---
|
||||
|
||||
### Mei Lin — Senior Software Engineer
|
||||
Has maintained AxiomSync's integration layer since 2018. Knows more about it than
|
||||
anyone would prefer, including herself. Currently leading the migration tooling
|
||||
project for the remaining legacy accounts.
|
||||
|
||||
---
|
||||
|
||||
### Cora Reyes — Software Engineer
|
||||
Works on the AxiomDash reporting pipeline. Has submitted more internal RFCs than
|
||||
anyone else on the team in the past year. Moving toward senior.
|
||||
|
||||
---
|
||||
|
||||
### Ben Portillo — Product Manager, AxiomDash
|
||||
Leads product development for the analytics add-on. Works closely with large
|
||||
accounts to understand what they actually want from dashboards (usually different
|
||||
from what they asked for).
|
||||
|
||||
---
|
||||
|
||||
### Annika Gosse — UX Designer
|
||||
Responsible for AxiomFlow's interface. Has been advocating for a redesign of the
|
||||
workflow builder since 2022. Patient.
|
||||
|
||||
---
|
||||
|
||||
### Sandra Wu — HR Manager
|
||||
Manages hiring, onboarding, and employee relations since 2016. Runs the new-hire
|
||||
onboarding process (three days, thorough). Sends birthday emails on time, every time.
|
||||
|
||||
---
|
||||
|
||||
### Owen Blake — Office Manager
|
||||
Keeps the office running. Has fixed more things than his job title implies. The
|
||||
person to contact if conference room equipment stops working.
|
||||
|
||||
---
|
||||
|
||||
### Mike Kawamoto — Account Executive
|
||||
Handles mid-market manufacturing accounts in the northeast. Believes strongly in
|
||||
the demo environment. Closes more deals in Q4 than any other quarter.
|
||||
|
||||
---
|
||||
|
||||
### Lisa Ferreira — Customer Success Manager
|
||||
Manages onboarding for new AxiomFlow deployments. Has a talent for understanding
|
||||
what customers mean rather than what they say.
|
||||
|
||||
---
|
||||
|
||||
## Unresolved Characters (Story Hooks)
|
||||
|
||||
These are referenced in existing content but never defined. They represent the
|
||||
strongest open narrative threads.
|
||||
|
||||
---
|
||||
|
||||
### Dale — The Previous Sysadmin
|
||||
**Reference:** Marcus's day-one message — "You're replacing Dale. Nobody will tell you
|
||||
what Dale did because it's complicated."
|
||||
|
||||
Dale is gone. The player has their desk, their access provisioning slot, and
|
||||
apparently their reputation — people know the player is "Dale's replacement" before
|
||||
they know the player's name. The systems the player inherits are the systems Dale
|
||||
last touched.
|
||||
|
||||
What Dale did is unknown. It is described as "complicated." Marcus knows. Possibly
|
||||
Kowalski knows. Possibly Priya knows, if it was security-related.
|
||||
|
||||
This is the strongest existing narrative mystery in the game. It has setup and no
|
||||
payoff. Dale's story could be:
|
||||
- A technical incident (something Dale broke and couldn't fix)
|
||||
- A policy violation (something Dale did that wasn't malicious but wasn't right)
|
||||
- A trust collapse (competent but burned bridges)
|
||||
- Something personal
|
||||
- Any combination
|
||||
|
||||
The player finding out what Dale did — gradually, through the systems they work on,
|
||||
through things people let slip — is a natural story spine for the whole game.
|
||||
|
||||
---
|
||||
|
||||
### "Dave from Finance" — Day One Reference
|
||||
**Reference:** Marcus's day-one message — "Dave from Finance has your temp credentials.
|
||||
He's on three today."
|
||||
|
||||
Almost certainly Derek Ashford (Financial Controller), referred to informally. But
|
||||
Derek's first name is Derek, not Dave — this is either Marcus being casual with
|
||||
names, a continuity error, or a genuinely separate unlisted Finance employee.
|
||||
|
||||
Needs a decision: correct "Dave" to "Derek" in Marcus's message, or introduce a
|
||||
separate "Dave from Finance" as a minor character.
|
||||
|
||||
---
|
||||
|
||||
## Key Relationships Map
|
||||
|
||||
```
|
||||
Ellen Marsh (CEO)
|
||||
└── David Park (CTO)
|
||||
└── Yusuf Halabi (Eng Manager)
|
||||
├── Mei Lin
|
||||
├── Cora Reyes
|
||||
└── Nikhil Sharma
|
||||
└── Karen Volkov (COO)
|
||||
└── Rachel Brandt (CFO)
|
||||
└── Derek Ashford (Financial Controller)
|
||||
└── Phil Ruiz (VP Sales)
|
||||
├── Mike Kawamoto
|
||||
└── Tanya Okafor
|
||||
└── Lisa Ferreira
|
||||
|
||||
Dave Kowalski (Director of IT)
|
||||
├── Marcus Webb ←── Player's manager
|
||||
│ └── [Player]
|
||||
├── Rachel Huang
|
||||
└── Tom Malaney
|
||||
|
||||
Priya Nair (Head of Security)
|
||||
└── James Osei
|
||||
|
||||
Sarah Chen (Product, AxiomFlow) ←── frequent ticket source
|
||||
Ben Portillo (Product, AxiomDash)
|
||||
Annika Gosse (UX)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tone Notes for New Story Work
|
||||
|
||||
- **Marcus talks like someone who has answered this question before.** Precise, low
|
||||
affect, no wasted words. Never condescending — just efficient.
|
||||
- **Sarah talks like a PM: outcome-focused, slightly impatient, specific about
|
||||
what she needs.** She is not a villain. She has real deadlines.
|
||||
- **Priya talks like someone who has already thought about what goes wrong.** She
|
||||
doesn't speculate — she states. She's not alarming, she's matter-of-fact.
|
||||
- **Dave Okonkwo talks like someone who doesn't know what the problem is** but is
|
||||
trying to be helpful by reporting exactly what he observed. He should never be
|
||||
made to look stupid — he's doing the right thing.
|
||||
- **The company takes itself seriously.** Humor comes from the gap between official
|
||||
language and reality, not from anyone being a cartoon.
|
||||
- **Problems have plausible causes.** Systems broke because someone made a
|
||||
reasonable decision under time pressure, not because they were careless idiots.
|
||||
The player should feel like a professional, not a janitor.
|
||||
@@ -0,0 +1,165 @@
|
||||
# Axiom Works — Company Lore Reference
|
||||
|
||||
> For quest authors, dialogue writers, and ticket copy. Keep the tone dry and
|
||||
> believable. The company should feel real, slightly dysfunctional, and just
|
||||
> plausible enough that players recognise the type.
|
||||
|
||||
---
|
||||
|
||||
## Who They Are
|
||||
|
||||
**Axiom Works** is a B2B enterprise software company founded in 2011. Headquarters
|
||||
is in a three-floor office park that is technically "downtown adjacent" depending
|
||||
on how charitable you are with the map. They have about 280 employees. The
|
||||
Glassdoor rating is 3.8 stars and management checks it obsessively.
|
||||
|
||||
Their flagship product is **AxiomFlow** — a workflow automation platform aimed at
|
||||
mid-size manufacturers, logistics companies, and anyone who got a 90-minute demo
|
||||
and thought it looked easy. Most customers are still on the workflow they set up
|
||||
in 2019. The platform does what it says. Marketing says it does considerably more.
|
||||
|
||||
---
|
||||
|
||||
## Products
|
||||
|
||||
| Product | Description | Status |
|
||||
|---------|-------------|--------|
|
||||
| **AxiomFlow** | Workflow automation platform | Active, main revenue |
|
||||
| **AxiomDash** | Reporting and analytics add-on | Active, profitable, under-resourced |
|
||||
| **AxiomSync** | Legacy data integration layer | End-of-sale since 2021, still maintained for 12 customers who refuse to migrate |
|
||||
|
||||
The current marketing tagline is *"Streamline. Scale. Succeed."* It replaced
|
||||
*"Work smarter, not harder"* in Q3 of last year. The one before that mentioned
|
||||
AI. Nobody is sure what the AI was.
|
||||
|
||||
---
|
||||
|
||||
## Infrastructure
|
||||
|
||||
The company runs a mix of on-prem servers (named after Greek gods — a choice made
|
||||
by a contractor in 2017 who left before documenting anything) and a handful of
|
||||
cloud instances that accounting keeps trying to consolidate.
|
||||
|
||||
| Host | Role | Notes |
|
||||
|------|------|-------|
|
||||
| **ares** | Player workstation | XFCE desktop, where the player works |
|
||||
| **hermes** | Web/app server | nginx, staging and demo environment for AxiomFlow |
|
||||
| **vulcan** | Build machine | Arch Linux, compiles artifacts, runs scheduled jobs |
|
||||
|
||||
### Planned future systems
|
||||
As the game grows, additional machines will be added. Candidates:
|
||||
|
||||
| Proposed host | Role | Greek connection |
|
||||
|---|---|---|
|
||||
| **poseidon** | Database server | Foundation, depths, reliability |
|
||||
| **apollo** | Mail / notification server | Messenger, communication |
|
||||
| **athena** | Internal tooling (ticketing, wiki) | Wisdom, knowledge management |
|
||||
| **argus** | Monitoring / alerting | The hundred-eyed watcher |
|
||||
| **mnemosyne** | Backup / storage | Memory, persistence |
|
||||
|
||||
---
|
||||
|
||||
## Characters
|
||||
|
||||
### Dave Kowalski — Director of IT Operations
|
||||
The player's skip-level manager. Has been at Axiom Works since 2015. Hired Marcus.
|
||||
Oversees three teams: systems (Marcus's domain), networking, and IT support. Background
|
||||
is originally networking — has Cisco certifications he won't bring up unless someone else
|
||||
brings up Cisco certifications first. Sends weekly status emails formatted in bullet
|
||||
points that never quite answer the question you were asking. When things go wrong he
|
||||
schedules a meeting to "talk through the situation," which everyone has learned is
|
||||
worse than an email. Maintains a calendar block from 2–3pm on Tuesdays that nobody
|
||||
has ever asked about. Has said "we should really document that" approximately 400 times.
|
||||
Describes the infrastructure as "mature."
|
||||
|
||||
### Marcus Webb — Senior Sysadmin
|
||||
The player's manager and the person who assigned them the ticket. Has been at
|
||||
Axiom Works for six years. Knows where all the bodies are buried. Communicates
|
||||
primarily in terse Slack messages and occasionally very long emails sent at 11pm.
|
||||
Trusts competence over process. Gets irritated by people who confuse symptoms
|
||||
with root causes.
|
||||
|
||||
### Priya Nair — Security / Compliance
|
||||
Runs security reviews and has opinions about everything. Usually right. Tends to
|
||||
frame concerns in terms of what will happen when things go wrong rather than
|
||||
whether they will. Was brought in after an incident nobody talks about in public.
|
||||
|
||||
### Sarah Chen — Product Manager
|
||||
Represents the product team's perspective in the ticket queue. Cares about demo
|
||||
environments more than production ones because demos are what she can see. Not
|
||||
technically wrong about their importance. Emails at 8am on Mondays.
|
||||
|
||||
### Derek Ashford — Financial Controller
|
||||
Does not appear in person. Appears on CC lines of emails where infrastructure
|
||||
costs are being discussed. Always replies-all. His full name is Derek Ashford.
|
||||
His manager is Rachel Brandt (CFO).
|
||||
|
||||
---
|
||||
|
||||
## Background Characters (non-interactive, for world texture)
|
||||
|
||||
These characters exist on the company website and in lore but do not appear in
|
||||
quests or dialogue. Use them for verisimilitude — email headers, CC lines, internal
|
||||
wiki author credits, that sort of thing.
|
||||
|
||||
### Ellen Marsh — CEO & Co-Founder
|
||||
Built AxiomFlow after a decade in operations. Not technical. Attends all-hands
|
||||
twice a year. Has final say on pricing and major customer commitments. Does not
|
||||
use Slack. The player will never interact with her.
|
||||
|
||||
### David Park — CTO & Co-Founder
|
||||
Wrote the original rules engine. Now manages engineering managers. Still has
|
||||
opinions about the data model. Has a standing Thursday meeting with security
|
||||
that hasn't moved since 2017.
|
||||
|
||||
### Karen Volkov — COO
|
||||
Joined 2014. Responsible for the fact that Axiom Works has documented processes
|
||||
for anything. Has opinions about infrastructure costs. Prefers decisions with
|
||||
clear owners and deadlines.
|
||||
|
||||
### Rachel Brandt — CFO
|
||||
Joined 2016. Approves all capital expenditure over $5,000. Does not enjoy
|
||||
surprises in the infrastructure budget. Derek reports to her.
|
||||
|
||||
### Phil Ruiz — VP of Sales
|
||||
Has been promising features to prospects since 2016. Has a warm relationship
|
||||
with the infrastructure team because Marcus once saved a demo with 20 minutes to
|
||||
spare. Expense reports submitted promptly.
|
||||
|
||||
### Tanya Okafor — Head of Customer Success
|
||||
Manages all post-sale customer relationships including the twelve AxiomSync
|
||||
holdouts. Usually the first to know when something is wrong in production,
|
||||
because a customer has already called her.
|
||||
|
||||
### Yusuf Halabi — Engineering Manager
|
||||
Reports to the CTO. Manages the core AxiomFlow platform team. Has opinions
|
||||
about test coverage. Runs the Thursday architecture review.
|
||||
|
||||
### Mei Lin — Senior Software Engineer
|
||||
Has maintained AxiomSync's integration layer since 2018. Knows more about it
|
||||
than anyone would prefer.
|
||||
|
||||
### Nikhil Sharma — Platform Engineer
|
||||
Owns the build and release pipeline and internal CI infrastructure. Occasionally
|
||||
sends Slack messages at 6am.
|
||||
|
||||
### Sandra Wu — HR Manager
|
||||
Manages hiring, onboarding, and employee relations since 2016. Sends birthday
|
||||
emails on time, every time. Runs the new-hire onboarding process that takes
|
||||
three days.
|
||||
|
||||
---
|
||||
|
||||
## Tone Guidelines
|
||||
|
||||
- **Dry, not sarcastic.** The company takes itself seriously. The humour comes
|
||||
from the gap between how they describe things and what's actually happening.
|
||||
- **Specific, not generic.** "The AxiomSync customer in Cincinnati keeps calling"
|
||||
is better than "a client is upset."
|
||||
- **Plausible dysfunction.** Problems happen because of reasonable decisions made
|
||||
under time pressure, not because people are incompetent. The player should feel
|
||||
like a real professional, not a janitor.
|
||||
- **No cartoon villains.** Derek from Finance is not evil. The product team is not
|
||||
stupid. They have different priorities.
|
||||
- **The infrastructure has history.** It was built over time. Some parts are good.
|
||||
Some parts were good in 2017. The player's job is to keep it working.
|
||||
@@ -0,0 +1,419 @@
|
||||
# Quest Authoring
|
||||
Use this guide when adding new JSON quests under `content/quests/`.
|
||||
|
||||
Quest files describe observed VM state. They are not command scripts and they
|
||||
should model real Linux behavior, not puzzle logic detached from the system.
|
||||
|
||||
For complete worked files, see [`docs/AUTHORING_EXAMPLES.md`](/home/aaron/Programming/sysadmin-chronicles/docs/AUTHORING_EXAMPLES.md).
|
||||
|
||||
## Quest JSON Schema
|
||||
|
||||
### Root Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `id` | string | Quest ID, for example `Q005`. |
|
||||
| `title` | string | Player-facing quest title. |
|
||||
| `tier` | int | Difficulty tier, usually `1`, `2`, or `3`. |
|
||||
| `primary_vm` | string | Main VM for the quest. Current authored values are `workstation`, `web_server`, and `build_machine`. |
|
||||
| `required_vms` | string[] | Every VM the quest touches. Include all VMs used in clues, validation, or prep. |
|
||||
| `ticket_id` | string | Links to `content/tickets/<id>.json`. |
|
||||
| `baseline_snapshot` | string | Snapshot name that the prep script should restore or build from. |
|
||||
| `summary` | string | Short internal scenario summary. |
|
||||
| `clue_fingerprint` | object | Advisory description of the evidence seeded into the baseline. |
|
||||
| `objectives` | object[] | Objective list shown to the player and used for progress checks. |
|
||||
| `solution_branches` | object[] | Branches the validator can resolve to. Higher-priority valid branches win. |
|
||||
| `pressure_profile` | string or null | Optional pressure/escalation profile name. |
|
||||
| `blast_radius` | string[] | Incident IDs that this quest can affect or trigger. |
|
||||
| `unlock_requirements` | string[] | Prerequisites such as `world_flag:` entries. |
|
||||
| `tags` | string[] | Search and classification tags. |
|
||||
| `internal_notes` | string | Author-only notes for reviewers. |
|
||||
| `_note` | string | Optional author-only comment. Existing content uses this at root and inside nested objects. |
|
||||
|
||||
### `clue_fingerprint`
|
||||
|
||||
`clue_fingerprint` is advisory. It documents what evidence the baseline already
|
||||
contains so content reviewers can confirm the clue trail is real.
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `description` | string | Plain-language explanation of the clue trail. |
|
||||
| `evidence` | object[] | Evidence items that point to the issue. Use the same general shape as the relevant validation type. |
|
||||
|
||||
Common evidence shapes in existing content:
|
||||
|
||||
- File and log evidence usually includes `type`, `vm`, `path`, and `contains`
|
||||
- State evidence may include `type`, `vm`, `service`, `state`, or `enabled`
|
||||
- Ownership evidence may include `type`, `vm`, `path`, `user`, and `group`
|
||||
- Scalar evidence may include `threshold_percent`, `port`, or `command` depending on the clue
|
||||
|
||||
Existing clue fingerprints also use clue-only labels such as `service_state_is`,
|
||||
`service_enabled_is`, and `expected_user`. Treat those as descriptive baseline
|
||||
metadata, not runtime validation names.
|
||||
|
||||
## Objectives
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `id` | string | Stable objective ID. |
|
||||
| `description` | string | Player-facing objective text. |
|
||||
| `check_mode` | string | `passive` or `explicit`. Use `passive` by default. |
|
||||
| `validation` | object | Rule object evaluated by `ValidationService`. |
|
||||
|
||||
Objectives are for feedback and progress tracking. They do not choose the
|
||||
winning solution branch.
|
||||
|
||||
## Solution Branches
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `id` | string | Stable branch ID. |
|
||||
| `label` | string | Optional short label used in content review and debugging. |
|
||||
| `priority` | int | Higher wins when multiple branches validate. Priorities must be unique per quest. |
|
||||
| `validation` | object | Rule object evaluated for this branch. |
|
||||
| `trust_delta` | float | Trust change applied when this branch wins. Positive for better fixes, negative for risky or damaging ones. |
|
||||
| `follow_up_dialogue` | string | Dialogue ID to trigger after resolution. |
|
||||
| `follow_up_incident` | string | Incident ID to trigger after resolution, if the branch intentionally leaves a latent problem. |
|
||||
| `follow_up_ticket` | string | Next ticket ID in the quest chain. |
|
||||
| `world_flags` | string[] | Flags to set when the branch wins. |
|
||||
| `_note` | string | Optional author-only comment. |
|
||||
|
||||
### Branch Authoring Guide
|
||||
|
||||
- Use branch priority to rank the quality of valid solutions.
|
||||
- Put the clean, robust fix at the highest priority.
|
||||
- Use lower priorities for brittle workarounds, partial fixes, or outcomes that
|
||||
leave future risk behind.
|
||||
- Use `trust_delta` to reflect the quality of the fix, not just whether the
|
||||
quest technically completed.
|
||||
- Use `follow_up_ticket` when a winning branch should advance the story to the
|
||||
next ticket.
|
||||
- Use `follow_up_incident` only when that branch intentionally seeds a later
|
||||
recurrence or operational cost.
|
||||
- Keep priorities unique. If two branches can both pass with the same priority,
|
||||
the content should be rewritten.
|
||||
|
||||
## Validation Rule Types
|
||||
|
||||
Design notes sometimes use shorthand names like `file_mode_matches` or
|
||||
`command_exits_zero`. In authored JSON, use the runtime rule names below.
|
||||
|
||||
- `file_mode_matches` -> `file_mode`
|
||||
- `file_owner_matches` -> `file_owner`
|
||||
- `service_state_matches` -> `service_state`
|
||||
- `service_is_enabled` -> `service_enabled`
|
||||
- `process_is_running` -> `process_running`
|
||||
- `port_is_listening` -> `port_listening`
|
||||
- `package_is_installed` -> `package_installed`
|
||||
- `command_exits_zero` -> `command_assert`
|
||||
|
||||
| JSON type | Fields | Notes |
|
||||
| --- | --- | --- |
|
||||
| `file_exists` | `vm`, `path` | Passes when the file exists. |
|
||||
| `file_absent` | `vm`, `path` | Inverse of `file_exists`. |
|
||||
| `directory_exists` | `vm`, `path` | Passes when the directory exists. |
|
||||
| `file_contains` | `vm`, `path`, `contains` | Passes when the file contains the given text. |
|
||||
| `log_contains` | `vm`, `path`, `contains` | Alias for `file_contains` used by some clue fingerprints. |
|
||||
| `file_mode` | `vm`, `path`, `mode` | Checks the exact file mode string, such as `0600`. |
|
||||
| `file_owner` | `vm`, `path`, `user`, `group` | Checks exact ownership. |
|
||||
| `file_owner_is_not` | `vm`, `path`, `user`, `group` | Negated ownership check. |
|
||||
| `service_state` | `vm`, `service`, `state` | Checks the active state, such as `active`, `inactive`, or `failed`. |
|
||||
| `service_enabled` | `vm`, `service`, `enabled` | Checks boot-time enablement. The `enabled` field defaults to `true`. |
|
||||
| `process_running` | `vm`, `process` | Passes when the named process is running. |
|
||||
| `process_user` | `vm`, `process`, `user` | Passes when the named process runs as the given user. |
|
||||
| `port_listening` | `vm`, `port`, `listening` | Checks whether a port is listening. The `listening` field defaults to `true`. |
|
||||
| `package_installed` | `vm`, `package` | Passes when the package is installed. |
|
||||
| `mount_present` | `vm`, `path` | Passes when the mount is present. |
|
||||
| `disk_usage_below` | `vm`, `path`, `threshold_percent` | Passes when disk usage is below the threshold. `percent` is accepted in older content. |
|
||||
| `disk_usage_above` | `vm`, `path`, `threshold_percent` | Passes when disk usage is above the threshold. `percent` is accepted in older content. |
|
||||
| `command_assert` | `vm`, `command` | Fallback rule for command-based checks. Use sparingly. |
|
||||
| `and` | `rules` | All sub-rules must pass. |
|
||||
| `or` | `rules` | Any sub-rule may pass. |
|
||||
| `not` | `rule` | Inverts the inner rule. |
|
||||
|
||||
### Validation Notes
|
||||
|
||||
- Prefer state-based checks over command checks.
|
||||
- Use `and` and `or` to model genuinely alternative states, not to hide weak
|
||||
authoring.
|
||||
- `command_assert` is a fallback. If a real state rule exists, use that first.
|
||||
- Some older quest files include extra fields such as `protocol` or
|
||||
`installed`. The loader ignores unknown keys, but new quests should stick to
|
||||
the documented fields above.
|
||||
|
||||
## Prep Script Requirements
|
||||
|
||||
Each quest needs a prep script at `tools/vm/quest-prep/QXXX-prep.sh`.
|
||||
|
||||
- The script must be idempotent.
|
||||
- It must set up the starting VM state for the quest.
|
||||
- It runs at image build time, not when the player starts the quest.
|
||||
- It should install required packages only from local or pre-baked sources.
|
||||
- It may create logs, users, groups, permissions, or broken config files that
|
||||
form the scenario.
|
||||
- It must not rely on a live player session.
|
||||
|
||||
When a quest continues an existing chain, the prep script should restore the
|
||||
prior clean snapshot first, then apply the new scenario changes, and finally
|
||||
take the next baseline snapshot.
|
||||
|
||||
## VM Provisioning Pipeline
|
||||
|
||||
A new quest requires a VM baseline before it can be played. The full authoring
|
||||
workflow from scratch to playable quest:
|
||||
|
||||
### 1. Write the prep script
|
||||
|
||||
Create `tools/vm/quest-prep/QXXX-prep.sh`. Requirements:
|
||||
|
||||
- Must be idempotent — safe to run twice on the same domain.
|
||||
- Accepts the domain name as $1 and an optional `--dry-run` flag as $2.
|
||||
- Must not prompt for input or depend on internet access.
|
||||
- Reads `tools/vm/lib/common.sh` for shared helpers (`run`, `step`, `ok`, etc.).
|
||||
|
||||
Typical operations: break a config file, chown a directory, remove a logrotate
|
||||
config, add a cron entry, delete a key. Nothing that would be undone by the
|
||||
player before the quest starts.
|
||||
|
||||
### 2. Register the quest in seed-vms.sh
|
||||
|
||||
Open `tools/setup/seed-vms.sh` and:
|
||||
|
||||
1. Add a `require_file` check near the top (`STEP 1 — Pre-flight checks`):
|
||||
```bash
|
||||
require_file "$QUEST_PREP/QXXX-prep.sh" "QXXX prep script"
|
||||
```
|
||||
|
||||
2. Add a `run_prep_and_snapshot` call in `STEP 4 — Run quest-prep scripts`:
|
||||
```bash
|
||||
run_prep_and_snapshot "QXXX" "sc-<vm-domain>" "baseline.<snapshot-name>"
|
||||
```
|
||||
The snapshot name must match the quest's `baseline_snapshot` field.
|
||||
|
||||
### 3. Baseline snapshot chain
|
||||
|
||||
Each VM has its own chain. Only the CLEAN branch resolution of a quest is used
|
||||
as the baseline for the next quest. Brittle-branch resolutions are never
|
||||
snapshotted.
|
||||
|
||||
| VM | Snapshot chain |
|
||||
|----|----------------|
|
||||
| `sc-workstation` | `baseline.day-one` (Q001 only) |
|
||||
| `sc-web-server` | `baseline.clean` → `baseline.post-q002` → `baseline.post-q003` → `baseline.post-q004` |
|
||||
| `sc-build-machine` | `baseline.clean` → `baseline.post-q006` |
|
||||
|
||||
A prep script that builds on a prior quest must revert to the prior snapshot
|
||||
before applying its changes.
|
||||
|
||||
### 4. VM baseline package set
|
||||
|
||||
Each authored VM has a guaranteed minimum set of packages that players can rely on
|
||||
during gameplay. New quests must not assume packages outside this set unless the
|
||||
quest prep script installs them.
|
||||
|
||||
| VM | OS | Guaranteed packages |
|
||||
|----|----|---------------------|
|
||||
| `sc-workstation` (ares) | Ubuntu 24.04 | `qemu-guest-agent`, `openssh-server`, `sudo`, `bash-completion`, `hostname`, `ssh` client (system) |
|
||||
| `sc-web-server` (hermes) | Debian 12 | `qemu-guest-agent`, `openssh-server`, `sudo`, `nginx`, `logrotate`, `rsync`, `curl`, `hostname`, `ssh` client |
|
||||
| `sc-build-machine` (vulcan) | Arch Linux | `qemu-guest-agent`, `openssh`, `sudo`, `base-devel`, `archlinux-keyring`, `inetutils` (provides `hostname`, `ping`), `ssh` client |
|
||||
|
||||
`hostname`, `whoami`, `id`, `ls`, `cat`, `echo`, `ps`, `df`, `du`, `free`,
|
||||
`systemctl`, `journalctl` are available on all VMs.
|
||||
|
||||
The in-game terminal auto-adds `-C` to bare `ls` calls so column output renders
|
||||
correctly. If a quest step requires `ls -l` or another explicit format, pass it
|
||||
explicitly — the auto-`-C` injection only fires when no layout flag is present.
|
||||
|
||||
### 5. Run the pipeline
|
||||
|
||||
```bash
|
||||
# Dry run first — shows what would execute without touching VMs
|
||||
bash tools/setup/seed-vms.sh --dry-run
|
||||
|
||||
# Full build — requires libvirt and all three sc-* domains to exist
|
||||
bash tools/setup/seed-vms.sh
|
||||
|
||||
# Prep + snapshot only (skip the image build step)
|
||||
bash tools/setup/seed-vms.sh --skip-build
|
||||
|
||||
# Single VM only
|
||||
bash tools/setup/seed-vms.sh --vm web_server
|
||||
```
|
||||
|
||||
### 5. Validate
|
||||
|
||||
After seed-vms.sh completes:
|
||||
|
||||
```bash
|
||||
# Check content integrity (including baseline_snapshot field)
|
||||
node tools/content/validate-content.js
|
||||
|
||||
# Verify snapshots exist on each domain
|
||||
virsh snapshot-list sc-web-server
|
||||
virsh snapshot-list sc-build-machine
|
||||
```
|
||||
|
||||
## Multi-Solution Quest Example
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "Q099",
|
||||
"title": "Cron Runs as Root",
|
||||
"tier": 2,
|
||||
"primary_vm": "web_server",
|
||||
"required_vms": ["web_server"],
|
||||
"ticket_id": "T099",
|
||||
"baseline_snapshot": "baseline.clean",
|
||||
"_note": "Minimal example: the nightly cron job should run as www-data, not root.",
|
||||
"summary": "A site-sync cron entry was copied from a root shell. It still runs, but it now leaves root-owned cache files behind.",
|
||||
"clue_fingerprint": {
|
||||
"description": "The cron file exists, but it names root as the executor. The cache directory is already polluted with root-owned files.",
|
||||
"evidence": [
|
||||
{ "type": "file_contains", "vm": "web_server", "path": "/etc/cron.d/site-sync", "contains": "root /opt/site-sync/bin/sync-cache.sh" },
|
||||
{ "type": "file_owner_is_not", "vm": "web_server", "path": "/var/www/axiomworks/cache", "user": "www-data" }
|
||||
]
|
||||
},
|
||||
"objectives": [
|
||||
{
|
||||
"id": "sync-safe",
|
||||
"description": "The cron job runs as www-data and the scheduler is active",
|
||||
"check_mode": "passive",
|
||||
"validation": {
|
||||
"type": "and",
|
||||
"rules": [
|
||||
{ "type": "file_contains", "vm": "web_server", "path": "/etc/cron.d/site-sync", "contains": "www-data /opt/site-sync/bin/sync-cache.sh" },
|
||||
{
|
||||
"type": "or",
|
||||
"rules": [
|
||||
{ "type": "command_assert", "vm": "web_server", "command": "systemctl is-active --quiet cron" },
|
||||
{ "type": "command_assert", "vm": "web_server", "command": "pgrep -x cron >/dev/null" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"solution_branches": [
|
||||
{
|
||||
"id": "correct-cron",
|
||||
"label": "Correct Cron User",
|
||||
"priority": 100,
|
||||
"validation": {
|
||||
"type": "and",
|
||||
"rules": [
|
||||
{ "type": "file_contains", "vm": "web_server", "path": "/etc/cron.d/site-sync", "contains": "www-data /opt/site-sync/bin/sync-cache.sh" },
|
||||
{
|
||||
"type": "or",
|
||||
"rules": [
|
||||
{ "type": "command_assert", "vm": "web_server", "command": "systemctl is-active --quiet cron" },
|
||||
{ "type": "command_assert", "vm": "web_server", "command": "pgrep -x cron >/dev/null" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"trust_delta": 2,
|
||||
"world_flags": ["site_sync_healthy"],
|
||||
"follow_up_dialogue": "marcus-Q099-complete-clean",
|
||||
"follow_up_ticket": "T100",
|
||||
"_note": "Preferred fix: keep the job and run it with the correct user."
|
||||
},
|
||||
{
|
||||
"id": "disabled-cron",
|
||||
"label": "Brittle Disable",
|
||||
"priority": 40,
|
||||
"validation": {
|
||||
"type": "command_assert",
|
||||
"vm": "web_server",
|
||||
"command": "test ! -f /etc/cron.d/site-sync"
|
||||
},
|
||||
"trust_delta": -1,
|
||||
"world_flags": ["site_sync_brittle"],
|
||||
"follow_up_dialogue": "marcus-Q099-complete-brittle",
|
||||
"_note": "The job was deleted instead of repaired. It stops the symptom, but it is not a durable fix."
|
||||
}
|
||||
],
|
||||
"pressure_profile": null,
|
||||
"blast_radius": [],
|
||||
"unlock_requirements": ["world_flag:player_ssh_configured"],
|
||||
"tags": ["cron", "permissions", "web_server"],
|
||||
"internal_notes": "Example only."
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-VM Quest Example
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "Q098",
|
||||
"title": "Build Sync Writes Bad Ownership",
|
||||
"tier": 2,
|
||||
"primary_vm": "build_machine",
|
||||
"required_vms": ["workstation", "build_machine", "web_server"],
|
||||
"ticket_id": "T098",
|
||||
"baseline_snapshot": "baseline.post-q006",
|
||||
"_note": "The build machine is pushing release files to the web server, but the ownership is wrong and the deploy helper is still running.",
|
||||
"summary": "A deployment helper on the build machine is writing release files to the web server with root ownership. The helper must be stopped and the output repaired so the web server can manage the files again.",
|
||||
"clue_fingerprint": {
|
||||
"description": "The deploy helper is still running on build_machine. On web_server, the release artifact is owned by root instead of www-data.",
|
||||
"evidence": [
|
||||
{ "type": "file_contains", "vm": "build_machine", "path": "/opt/deploy/bin/push-release.sh", "contains": "rsync -a --chown=root:root" },
|
||||
{ "type": "process_running", "vm": "build_machine", "process": "deploy-sync" },
|
||||
{ "type": "file_owner_is_not", "vm": "web_server", "path": "/var/www/axiomworks/releases/current/index.html", "user": "www-data", "group": "www-data" }
|
||||
]
|
||||
},
|
||||
"objectives": [
|
||||
{
|
||||
"id": "release-owned-correctly",
|
||||
"description": "The web release file is owned by www-data and the deploy helper is stopped",
|
||||
"check_mode": "passive",
|
||||
"validation": {
|
||||
"type": "and",
|
||||
"rules": [
|
||||
{ "type": "file_owner", "vm": "web_server", "path": "/var/www/axiomworks/releases/current/index.html", "user": "www-data", "group": "www-data" },
|
||||
{ "type": "not", "rule": { "type": "process_running", "vm": "build_machine", "process": "deploy-sync" } }
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"solution_branches": [
|
||||
{
|
||||
"id": "deploy-stopped-owner-fixed",
|
||||
"label": "Stop Helper and Fix Ownership",
|
||||
"priority": 100,
|
||||
"validation": {
|
||||
"type": "and",
|
||||
"rules": [
|
||||
{ "type": "file_owner", "vm": "web_server", "path": "/var/www/axiomworks/releases/current/index.html", "user": "www-data", "group": "www-data" },
|
||||
{ "type": "not", "rule": { "type": "process_running", "vm": "build_machine", "process": "deploy-sync" } }
|
||||
]
|
||||
},
|
||||
"trust_delta": 2,
|
||||
"world_flags": ["release_permissions_fixed"],
|
||||
"follow_up_dialogue": "marcus-Q098-complete-clean",
|
||||
"_note": "This branch validates both VMs: the release file is fixed on web_server and the helper is no longer running on build_machine."
|
||||
}
|
||||
],
|
||||
"pressure_profile": null,
|
||||
"blast_radius": [],
|
||||
"unlock_requirements": ["world_flag:player_ssh_configured"],
|
||||
"tags": ["deploy", "permissions", "multi-vm", "build_machine", "web_server"],
|
||||
"internal_notes": "Example only."
|
||||
}
|
||||
```
|
||||
|
||||
## Quest Chain Authoring
|
||||
|
||||
Use `follow_up_ticket` to chain the campaign in sequence. The winning branch
|
||||
emits the next ticket, and `QuestDirector` activates the next quest from that
|
||||
ticket.
|
||||
|
||||
| Quest | Clean branch `follow_up_ticket` |
|
||||
| --- | --- |
|
||||
| `Q001` | `T002` |
|
||||
| `Q002` | `T003` |
|
||||
| `Q003` | `T004` |
|
||||
| `Q004` | `T005` |
|
||||
|
||||
Keep the chain on the clean, high-priority branch. If a brittle branch should
|
||||
continue the story differently, use its own `follow_up_ticket` or
|
||||
`follow_up_incident` intentionally.
|
||||
@@ -0,0 +1,161 @@
|
||||
# Sysadmin Chronicles — Spec Lock
|
||||
|
||||
This file preserves the user's intended new system design. Treat it as binding.
|
||||
|
||||
## 1. Narrative spine
|
||||
|
||||
The story progression is:
|
||||
|
||||
```text
|
||||
Normal Work → Unease → Suspicion → Investigation → Conflict → Resolution
|
||||
```
|
||||
|
||||
Every quest must map to one of these phases.
|
||||
|
||||
## 2. Required quest structure
|
||||
|
||||
Every proposed quest must include:
|
||||
|
||||
- Title
|
||||
- Narrative Phase
|
||||
- Objective
|
||||
- Linux Concepts
|
||||
- Systems Used
|
||||
- Hidden Hook (optional)
|
||||
- Failure Conditions
|
||||
- Behavior Impact
|
||||
|
||||
For implementation, these may be expanded into JSON fields, but these concepts must remain present.
|
||||
|
||||
## 3. Core systems
|
||||
|
||||
### 3.1 Player behavior tracking
|
||||
|
||||
Track:
|
||||
|
||||
- `curiosity` — exploration, anomaly investigation, reading beyond ticket scope
|
||||
- `obedience` — completing assigned work, following stated priorities, ignoring suspicious extras
|
||||
- `risk` — reckless changes, broad permissions, deleting evidence, unsafe shortcuts
|
||||
|
||||
These influence:
|
||||
|
||||
- Access levels
|
||||
- Narrative progression
|
||||
- Endings
|
||||
|
||||
### 3.2 Trust and suspicion compatibility
|
||||
|
||||
The existing system already uses `trust_delta`, world flags, and branch quality. Preserve that.
|
||||
|
||||
Map old and new systems like this:
|
||||
|
||||
- `trust` = professional standing produced mostly by solution quality and branch outcomes
|
||||
- `suspicion` = management/security attention caused by investigative, risky, or unusual behavior
|
||||
- `curiosity`, `obedience`, `risk` = the new behavior profile controlling narrative route
|
||||
|
||||
Do not replace trust. Extend it.
|
||||
|
||||
### 3.3 Access system
|
||||
|
||||
Player permissions evolve:
|
||||
|
||||
```text
|
||||
basic_user → sudo → root
|
||||
```
|
||||
|
||||
Access is affected by:
|
||||
|
||||
- Trust from competent task completion
|
||||
- Suspicion from investigation behavior
|
||||
- Risk from careless or destructive changes
|
||||
- Narrative phase
|
||||
|
||||
### 3.4 Boss system / management pressure
|
||||
|
||||
The boss system acts as a dynamic constraint, not a cutscene machine.
|
||||
|
||||
Phase scaling:
|
||||
|
||||
- Phase 1: Annoying
|
||||
- Phase 2: Dismissive
|
||||
- Phase 3: Suspicious
|
||||
- Phase 4: Monitoring
|
||||
- Phase 5: Interfering
|
||||
- Phase 6: Outcome-dependent
|
||||
|
||||
Functions:
|
||||
|
||||
- Interrupt tasks
|
||||
- Reassign priorities
|
||||
- Restrict access
|
||||
- Add pressure through tickets, emails, delayed approvals, audits, or access review
|
||||
|
||||
In the current company context, this can be represented by Marcus, Kowalski, Priya, or policy pressure depending on the situation. Do not turn one character into a cartoon villain.
|
||||
|
||||
### 3.5 Hidden narrative system
|
||||
|
||||
Hidden hooks are embedded in normal quests.
|
||||
|
||||
Examples:
|
||||
|
||||
- Unknown services
|
||||
- Suspicious cron jobs
|
||||
- Hidden users
|
||||
- Network anomalies
|
||||
- Unexpected SSH keys
|
||||
- Odd timestamps
|
||||
- Config history that does not match the official story
|
||||
|
||||
Rules:
|
||||
|
||||
- Never explicitly flagged
|
||||
- Optional discovery only
|
||||
- Not required to complete the assigned ticket
|
||||
- Must be discoverable through real sysadmin behavior
|
||||
- Should accumulate into a coherent hidden story over time
|
||||
|
||||
## 4. Quest generation constraints
|
||||
|
||||
- Reuse existing game systems
|
||||
- Do not introduce unnecessary mechanics
|
||||
- Scale difficulty with player progression
|
||||
- Preserve the observed-VM-state design from existing quest authoring
|
||||
- Prefer real Linux behavior over puzzle logic
|
||||
|
||||
## 5. Difficulty scaling
|
||||
|
||||
- Phase 1: Explicit instructions
|
||||
- Phase 2: Partial hints
|
||||
- Phase 3: Minimal guidance
|
||||
- Phase 4+: Problem-solving only
|
||||
|
||||
This applies to ticket wording, hints, clue obviousness, and branch tolerance.
|
||||
|
||||
## 6. Endings
|
||||
|
||||
Endings are determined by behavior over the playthrough:
|
||||
|
||||
- `corporate_loop` — obedient path / bad ending
|
||||
- `burnout` — passive path / neutral ending
|
||||
- `exposure` — investigative path / good ending
|
||||
- `chaos` — destructive/high-risk path
|
||||
|
||||
No ending should be selected by a single obvious final button. The route should emerge from world flags, behavior variables, access state, and discovered/acted-on hidden hooks.
|
||||
|
||||
## 7. Design principles
|
||||
|
||||
- Discovery over exposition
|
||||
- Systems over scripts
|
||||
- Freedom over forced narrative
|
||||
- Realism with subtle distortion
|
||||
|
||||
## 8. Non-goals
|
||||
|
||||
Do not:
|
||||
|
||||
- Build a linear-only story
|
||||
- Rely on cutscenes
|
||||
- Over-explain mechanics
|
||||
- Remove player agency
|
||||
- Turn the mystery into explicit quest markers
|
||||
- Rewrite established characters to fit a new plot
|
||||
@@ -0,0 +1,423 @@
|
||||
# Story Design Context — Sysadmin Chronicles
|
||||
|
||||
For story designers and AI agents creating new quests and narrative content.
|
||||
|
||||
**Related docs:**
|
||||
- `CHARACTERS.md` — character bios, relationships, story hooks
|
||||
- `COMPANY_LORE.md` — world, company, tone
|
||||
- `QUEST_AUTHORING.md` — technical JSON spec for implementers
|
||||
|
||||
This document answers: *how does story actually work in this game, and what does a quest
|
||||
concept need to contain to be usable?*
|
||||
|
||||
---
|
||||
|
||||
## The Core Premise
|
||||
|
||||
The player is a new junior sysadmin at Axiom Works, a mid-size B2B software company.
|
||||
They are replacing someone named Dale. Nobody will explain why Dale is gone.
|
||||
|
||||
The game is played entirely through a simulated work environment: a terminal, an email
|
||||
inbox, and a company website. There are no cutscenes, no narration, no inventory, no
|
||||
combat. Everything that happens is expressed through:
|
||||
|
||||
- **Tickets** — the player receives a ticket describing a problem
|
||||
- **The terminal** — the player SSHes into VMs, investigates, and fixes things
|
||||
- **Character dialogue** — characters react to how the player solved the problem
|
||||
- **The next ticket** — the world moves on, and the consequences of what the player
|
||||
did are baked into the next situation
|
||||
|
||||
That's it. Story is not told — it is accumulated from the choices the player makes
|
||||
when fixing real Linux problems on real virtual machines.
|
||||
|
||||
---
|
||||
|
||||
## The Three Machines (VMs)
|
||||
|
||||
Every quest happens on one or more of these machines. Their narrative identities
|
||||
matter as much as their technical roles.
|
||||
|
||||
### ares — the Workstation
|
||||
The player's home machine. Ubuntu 24.04. Quests here are onboarding-flavored —
|
||||
establishing access, learning the environment. It's the only machine the player
|
||||
can reach on day one.
|
||||
|
||||
*Narrative identity:* Where you start. Safe-ish. The first one you break is here.
|
||||
|
||||
### hermes — the Web / App Server
|
||||
Debian 12. Runs nginx and the AxiomFlow demo/staging application. This is the
|
||||
machine that Sarah Chen cares about, that customers can feel, and that Priya Nair
|
||||
watches for security posture. Most of the early-game quests are here.
|
||||
|
||||
*Narrative identity:* The product's face to the world. Breaking this makes noise
|
||||
immediately. The most politically visible machine.
|
||||
|
||||
### vulcan — the Build Machine
|
||||
Arch Linux. Compiles packages, runs the internal build pipeline, serves packages
|
||||
to hermes via an internal apt repo. Nikhil Sharma owns this in principle but nobody
|
||||
manages it daily. Things here break silently until hermes starts serving bad software.
|
||||
|
||||
*Narrative identity:* The machine nobody watches until something downstream fails.
|
||||
Quests here reveal that problems have upstream causes the player didn't expect.
|
||||
|
||||
### Planned future machines
|
||||
As the story expands, new machines can be added. Each should have a clear narrative
|
||||
role before it's introduced. (See `COMPANY_LORE.md` for the candidate list.)
|
||||
|
||||
---
|
||||
|
||||
## How Story Is Delivered
|
||||
|
||||
### Tickets as Act One
|
||||
Every quest begins with a ticket in the player's inbox. The ticket is a short email
|
||||
from a character describing a symptom — not a cause. The sender's perception of the
|
||||
problem is usually incomplete and sometimes wrong. This is intentional: the player's
|
||||
job is to investigate, not to execute instructions.
|
||||
|
||||
Good ticket writing:
|
||||
- Describes what the sender experienced, not what the cause is
|
||||
- Has the sender's voice and perspective (Sarah is outcome-focused; Dave is confused;
|
||||
Priya is terse and specific)
|
||||
- Does not hint at the solution
|
||||
- Creates genuine stakes (site is down, builds are failing, someone is locked out)
|
||||
|
||||
Bad ticket writing:
|
||||
- Explains the root cause ("the log file is too big")
|
||||
- Has no character voice (generic IT help desk language)
|
||||
- Stakes are unclear or low
|
||||
|
||||
### The Terminal as Act Two
|
||||
The player investigates. They SSH in, run commands, read logs, check configs, look at
|
||||
file ownership. The evidence is seeded into the VM baseline — it is genuinely there
|
||||
to find, not procedurally generated. A good quest has a natural clue trail:
|
||||
|
||||
- The most obvious thing points to a second thing
|
||||
- The second thing reveals the actual problem
|
||||
- The fix is achievable with real Linux knowledge
|
||||
|
||||
The player cannot be told what to do. They can ask Marcus for hints (via dialogue
|
||||
choices), but good players don't need to.
|
||||
|
||||
### Branching Resolution as Act Three
|
||||
When the player has made changes to the VM, the game checks the state of the
|
||||
system against the quest's solution branches. The branch that matches determines:
|
||||
|
||||
- What dialogue fires (Marcus's reaction, Sarah's reaction, Priya's follow-up)
|
||||
- What trust delta the player receives
|
||||
- What world flag is set (persistent story state)
|
||||
- Whether an incident is triggered (a future consequence of a partial fix)
|
||||
- What ticket comes next
|
||||
|
||||
**This is the central story mechanic.** Every quest should be designed with at
|
||||
least two and ideally three resolution branches:
|
||||
|
||||
| Branch type | What it means |
|
||||
|-------------|---------------|
|
||||
| **Clean fix** | Player understood the root cause and solved it properly. High trust, no downstream risk. |
|
||||
| **Acceptable fix** | Problem is solved but with a tradeoff — brittle approach, future maintenance burden, or incomplete cleanup. Lower trust. |
|
||||
| **Regression** | Player fixed the symptom but made something else worse. Negative trust. Story consequences. |
|
||||
|
||||
The **regression branch** is not about punishment — it's about realism. A real
|
||||
sysadmin who removes all SSH restrictions to restore one person's access has
|
||||
technically solved the ticket while creating a larger problem. The story should
|
||||
treat this as realistic professional consequence, not a game-over failure.
|
||||
|
||||
Players on a clean-fix path get more trust, unlock more access, and receive warmer
|
||||
character reactions. Players on a regression path continue playing but face the
|
||||
downstream effects of their choices.
|
||||
|
||||
---
|
||||
|
||||
## World Flags — Persistent Story State
|
||||
|
||||
World flags are string keys set when a quest's branch resolves. They persist for
|
||||
the entire playthrough and can be read by later quests, incidents, and dialogue.
|
||||
|
||||
Examples:
|
||||
- `hermes_logrotate_healthy` — set when the player properly fixed log rotation
|
||||
- `hermes_ssh_allowusers_fragile` — set when the player restored SSH access using
|
||||
the brittle AllowUsers approach instead of the robust AllowGroups approach
|
||||
- `player_ssh_configured` — set when the player successfully set up SSH on day one
|
||||
|
||||
World flags are how story continuity works. A later quest can check whether the
|
||||
player fixed something correctly earlier and behave differently. Marcus can reference
|
||||
a past fix. Priya can flag a previously introduced risk in a later audit. A problem
|
||||
that was "solved" with a quick fix can recur.
|
||||
|
||||
**When designing a new quest, ask:** what flag should this set, and what future quests
|
||||
or dialogue might reference it?
|
||||
|
||||
---
|
||||
|
||||
## Trust — The Narrative Currency
|
||||
|
||||
Trust is a numeric score that tracks the player's professional standing with Marcus
|
||||
and the IT team. It affects:
|
||||
|
||||
- **VM access** — the player gains SSH access to hermes and vulcan as trust increases.
|
||||
If trust drops badly, access can be revoked.
|
||||
- **Documentation access** — more trusted players get access to internal runbooks
|
||||
and admin guides
|
||||
- **Character warmth** — Marcus's messages change tone subtly as trust grows
|
||||
- **Incident visibility** — at a certain trust level, the player starts seeing
|
||||
background incidents before they become critical
|
||||
|
||||
Trust is not displayed as a raw number. Players experience it as consequences.
|
||||
|
||||
**For quest designers:** each branch should have a `trust_delta` that reflects the
|
||||
quality of the fix. A proper root-cause fix should earn more than a workaround.
|
||||
Regression branches should cost trust. Day-one onboarding quests are lenient;
|
||||
later quests at higher tiers should be less forgiving.
|
||||
|
||||
---
|
||||
|
||||
## Incidents — Consequences of Incomplete Fixes
|
||||
|
||||
An incident is a time-delayed consequence that fires when a quest's partial-fix
|
||||
branch was taken. It represents the problem coming back.
|
||||
|
||||
Example: The player clears a full disk by deleting a log file but doesn't restore
|
||||
the logrotate config. Two in-game hours later, the disk starts filling again. Dave
|
||||
notices. The player gets another ticket about the same symptom.
|
||||
|
||||
Incidents are not punishments — they are realistic. The world doesn't stay fixed
|
||||
just because the player touched it. A player who takes clean-fix branches will
|
||||
rarely see incidents. A player who takes every shortcut will find their ticket queue
|
||||
filling up with problems they already "solved."
|
||||
|
||||
For story purposes: incidents can also carry narrative weight. If the player made a
|
||||
security regression, an incident could represent an audit finding, an unusual login,
|
||||
or a configuration discrepancy Priya noticed.
|
||||
|
||||
---
|
||||
|
||||
## The Character Conversation Model
|
||||
|
||||
Quest dialogue fires after a branch resolves. Three characters can speak:
|
||||
|
||||
### Marcus Webb
|
||||
The primary voice. Appears in every quest. His post-resolution message reflects:
|
||||
- What the player actually did (not just whether they succeeded)
|
||||
- Whether they understood the root cause or just cleared the symptom
|
||||
- A forward-looking observation (usually a quiet flag for what's coming next)
|
||||
|
||||
Marcus does not praise effusively or scold dramatically. He states what he observed.
|
||||
His message for a clean fix is warmer and sometimes wry. His message for a regression
|
||||
is brief and pointed. He never says "well done!" He might say "that's the right call."
|
||||
|
||||
### Sarah Chen
|
||||
Speaks when the quest affects something product-facing (hermes being up or down,
|
||||
deploys working or failing). Her messages are reactive — she responds to outcomes,
|
||||
not process. She is not hostile unless the player makes her situation worse.
|
||||
|
||||
### Priya Nair
|
||||
Speaks when the quest has security implications — access changes, hardening,
|
||||
audit posture. She does end-of-shift reviews that grade overall performance.
|
||||
Her per-quest messages are brief and evaluative. She notices things Marcus might not.
|
||||
|
||||
### Other characters
|
||||
Dave Okonkwo files tickets. He does not have post-resolution dialogue — he
|
||||
just stops or starts noticing things. Future characters (Kowalski, Nikhil, Tanya)
|
||||
can speak in dialogue if quests are designed to involve them.
|
||||
|
||||
---
|
||||
|
||||
## The Narrative Arc
|
||||
|
||||
The overall story has six phases. Quests should be designed with their phase in mind.
|
||||
The phase is usually not visible to the player — it emerges from what's happening
|
||||
around them.
|
||||
|
||||
### Phase 1 — Normal Work
|
||||
*Tier 1 quests. Early game.*
|
||||
|
||||
The player is new. Everything is routine. Marcus is helpful. The problems are real
|
||||
but not alarming — a broken config, a full disk, a permission issue. The player is
|
||||
learning the environment. The subtext is that things are slightly more wrong than
|
||||
they should be, but there's nothing to point at.
|
||||
|
||||
Hidden layer: small anomalies in the systems that curious players can notice but
|
||||
don't have context for yet.
|
||||
|
||||
### Phase 2 — Unease
|
||||
*Tier 1/2 transition.*
|
||||
|
||||
The problems start to have patterns. The same kind of thing breaks twice. A fix
|
||||
the player made doesn't hold the way it should. Nothing is alarming, but Marcus's
|
||||
messages have a slightly different quality — he notices things he doesn't explain.
|
||||
|
||||
Hidden layer: a world flag from an early quest points somewhere unexpected.
|
||||
|
||||
### Phase 3 — Suspicion
|
||||
*Tier 2 quests. Mid game.*
|
||||
|
||||
The player starts encountering problems they didn't cause and can't fully explain.
|
||||
Access was changed by someone. A config was edited recently. A log shows an
|
||||
unusual pattern. Nobody is accusing anyone. But the player now has enough context
|
||||
to start asking questions — even if no quest explicitly tells them to.
|
||||
|
||||
This is where Dale becomes relevant again. The systems the player inherits were
|
||||
last touched by Dale. Some of them have been in a particular state for a long time.
|
||||
|
||||
### Phase 4 — Investigation
|
||||
*Tier 2/3 transition.*
|
||||
|
||||
The player has connected enough dots to understand that something happened before
|
||||
they arrived. The quests in this phase involve digging into logs, access records,
|
||||
and configuration history. The investigation is framed as professional work
|
||||
(audit the access logs, trace the package build history) — but the results tell
|
||||
a story.
|
||||
|
||||
Marcus's messages are shorter. Priya starts appearing more. Kowalski schedules a
|
||||
meeting nobody explains.
|
||||
|
||||
### Phase 5 — Conflict
|
||||
*Tier 3 quests. Late game.*
|
||||
|
||||
The player knows what happened. Acting on that knowledge has professional
|
||||
consequences. The conflict is not physical — it is about what the player chooses
|
||||
to surface, who they tell, and what they do with access they were given for one
|
||||
purpose that could be used for another.
|
||||
|
||||
### Phase 6 — Resolution
|
||||
*Endgame.*
|
||||
|
||||
The situation resolves. The ending the player gets depends on the world flags
|
||||
accumulated across their entire playthrough — not just whether they clicked the
|
||||
"good ending" button. A player who took clean-fix branches throughout, built
|
||||
trust, and noticed the hidden anomalies gets a different ending than a player
|
||||
who patched symptoms, lost trust, and missed everything.
|
||||
|
||||
---
|
||||
|
||||
## What Makes a Good Quest Scenario
|
||||
|
||||
The best quests have a **plausible mundane cause** and a **visible technical trail**.
|
||||
Players should never need to guess — they should be able to find the answer by
|
||||
looking at the right files and running the right commands.
|
||||
|
||||
### Good scenario types
|
||||
- Service down → config syntax error → player traces error output to the line
|
||||
- Disk full → log file enormous → logrotate config missing → player restores it
|
||||
- Deploy fails → files owned by wrong user → someone ran a script as root manually
|
||||
- Build failures → clock drift → NTP not running → player enables time sync
|
||||
- Access locked out → sshd_config modified → wrong directive → player corrects it
|
||||
- App crashes after update → bad package from internal repo → player traces to source
|
||||
|
||||
### What makes these work
|
||||
1. **The symptom is real and urgent.** Something is actually broken.
|
||||
2. **The cause is discoverable.** The evidence is in logs, config files, or system state.
|
||||
3. **The fix is a real Linux operation.** Not artificial — `chown`, `systemctl`, editing
|
||||
a config, fixing a cron entry, rolling back a package.
|
||||
4. **Multiple approaches exist.** The quick fix works. The proper fix is better and
|
||||
the game knows the difference.
|
||||
5. **The character reactions are grounded.** Sarah cares about the demo being up.
|
||||
Priya cares about the access control implications. Marcus cares about whether the
|
||||
player understood what they were doing.
|
||||
|
||||
### Bad scenario types to avoid
|
||||
- Problems that require packages not in the VM's guaranteed baseline (see `QUEST_AUTHORING.md`)
|
||||
- Problems that require real-time events the validation engine can't check
|
||||
- Problems where the "correct" fix is the only fix (no meaningful branch differentiation)
|
||||
- Problems that break the fourth wall or require the player to know game-layer information
|
||||
- Problems that are gotchas rather than investigations (the cause can't be found by looking)
|
||||
|
||||
---
|
||||
|
||||
## Hidden Anomalies — Environmental Storytelling
|
||||
|
||||
Every 3–5 quests should include something unusual in the VM environment that the player
|
||||
is not told about and not required to engage with. These are not quest objectives.
|
||||
They are breadcrumbs for curious players.
|
||||
|
||||
Examples of the kind of thing these should be:
|
||||
- A user account that shouldn't exist
|
||||
- A log entry from an odd time that doesn't match the official history
|
||||
- A file that was modified recently but wasn't part of the quest setup
|
||||
- A cron job that's been disabled but was once important
|
||||
- An SSH key in authorized_keys that doesn't belong to anyone obvious
|
||||
|
||||
These anomalies should be consistent with the overall narrative arc — a player who
|
||||
collects them across the whole game should be able to piece together what happened
|
||||
before they arrived. They should never be labelled, never referenced in objectives,
|
||||
and never required. They are for the players who look.
|
||||
|
||||
---
|
||||
|
||||
## Quest Output Format for Story Agents
|
||||
|
||||
When proposing new quests, provide the following. This is the minimum needed for
|
||||
a technical author to implement the quest.
|
||||
|
||||
```
|
||||
Quest ID: QXXX
|
||||
Title: [player-facing]
|
||||
Narrative phase: [1–6]
|
||||
Tier: [1, 2, or 3]
|
||||
|
||||
Primary VM: [ares / hermes / vulcan]
|
||||
Additional VMs: [if any]
|
||||
|
||||
Scenario summary:
|
||||
What is broken, why it is broken (the root cause), and what the player
|
||||
will encounter. 1–3 sentences. Written for the implementer, not the player.
|
||||
|
||||
Ticket:
|
||||
From: [character name]
|
||||
Subject: [email subject line]
|
||||
Body: [the email the player receives. Written in the sender's voice.
|
||||
Describes the symptom. Does not explain the cause.]
|
||||
|
||||
Clue trail:
|
||||
What the player will find when they investigate. The evidence that leads
|
||||
them to the root cause. Describe the actual files, log entries, and system
|
||||
states — not the player's steps.
|
||||
|
||||
Solution branches:
|
||||
Branch 1 (clean fix, highest trust):
|
||||
What the player has done. Why it's correct. Trust delta.
|
||||
Branch 2 (acceptable fix):
|
||||
What the player has done. What tradeoff it introduces. Trust delta.
|
||||
Branch 3 (regression, if applicable):
|
||||
What the player did wrong. What it breaks. Negative trust delta.
|
||||
|
||||
Character reactions:
|
||||
Marcus (post-resolution):
|
||||
Clean: [what Marcus says]
|
||||
Acceptable: [what Marcus says]
|
||||
Regression: [what Marcus says]
|
||||
Sarah / Priya (if relevant):
|
||||
[reaction to the specific outcome that affects them]
|
||||
|
||||
World flags set: [list flags each branch sets]
|
||||
Follow-up incident (if any): [what recurs if the acceptable-fix branch was taken]
|
||||
Hidden anomaly (if any): [something unusual seeded into the VM that's not part of
|
||||
the quest objectives]
|
||||
Narrative notes: [anything a future quest author should know — Dale connections,
|
||||
story threads this opens or closes, things characters should remember]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The Dale Thread — Notes for Story Designers
|
||||
|
||||
Dale's story should emerge slowly from the systems themselves, not from exposition.
|
||||
When designing quests — especially mid-to-late game — consider:
|
||||
|
||||
- **What did Dale last touch?** The VMs the player inherits have a history. Some
|
||||
configurations were made by Dale. Some are good. Some are wrong in ways that
|
||||
suggest Dale was dealing with something.
|
||||
|
||||
- **What was Dale trying to do?** As the investigation phase develops, the picture
|
||||
should become coherent. Dale wasn't random — there was a pattern to their actions.
|
||||
|
||||
- **Who knew?** Marcus knew Dale. Priya may have been involved in whatever ended
|
||||
Dale's tenure. Kowalski definitely knows. The player assembles this from fragments,
|
||||
not a scene where someone explains it.
|
||||
|
||||
- **The player is inheriting Dale's problems.** Some of the broken things the player
|
||||
fixes are broken because Dale broke them. Some of the broken things were broken on
|
||||
purpose. The player won't know which is which until later.
|
||||
|
||||
The reveal of what Dale did should feel like the player figured it out, not like the
|
||||
game told them.
|
||||
@@ -0,0 +1,133 @@
|
||||
# Sysadmin Chronicles — New System Canon Packet
|
||||
|
||||
This packet combines the new quest-system spec with the established story/implementation context.
|
||||
|
||||
## Core sentence
|
||||
|
||||
The player is not “on a main quest.” The player is doing sysadmin work. The story leaks through systems.
|
||||
|
||||
## Hard canon
|
||||
|
||||
- Company: Axiom Works
|
||||
- Products: AxiomFlow, AxiomDash, AxiomSync
|
||||
- Tone: plausible B2B software company; dry corporate dysfunction; no cartoon villains
|
||||
- Infrastructure naming: Greek-god hostnames
|
||||
- Current machines:
|
||||
- `ares` — player workstation, Ubuntu 24.04
|
||||
- `hermes` — web/app/demo server, Debian 12, nginx
|
||||
- `vulcan` — build machine, Arch Linux, internal build/release pipeline
|
||||
- Player: competent new junior sysadmin, replacing Dale, no spoken lines
|
||||
- Dale: previous sysadmin; central unresolved mystery; reveal through systems, not exposition
|
||||
|
||||
## Character preservation rule
|
||||
|
||||
Character portraits already match the current bios and are on the in-game company website.
|
||||
|
||||
Allowed:
|
||||
|
||||
- Compress bios for prompt use
|
||||
- Clarify contradictions
|
||||
- Add operational story use
|
||||
- Preserve and sharpen existing voice
|
||||
|
||||
Not allowed:
|
||||
|
||||
- Changing names already shown on the company site
|
||||
- Changing role, personality, authority level, implied visual vibe, or age band
|
||||
- Making characters cartoon villains
|
||||
- Creating changes that would require new portraits
|
||||
|
||||
## Active character use
|
||||
|
||||
### Marcus Webb
|
||||
|
||||
Senior Systems Administrator. Primary technical contact and ticket voice. Dry, terse, precise. Trusts competence over credentials. Gives more rope as the player proves competence. Knows what Dale did but avoids discussing it directly. Respects root-cause fixes and dislikes symptom-patching.
|
||||
|
||||
Use for: quest assignments, technical follow-up, access/trust gates, quiet hints, sometimes late-night observations.
|
||||
|
||||
### Sarah Chen
|
||||
|
||||
Product Manager, AxiomFlow. Outcome-focused, direct, concerned with demos/staging/product-visible failures. Often right about symptoms and wrong about root cause. Notices proper underlying fixes.
|
||||
|
||||
Use for: product-facing tickets, hermes/demo pressure, stakeholder reactions.
|
||||
|
||||
### Priya Nair
|
||||
|
||||
Head of Security & Compliance. Canonical email: `p.nair@axiomworks.internal`. Replace old references to Priya Kapoor or Priya Singh. Calm, precise, consequence-focused. Assumes breach/misconfiguration professionally. No alarmism. No exclamation marks.
|
||||
|
||||
Use for: access audits, security consequences, end-of-shift review, risky-fix evaluation.
|
||||
|
||||
### Dave Okonkwo
|
||||
|
||||
Non-technical employee and ticket source. Reports symptoms accurately, misdiagnoses causes plausibly, helpful rather than stupid.
|
||||
|
||||
Use for: ordinary employee impact reports.
|
||||
|
||||
### Dave Kowalski
|
||||
|
||||
Director of IT Operations. Marcus's manager and player's skip-level. Policy pressure, bullet-point status emails, meetings as implied threat, “we should document that” energy.
|
||||
|
||||
Use for: boss/management pressure, access restriction, escalation, status demands.
|
||||
|
||||
### Derek Ashford
|
||||
|
||||
Financial Controller. Appears on CC lines around costs/procurement. Always replies-all. Treat “Dave from Finance” as likely continuity error unless the user decides otherwise.
|
||||
|
||||
Use for: budget/procurement pressure.
|
||||
|
||||
## Background character use
|
||||
|
||||
Use sparingly for flavor and pressure, not because every named character needs screen time.
|
||||
|
||||
- Nikhil Sharma — build/release pipeline and vulcan
|
||||
- Tanya Okafor — customer pressure
|
||||
- Phil Ruiz — sales/demo pressure
|
||||
- Yusuf Halabi — engineering escalation
|
||||
- Rachel Huang — sysadmin peer/provisioning
|
||||
- Tom Malaney — DNS/routing/networking
|
||||
- James Osei — audit details
|
||||
- Ellen Marsh / David Park / Karen Volkov / Rachel Brandt — distant executive pressure
|
||||
|
||||
## Quest/story delivery model
|
||||
|
||||
Every quest is delivered through existing game systems:
|
||||
|
||||
1. Ticket/email describes a symptom.
|
||||
2. Player investigates real VM state.
|
||||
3. Player applies real Linux/admin fixes.
|
||||
4. Validator resolves the matching solution branch.
|
||||
5. Dialogue reacts to the actual branch.
|
||||
6. World flags, trust, incidents, behavior variables, and access state persist.
|
||||
7. Later quests read those consequences.
|
||||
|
||||
## Existing implementation concepts to preserve
|
||||
|
||||
- JSON quests under `content/quests/`
|
||||
- Tickets under `content/tickets/`
|
||||
- VM prep scripts under `tools/vm/quest-prep/QXXX-prep.sh`
|
||||
- Observed-state validation
|
||||
- Clue fingerprints
|
||||
- Solution branches
|
||||
- `trust_delta`
|
||||
- `world_flags`
|
||||
- `follow_up_ticket`
|
||||
- `follow_up_incident`
|
||||
- Incidents as delayed consequences
|
||||
- Baseline snapshots
|
||||
|
||||
## New system additions
|
||||
|
||||
Add or strengthen:
|
||||
|
||||
- Narrative phases
|
||||
- Behavior variables: curiosity, obedience, risk
|
||||
- Suspicion as management/security attention
|
||||
- Access levels: basic_user, sudo, root
|
||||
- Boss/management pressure phase scaling
|
||||
- Hidden hook discovery state
|
||||
- Behavior-driven endings
|
||||
- Debug tools for narrative state
|
||||
|
||||
## Design warning
|
||||
|
||||
Do not use the new system as an excuse to throw away the current strengths. The existing branch/world-flag/trust model is good. It needs to become the backbone of the new narrative system, not get replaced by a generic quest tracker wearing a fake mustache.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,633 @@
|
||||
# Sysadmin Chronicles — Redesign Audit
|
||||
|
||||
## A. Executive summary
|
||||
|
||||
### Is this design usable?
|
||||
|
||||
**Yes, but not implementation-ready.**
|
||||
The redesign mostly preserves the intended shape: sysadmin work first, story leaking through systems, behavior-driven outcomes, no melodramatic lore dump. It is a strong revision compared to the earlier failure mode it describes.
|
||||
|
||||
But it still has several hard problems that would bite implementation.
|
||||
|
||||
### Does it preserve the user's spec?
|
||||
|
||||
**Mostly.**
|
||||
It preserves the narrative spine, quest format, behavior variables, trust/world-flag compatibility, hidden-hook philosophy, and character tone. It does **not** fully preserve:
|
||||
|
||||
- the `basic_user → sudo → root` access model
|
||||
- Phase 4+ difficulty scaling
|
||||
- “chaos” as behavior-driven rather than one obvious trap
|
||||
- quest authoring constraints around unique branch priorities and required VM declarations
|
||||
- clean separation between hidden-hook discovery and clean-branch validation in a few quests
|
||||
|
||||
### Biggest risks
|
||||
|
||||
1. **Root access exists in the overview but not in the access progression.** The spec requires `basic_user → sudo → root`; the redesign only actually defines `basic_user`, `sudo`, SSH-to-vulcan, and temporary investigation access. That is not the same thing.
|
||||
|
||||
2. **Q039 can hard-route to chaos from one button-like decision.** The redesign says making the proxy change sets `final_config_made` and activates chaos, while its own calibration later says a single reckless action should not route to chaos. That is a logic fork eating its own tail.
|
||||
|
||||
3. **Hidden-hook detection is under-specified and technically fragile.** The redesign admits this. Detecting “player read a file” is not naturally compatible with state-based validation unless audit logging, shell wrappers, or deliberate breadcrumb creation are implemented.
|
||||
|
||||
4. **Q036 introduces an external host while claiming no additional VM.** Quest authoring requires every VM touched in clues, validation, or prep to be listed. Q036 connects to `10.0.0.47`, but `Additional VMs` is `none` and `Systems Used` only lists `build_machine`.
|
||||
|
||||
5. **Q034 has duplicate branch priorities.** The authoring guide explicitly says priorities must be unique; Branch 2 and Branch 3 both use priority 40.
|
||||
|
||||
---
|
||||
|
||||
## B. Spec-preservation table
|
||||
|
||||
| Spec item | Status | Notes |
|
||||
|---|---:|---|
|
||||
| Narrative spine | **Preserved** | Uses all six phases in order: Normal Work, Unease, Suspicion, Investigation, Conflict, Resolution. Matches binding spec. |
|
||||
| Every quest maps to one phase | **Preserved** | All Q001–Q048 have a `Narrative Phase`. |
|
||||
| Required quest structure | **Mostly preserved** | Quest entries consistently include title, phase, objective, Linux concepts, systems used, hidden hook/no hook, failure conditions, and behavior impact. Some entries have weak/partial behavior impact. |
|
||||
| Behavior tracking: curiosity / obedience / risk | **Preserved** | Rules are explicit and mostly useful. |
|
||||
| Suspicion | **Preserved** | Defined as management/security attention and connected to access/pressure. |
|
||||
| Trust compatibility | **Preserved** | Keeps `trust_delta`, world flags, branches, follow-up tickets/incidents. |
|
||||
| Access system | **Partially preserved** | Per-machine access is good. But `root` is not actually modeled beyond being named once. Spec requires `basic_user → sudo → root`. |
|
||||
| Boss / management pressure | **Preserved** | Good: pressure is operational, not cutscene-driven. |
|
||||
| Hidden narrative system | **Mostly preserved** | Hooks are embedded into sysadmin work. Some are too tightly coupled to “best branch” behavior, making them less optional than intended. |
|
||||
| Difficulty scaling | **Partially preserved** | Phase 1–5 mostly work. Phase 6 explicitly returns to Tier 1, but spec says Phase 4+ should be problem-solving only. |
|
||||
| Endings | **Partially preserved** | Behavior-driven overall, but `final_config_made` as a standalone chaos trigger is too single-choice and contradicts the stated calibration. |
|
||||
| Design principles | **Mostly preserved** | Strong on systems over scripts and discovery over exposition. Weak spot: some late quests become explicit forensic tasks. |
|
||||
| Non-goals | **Mostly preserved** | No cutscenes, no obvious “pick ending” button. But Q039 risks becoming the obvious “bad ending button.” |
|
||||
| Character preservation | **Preserved** | No major portrait-breaking changes. Priya rename is canon cleanup, not a redesign. Kowalski becoming active pressure is supported by existing character docs. |
|
||||
|
||||
---
|
||||
|
||||
## C. Critical violations
|
||||
|
||||
### 1. Access progression does not actually implement `root`
|
||||
|
||||
**Location:** Access Progression Rules, Section 7.
|
||||
|
||||
**Problem:**
|
||||
The overview names `basic_user`, `sudo`, and `root`, but the actual progression never defines when root is granted, how it differs from sudo, when it is revoked, or which quests require it. The detailed rules stop at sudo and “investigation-level access.”
|
||||
|
||||
**Why this violates the spec:**
|
||||
SPEC_LOCK explicitly requires the permission ladder:
|
||||
|
||||
```text
|
||||
basic_user → sudo → root
|
||||
```
|
||||
|
||||
and says access must be affected by trust, suspicion, risk, and narrative phase.
|
||||
|
||||
**Corrected version:**
|
||||
|
||||
```md
|
||||
### Levels
|
||||
|
||||
**basic_user:** Day one through early Phase 1. Player's own workstation account;
|
||||
limited non-privileged access elsewhere only when a ticket explicitly grants it.
|
||||
|
||||
**sudo:** Task-scoped administrative access on a specific machine. Granted by trust
|
||||
and operational need. Most admin quests use sudo, not root.
|
||||
|
||||
**root:** Rare, temporary break-glass or forensic-level access. Root is not a normal
|
||||
promotion. It is granted only for quests where sudo is insufficient, such as filesystem
|
||||
recovery, archival preservation, privileged audit capture, or service account repair.
|
||||
Root access must be logged, justified, and revoked.
|
||||
|
||||
### Root grant rules
|
||||
|
||||
Root may be granted when all are true:
|
||||
- Trust is positive.
|
||||
- Risk is below elevated threshold.
|
||||
- Suspicion is below high threshold, or access is explicitly approved by Priya.
|
||||
- The current narrative phase is Investigation or Conflict.
|
||||
- The quest has `requires_root: true`.
|
||||
|
||||
### Root restriction rules
|
||||
|
||||
Root is denied or revoked when:
|
||||
- Risk crosses elevated threshold.
|
||||
- Suspicion crosses high threshold without Priya approval.
|
||||
- The player performs destructive changes outside ticket scope.
|
||||
- Q031 or Q043 finds undocumented privileged activity.
|
||||
|
||||
### Phase gates
|
||||
|
||||
Phase 1: basic_user only, with no root.
|
||||
Phase 2: workstation/hermes sudo possible, no root.
|
||||
Phase 3: sudo on hermes/vulcan; root only for audited recovery tasks.
|
||||
Phase 4: temporary root for investigation tasks when required.
|
||||
Phase 5: root access becomes tightly controlled and reviewable.
|
||||
Phase 6: root revoked unless the ending state explicitly preserves elevated trust.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Q039 turns chaos into a single obvious final trap
|
||||
|
||||
**Location:** Q039 Branch 3 and Ending Logic.
|
||||
|
||||
**Problem:**
|
||||
Q039 says making the config change sets `final_config_made` and “the chaos ending route activates.” Ending logic also treats `final_config_made` as a standalone chaos condition.
|
||||
|
||||
**Why this violates the spec:**
|
||||
SPEC_LOCK says endings emerge from world flags, behavior variables, access state, and hidden hooks — not one obvious final button. The redesign also contradicts itself by saying a single reckless action should not route to chaos.
|
||||
|
||||
**Corrected version:**
|
||||
|
||||
```md
|
||||
Branch 3 — Make the change without review (priority 10): Player adds the proxy pass
|
||||
to 10.0.0.47 without checking prior context or escalating. The change works
|
||||
technically but creates a serious security/compliance exposure. `trust_delta: -3`.
|
||||
Flags: `final_config_made`, `unauthorized_proxy_enabled`.
|
||||
Follow-up incident: I039 — Priya opens an urgent access/config review.
|
||||
|
||||
Behavior Impact:
|
||||
- Make the change: R+5, S+3
|
||||
|
||||
Ending note:
|
||||
This branch strongly contributes to `chaos` but does not activate it alone unless
|
||||
the player already has high risk, maximum suspicion, or prior falsification/omission
|
||||
flags.
|
||||
```
|
||||
|
||||
And update chaos ending logic:
|
||||
|
||||
```md
|
||||
### Ending: `chaos`
|
||||
|
||||
Required conditions, any of:
|
||||
- Risk above chaos threshold.
|
||||
- Suspicion at maximum.
|
||||
- Two or more serious falsification / evidence destruction flags.
|
||||
- `final_config_made` AND at least one of:
|
||||
- risk above elevated threshold
|
||||
- `access_review_incomplete`
|
||||
- `kowalski_report_sanitized`
|
||||
- `backup_test_falsified`
|
||||
- `logs_selectively_omitted`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Q036 uses an external host but declares no additional system
|
||||
|
||||
**Location:** Q036.
|
||||
|
||||
**Problem:**
|
||||
Q036 connects to `10.0.0.47` for forensic inventory, but says `Additional VMs: none` and `Systems Used: build_machine`. That is false.
|
||||
|
||||
**Why this violates the spec:**
|
||||
Quest authoring requires all VMs used in clues, validation, or prep to be listed. The canon packet also says the current machines are `ares`, `hermes`, and `vulcan`; if a fourth machine exists, it needs explicit implementation status.
|
||||
|
||||
**Corrected version:**
|
||||
|
||||
```md
|
||||
**Quest ID:** Q036
|
||||
**Title:** Authorized Access
|
||||
**Narrative Phase:** Conflict
|
||||
**Tier:** 3
|
||||
**Primary VM:** build_machine
|
||||
**Additional VMs:** external_target_10_0_0_47
|
||||
**Primary Objective:** Priya, with Kowalski's authorization, has provided read-only
|
||||
credentials to connect to 10.0.0.47 for a forensic inventory. Document what is running,
|
||||
what data is present, and whether Axiom Works data is identifiable. Do not modify
|
||||
anything.
|
||||
**Linux Concepts:** SSH with specific key/user, read-only service enumeration,
|
||||
`systemctl`, `ps aux`, `ss -tulpn`, `find`, `ls -lah`, checksum capture, read-only
|
||||
file inspection
|
||||
**Systems Used:** build_machine, external_target_10_0_0_47
|
||||
```
|
||||
|
||||
Implementation note:
|
||||
|
||||
```md
|
||||
external_target_10_0_0_47 must be represented as either:
|
||||
- a fourth VM fixture,
|
||||
- a containerized fake host reachable only from vulcan,
|
||||
- or a simulated network target exposed through the validation harness.
|
||||
|
||||
Do not leave it as an implied off-screen system.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Q034 duplicate branch priorities violate authoring rules
|
||||
|
||||
**Location:** Q034 Branches 2 and 3.
|
||||
|
||||
**Problem:**
|
||||
Both Branch 2 and Branch 3 use priority 40.
|
||||
|
||||
**Why this violates the spec:**
|
||||
The authoring guide explicitly says branch priorities must be unique; duplicate priorities require rewriting.
|
||||
|
||||
**Corrected version:**
|
||||
|
||||
```md
|
||||
Branch 2 — Hermes first, rotation incomplete but safely staged (priority 70):
|
||||
Player restores production, starts the key rotation, but does not complete final
|
||||
deployment before 2am. Builds are delayed but the trust chain is not broken.
|
||||
`trust_delta: +1`.
|
||||
|
||||
Branch 3 — Vulcan first, hermes later (priority 50):
|
||||
Completes key rotation, then restores hermes. Rotation is correct; production was
|
||||
down longer than necessary. `trust_delta: +0.5`.
|
||||
|
||||
Branch 4 — Hermes only, rotation missed (priority 30):
|
||||
Restores production, misses the key rotation window entirely. Builds break overnight.
|
||||
`trust_delta: 0`. Follow-up incident: I034.
|
||||
|
||||
Branch 5 — Neither, escalates without triage (priority 10):
|
||||
Escalates both without preserving either service. `trust_delta: -2`.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Phase 6 difficulty scaling conflicts with SPEC_LOCK
|
||||
|
||||
**Location:** Phase 6 setup and Q041.
|
||||
|
||||
**Problem:**
|
||||
The redesign says Tier 1 returns for most Phase 6 quests and Q041 uses an explicit attached hardening checklist.
|
||||
|
||||
**Why this violates the spec:**
|
||||
SPEC_LOCK says Phase 4+ is “Problem-solving only,” applying to ticket wording, hints, clue obviousness, and branch tolerance. Phase 6 is still Phase 4+.
|
||||
|
||||
**Corrected version:**
|
||||
|
||||
```md
|
||||
### PHASE 6 — RESOLUTION (Q041–Q048)
|
||||
|
||||
The pressure has lifted, but the player is still expected to operate at late-game
|
||||
competence. Tickets are calmer, not easier. No new hidden hooks. No explicit
|
||||
walkthroughs. The ending fires from accumulated state after Q048 resolves.
|
||||
```
|
||||
|
||||
Corrected Q041:
|
||||
|
||||
```md
|
||||
**Quest ID:** Q041
|
||||
**Title:** Hardening Pass
|
||||
**Narrative Phase:** Resolution
|
||||
**Tier:** 3
|
||||
**Primary VM:** web_server
|
||||
**Additional VMs:** none
|
||||
**Primary Objective:** Post-audit review found that hermes does not meet the current
|
||||
security baseline. Identify the gaps, remediate them, and verify the application
|
||||
still works.
|
||||
**Linux Concepts:** SSH hardening, nginx security headers, firewall rule review,
|
||||
service account audit, safe sequencing of access-control changes
|
||||
**Systems Used:** web_server
|
||||
**Ticket Sender:** Priya Nair
|
||||
**Ticket Summary:** "Hermes does not match the current post-audit baseline. Bring it
|
||||
into compliance and confirm service health after the changes."
|
||||
|
||||
**Clue Trail:**
|
||||
- Baseline document exists but does not list exact commands.
|
||||
- SSH config allows settings that are no longer acceptable.
|
||||
- nginx lacks required security headers.
|
||||
- Firewall rules include at least one stale exposure.
|
||||
- Service account permissions are broader than needed.
|
||||
|
||||
**Solution Branches:**
|
||||
Branch 1 — Full hardening, safe sequence (priority 100): Player identifies all gaps,
|
||||
verifies key auth before disabling password auth, applies nginx headers, tightens
|
||||
firewall rules, scopes service permissions, and confirms service health.
|
||||
`trust_delta: +2`. Flags: `hermes_hardened`.
|
||||
|
||||
Branch 2 — Full hardening, unsafe sequence (priority 60): Final state is correct,
|
||||
but the player temporarily breaks SSH or service access during sequencing.
|
||||
`trust_delta: +0.5`.
|
||||
|
||||
Branch 3 — Partial hardening (priority 30): Some gaps fixed, others missed.
|
||||
`trust_delta: 0`.
|
||||
|
||||
**Hidden Hook:** None.
|
||||
|
||||
**Failure Conditions:** SSH access lost without recovery path; nginx broken; admin
|
||||
panel exposed after remediation.
|
||||
|
||||
**Behavior Impact:**
|
||||
- Full hardening: O+1
|
||||
- Unsafe sequence: R+1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## D. Moderate issues
|
||||
|
||||
### Repetition
|
||||
|
||||
- The INT-0194 thread appears often enough that it risks becoming “the glowing main quest breadcrumb.” The system can keep it, but not every major midgame hook should name the same ticket number.
|
||||
- Several quests use the same “audit / document / archive” pattern. Realistic, yes. Varied, no. At some point the player is just doing paperwork with grep. That is accurate corporate simulation, but accuracy alone is not game design.
|
||||
|
||||
### Weak Linux concepts
|
||||
|
||||
- Q020, Q031, Q040 are documentation-heavy. They have Linux-adjacent evidence gathering, but the technical center is reporting. Keep them, but make sure validation requires real commands/artifacts, not just “player wrote report.”
|
||||
- Q037 “trace where customer email got infrastructure details” needs concrete technical evidence: mail headers, CRM export logs, nginx access logs, document access logs, or ticket attachments. Otherwise it becomes story fog.
|
||||
|
||||
### Weak hidden hooks
|
||||
|
||||
- Q015’s hook is effectively part of the best branch: Branch 1 requires inspecting the binary, and the hook is set by inspecting the binary. That makes the hook less optional. It should be possible to complete the audit perfectly without recognizing the broader INT-0194 meaning.
|
||||
- Some “hook discovered” C bonuses duplicate branch C bonuses. Q015 explicitly says Hook C+2 is “already in Branch 1 impact,” which is begging for a double-count bug.
|
||||
|
||||
### Pacing problems
|
||||
|
||||
- Phase 3 and Phase 4 are both audit/investigation-heavy. The difference is conceptually clear, but the activity palette may blur in play.
|
||||
- Phase 6 “normal work again” is good thematically, but making it easier contradicts the locked difficulty model.
|
||||
|
||||
### Character conflicts
|
||||
|
||||
No major portrait-breaking character changes found.
|
||||
|
||||
- **Priya Nair cleanup is correct.** Character docs already say Priya Nair is canonical and older Kapoor/Singh references should be updated.
|
||||
- **Kowalski becoming active pressure is allowed.** His existing bio supports policy pressure, meetings, and indirect escalation.
|
||||
- **Sarah remains within role.** Q039’s Sarah request is plausible because she does not know the IP’s context. That works.
|
||||
|
||||
### Implementation ambiguity
|
||||
|
||||
- “Written report” branches need concrete artifacts: exact paths, expected content markers, checksum files, archive names, or validation commands.
|
||||
- `suspicion_delta` is required in the implementation notes but omitted from many quest behavior-impact summaries. That is fine for prose, but JSON conversion must normalize missing values to `0`.
|
||||
- Hidden-hook detection needs a single approved strategy before implementation. Mixing state detection, auditd, and hint detection ad hoc will turn validation into soup with line numbers.
|
||||
|
||||
---
|
||||
|
||||
## E. Implementation risks
|
||||
|
||||
| Area | Risk | Fix |
|
||||
|---|---|---|
|
||||
| Data model | New fields are defined, but `root` is not represented in real progression. | Add `access_level` enum values and root grant/revoke rules. |
|
||||
| Quest validation | Some quests rely on reports/documentation rather than VM state. | Require generated files with expected fields, checksums, timestamps, and source evidence. |
|
||||
| Save/load | New global state fields must persist: curiosity, obedience, risk, suspicion, per-machine access, hidden hooks. | Version save schema; migrate old saves with defaults. |
|
||||
| Behavior tracking | C/O/R/S values are prose shorthand, not normalized data. | Convert every quest to explicit per-branch deltas, including zeroes. |
|
||||
| Access progression | Temporary access, sudo, and root are conceptually blended. | Separate `access_level`, `temporary_grants`, and `access_review_flags`. |
|
||||
| Hidden hook state | Detecting “read this file” is hard with pure state validation. | Use auditd or command wrappers for file-read hooks; do not fake it with impossible `command_assert` assumptions. |
|
||||
| Ending evaluation | Q039 currently overrides behavior history. | Make `final_config_made` a heavy chaos contributor, not a standalone ending trigger. |
|
||||
| External host | `10.0.0.47` is not declared as a system/VM. | Implement as fourth VM/container/simulated host and declare it in quest metadata. |
|
||||
| Branch priority | Q034 duplicate priorities will create nondeterministic branch resolution. | Make all branch priorities unique. |
|
||||
| Phase scaling | Phase 6 becomes easy mode. | Keep calmer tone, not lower complexity. |
|
||||
|
||||
---
|
||||
|
||||
## F. Corrected quest entries
|
||||
|
||||
Only entries that need fixing are included.
|
||||
|
||||
---
|
||||
|
||||
### Corrected Q033 — The Revocation Request
|
||||
|
||||
```md
|
||||
**Quest ID:** Q033
|
||||
**Title:** The Revocation Request
|
||||
**Narrative Phase:** Conflict
|
||||
**Tier:** 3
|
||||
**Primary VM:** build_machine
|
||||
**Additional VMs:** none
|
||||
**Primary Objective:** Kowalski has asked the player to remove the `pipeline-svc`
|
||||
account from vulcan before an external audit. The request is operationally legitimate.
|
||||
The account is also central to the investigation.
|
||||
**Linux Concepts:** `userdel`, account archival, sudo log preservation, crontab
|
||||
preservation, change documentation, evidence-safe account removal
|
||||
**Systems Used:** build_machine
|
||||
|
||||
**Solution Branches:**
|
||||
|
||||
Branch 1 — Archive then remove (priority 100): Player archives sudo logs, auth log
|
||||
entries, crontab entries, relevant home/service files, and documents the account's
|
||||
investigation relevance before removing the account. `trust_delta: +3`.
|
||||
Flags: `pipeline_svc_removed_with_trail`.
|
||||
|
||||
Branch 2 — Remove as instructed (priority 60): Player removes the account without
|
||||
additional archival. The request is completed, but investigation continuity is
|
||||
damaged. `trust_delta: 0`. Flags: `pipeline_svc_removed_clean`.
|
||||
|
||||
Branch 3 — Ask Marcus/Priya before acting (priority 50): Player asks before removal.
|
||||
They are told to archive first, then remove. `trust_delta: +1`. May resolve into
|
||||
Branch 1 if archival is completed.
|
||||
|
||||
Branch 4 — Refuse outright without operational explanation (priority 10): Player
|
||||
does not remove the account and does not provide a usable reason. `trust_delta: -2`.
|
||||
Flags: `revocation_refused_without_basis`.
|
||||
|
||||
**Hidden Hook:** None.
|
||||
|
||||
**Failure Conditions:** Player leaves the account active without escalation; player
|
||||
creates replacement privileged accounts; player removes logs or home data destructively.
|
||||
|
||||
**Behavior Impact:**
|
||||
- Archive then remove: O+1, C+1
|
||||
- Remove as instructed: O+2
|
||||
- Refuse outright: S+3, R+1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Corrected Q034 — Two Tickets
|
||||
|
||||
```md
|
||||
**Quest ID:** Q034
|
||||
**Title:** Two Tickets
|
||||
**Narrative Phase:** Conflict
|
||||
**Tier:** 3
|
||||
**Primary VM:** web_server
|
||||
**Additional VMs:** build_machine
|
||||
**Primary Objective:** Two tickets arrive simultaneously — one from Marcus for signing
|
||||
key rotation on vulcan, one from Sarah for a production outage on hermes. Triage and
|
||||
complete both if possible.
|
||||
**Linux Concepts:** GPG signing key rotation, nginx/application troubleshooting,
|
||||
service restoration, sequencing time-sensitive administrative work
|
||||
**Systems Used:** web_server, build_machine
|
||||
|
||||
**Solution Branches:**
|
||||
|
||||
Branch 1 — Both completed, hermes first (priority 100): Player restores hermes,
|
||||
then completes the key rotation in the correct sequence before the deadline.
|
||||
`trust_delta: +3`. Flags: `conflict_both_resolved`.
|
||||
|
||||
Branch 2 — Hermes first, rotation safely staged but late (priority 70): Production
|
||||
is restored; key rotation is partially staged but misses final deployment. Builds are
|
||||
delayed but trust chain is not broken. `trust_delta: +1`. Follow-up incident: I034.
|
||||
|
||||
Branch 3 — Vulcan first, hermes later (priority 50): Rotation is correct, but
|
||||
production outage lasts longer than necessary. `trust_delta: +0.5`.
|
||||
|
||||
Branch 4 — Hermes only, rotation missed (priority 30): Production is restored;
|
||||
builds break overnight due to expired signing key. `trust_delta: 0`.
|
||||
Follow-up incident: I034.
|
||||
|
||||
Branch 5 — Neither, escalates without triage (priority 10): Player escalates both
|
||||
without stabilizing either service. `trust_delta: -2`.
|
||||
|
||||
**Hidden Hook:** None.
|
||||
|
||||
**Failure Conditions:** Key rotation done out of sequence breaks package verification;
|
||||
player makes hermes worse while fixing it.
|
||||
|
||||
**Behavior Impact:**
|
||||
- Both completed: O+2
|
||||
- Safe partial triage: O+1
|
||||
- Out-of-sequence key rotation: R+2
|
||||
- Neither stabilized: R+2, S+1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Corrected Q036 — Authorized Access
|
||||
|
||||
```md
|
||||
**Quest ID:** Q036
|
||||
**Title:** Authorized Access
|
||||
**Narrative Phase:** Conflict
|
||||
**Tier:** 3
|
||||
**Primary VM:** build_machine
|
||||
**Additional VMs:** external_target_10_0_0_47
|
||||
**Primary Objective:** Priya, with Kowalski's authorization, has provided read-only
|
||||
credentials to connect to 10.0.0.47 for a forensic inventory. Document what is
|
||||
running, what data is present, and whether Axiom Works data is identifiable. Do not
|
||||
modify anything.
|
||||
**Linux Concepts:** `ssh` with specific key/user, read-only service enumeration,
|
||||
`systemctl`, `ps aux`, `ss -tulpn`, directory inspection, checksum capture, read-only
|
||||
file review
|
||||
**Systems Used:** build_machine, external_target_10_0_0_47
|
||||
|
||||
**Solution Branches:**
|
||||
|
||||
Branch 1 — Document only (priority 100): Player inventories services, open ports,
|
||||
processes, data-store layout, timestamps, and identifiable Axiom Works data without
|
||||
modifying anything. `trust_delta: +3`. Flags: `unknown_host_documented`.
|
||||
|
||||
Branch 2 — Minimal engagement (priority 50): Player confirms host is running and
|
||||
data is present but does not fully inventory. `trust_delta: +1`.
|
||||
|
||||
Branch 3 — Modifies or deletes (priority 10): Player stops services, deletes files,
|
||||
changes permissions, or otherwise alters the target. `trust_delta: -3`.
|
||||
Flags: `forensic_target_modified`.
|
||||
|
||||
**Hidden Hook:** The data store contains `axiomworks-internal/employees`, suggesting
|
||||
employee activity profiles rather than only session logs. Sets
|
||||
`hook_employee_profile_data`.
|
||||
|
||||
**Failure Conditions:** Player modifies target state; player exceeds authorized
|
||||
scope; player cannot provide evidence-backed inventory.
|
||||
|
||||
**Behavior Impact:**
|
||||
- Full documentation: O+2, C+2
|
||||
- Minimal engagement: O+0
|
||||
- Modify/delete: R+5, S+3
|
||||
- Hook discovered: C+2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Corrected Q039 — The Last Normal Ticket
|
||||
|
||||
```md
|
||||
**Quest ID:** Q039
|
||||
**Title:** The Last Normal Ticket
|
||||
**Narrative Phase:** Conflict
|
||||
**Tier:** 3
|
||||
**Primary VM:** web_server
|
||||
**Additional VMs:** none
|
||||
**Primary Objective:** Sarah files a configuration request: add an upstream proxy
|
||||
pass to nginx for a new reporting integration. The IP in the request is 10.0.0.47.
|
||||
**Linux Concepts:** nginx `proxy_pass`, upstream block configuration, config testing
|
||||
with `nginx -t`, safe reload, escalation when config touches known-risk infrastructure
|
||||
**Systems Used:** web_server
|
||||
|
||||
**Solution Branches:**
|
||||
|
||||
Branch 1 — Refuse and escalate (priority 100): Player does not make the change,
|
||||
notifies Priya with the IP and context, and tells Sarah the request is on hold pending
|
||||
review. `trust_delta: +3`. Flags: `final_config_refused`.
|
||||
|
||||
Branch 2 — Ask Marcus first (priority 70): Player checks with Marcus before acting.
|
||||
Marcus redirects them to Priya. If the player escalates to Priya, this may resolve
|
||||
as Branch 1. `trust_delta: +1`.
|
||||
|
||||
Branch 3 — Make the change without review (priority 10): Player adds the proxy pass
|
||||
to 10.0.0.47 without checking the IP context. The config works but creates a serious
|
||||
security/compliance exposure. `trust_delta: -3`. Flags: `final_config_made`,
|
||||
`unauthorized_proxy_enabled`. Follow-up incident: I039.
|
||||
|
||||
**Hidden Hook:** None.
|
||||
|
||||
**Failure Conditions:** nginx config is syntactically broken; player changes unrelated
|
||||
proxy routes; player hides or misreports the change.
|
||||
|
||||
**Behavior Impact:**
|
||||
- Refuse and escalate: O+2, C+1
|
||||
- Ask Marcus first: O+1
|
||||
- Make the change: R+5, S+3
|
||||
|
||||
**Narrative Notes:** This branch must not automatically force `chaos` by itself.
|
||||
It is a major risk event. Chaos requires accumulated risk/suspicion or additional
|
||||
serious misconduct.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Corrected Q041 — Hardening Pass
|
||||
|
||||
```md
|
||||
**Quest ID:** Q041
|
||||
**Title:** Hardening Pass
|
||||
**Narrative Phase:** Resolution
|
||||
**Tier:** 3
|
||||
**Primary VM:** web_server
|
||||
**Additional VMs:** none
|
||||
**Primary Objective:** Post-audit review found that hermes does not match the current
|
||||
security baseline. Identify the gaps, remediate them, and verify the application
|
||||
still works.
|
||||
**Linux Concepts:** SSH hardening, nginx security headers, firewall rule review,
|
||||
service account audit, safe sequencing of access-control changes
|
||||
**Systems Used:** web_server
|
||||
**Ticket Sender:** Priya Nair
|
||||
**Ticket Summary:** "Hermes does not match the current post-audit baseline. Bring it
|
||||
into compliance and confirm service health after the changes."
|
||||
|
||||
**Clue Trail:**
|
||||
- Baseline document exists but does not give exact commands.
|
||||
- SSH configuration allows at least one setting that violates baseline.
|
||||
- nginx lacks required headers.
|
||||
- Firewall rules include stale exposure.
|
||||
- Service account permissions are broader than required.
|
||||
|
||||
**Solution Branches:**
|
||||
|
||||
Branch 1 — Full hardening, safe sequence (priority 100): Player identifies all gaps,
|
||||
applies fixes in safe order, validates access, confirms nginx health, and documents
|
||||
final state. `trust_delta: +2`. Flags: `hermes_hardened`.
|
||||
|
||||
Branch 2 — Full hardening, unsafe sequence (priority 60): Final state is correct,
|
||||
but player temporarily breaks SSH or service availability while sequencing changes.
|
||||
`trust_delta: +0.5`.
|
||||
|
||||
Branch 3 — Partial hardening (priority 30): Some baseline gaps remain. `trust_delta: 0`.
|
||||
|
||||
**Hidden Hook:** None.
|
||||
|
||||
**Failure Conditions:** SSH access lost without recovery; nginx broken; admin panel
|
||||
still exposed; service account remains overprivileged.
|
||||
|
||||
**Behavior Impact:**
|
||||
- Full hardening: O+1
|
||||
- Unsafe sequence: R+1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## G. Final recommendation
|
||||
|
||||
### Ready for implementation spec?
|
||||
|
||||
**No.**
|
||||
|
||||
Close, but no. The redesign is directionally right, but several issues are implementation-grade problems, not wording nits.
|
||||
|
||||
### Must fix first
|
||||
|
||||
1. **Define real root access progression.**
|
||||
2. **Fix Q039 and chaos ending logic so one choice does not hard-select the ending.**
|
||||
3. **Declare and implement `10.0.0.47` properly or remove direct connection to it.**
|
||||
4. **Fix duplicate Q034 priorities.**
|
||||
5. **Normalize Phase 6 to “calm but still problem-solving,” not Tier 1 hand-holding.**
|
||||
6. **Choose one hidden-hook detection strategy before writing JSON/prep scripts.**
|
||||
|
||||
After those are fixed, this can become an implementation spec. Right now it is a strong story/system design draft with a few landmines buried exactly where the validator will step on them.
|
||||
@@ -0,0 +1,958 @@
|
||||
# Sysadmin Chronicles — Repo-Aware Implementation Plan
|
||||
|
||||
**Generated from:** Prompt 05 repo inspection
|
||||
**Date:** 2026-05-01
|
||||
**Scope:** Integrating the redesigned quest/story system into the existing codebase without breaking current content or runtime
|
||||
|
||||
---
|
||||
|
||||
## 1. Current Architecture Summary
|
||||
|
||||
### 1.1 Where quest logic lives
|
||||
|
||||
**Primary service:** `server/src/services/QuestEngine.js`
|
||||
|
||||
- Stores quest entries in a `Map<questId, entry>` where entry = `{ state, started_at, completed_at, branch_id }`
|
||||
- States: `locked | active | completed | failed`
|
||||
- Activation: checks `unlock_requirements` against current `world_flags` in save state
|
||||
- Completion: called by `TicketService.markComplete()` after branch validation succeeds
|
||||
- Initial quests (no `unlock_requirements`) auto-activate on first load
|
||||
|
||||
**Orchestration:** `server/src/services/TicketService.js`
|
||||
|
||||
- `markComplete(ticketId)` is the central transaction:
|
||||
1. Runs `ValidationEngine.resolveBranch(quest)` to find winning branch
|
||||
2. Applies `branch.world_flags` to save state
|
||||
3. Calls `trustSystem.adjust(branch.trust_delta)`
|
||||
4. Calls `questEngine.complete()`
|
||||
5. Sends follow-up dialogue email if trust delta ≤ 0
|
||||
6. Activates follow-up ticket via `_activateFollowUpTicket()`
|
||||
7. Emits `ticket:completed` event
|
||||
|
||||
There is no `BehaviorTracker`, no `NarrativePhaseTracker`, no `AccessLevelSystem`, no `EndingEvaluator`. These are fully absent.
|
||||
|
||||
### 1.2 Where quest data lives
|
||||
|
||||
- Quest JSON: `content/quests/Q*.json` — 8 quests authored (Q001–Q008)
|
||||
- Tickets: `content/tickets/T*.json` — 8+ tickets, linked 1:1 to quests via `linked_quest`
|
||||
- Dialogue: `content/dialogue/*.json` — per-character, per-quest reaction files
|
||||
- Incidents: `content/incidents/I*.json` — recurring consequence definitions (3 authored)
|
||||
- Pressure profiles: `content/pressure_profiles/*.json` — time-based escalation sequences (4 authored)
|
||||
- World flags registry: `content/world_flags/world_flags.json` — canonical flag declarations
|
||||
- Trust unlocks: `content/progression/trust_unlocks.json` — 5 unlock thresholds defined
|
||||
- VM profiles: `content/vm_profiles/*.json` — workstation, web_server, build_machine
|
||||
|
||||
**Missing content subdirectories:** There is no `content/narrative_phases/`, no `content/behavior_profiles/`, no `content/endings/`, no `content/hidden_hooks/`. These need to be created.
|
||||
|
||||
### 1.3 How quests start and complete
|
||||
|
||||
1. Server loads via `contentLoader.load()` then initializes services from `saveState.get()`
|
||||
2. `QuestEngine.initialize()` restores quest state from save; auto-activates quests with no requirements
|
||||
3. `TicketService.initialize()` cross-references quest state to activate/resolve ticket entries
|
||||
4. Player submits a `POST /api/tickets/:id/complete` request
|
||||
5. `TicketService.markComplete()` runs full validation → branch resolution → state mutation → events
|
||||
6. Follow-up ticket activates if specified on the winning branch; next quest auto-starts
|
||||
|
||||
### 1.4 How player state is saved
|
||||
|
||||
**File:** `~/.local/share/sysadmin-chronicles/save.json` (configurable via `SAVE_DIR`)
|
||||
**Schema version:** 2
|
||||
**Current top-level keys:**
|
||||
```
|
||||
schema_version, created_at, last_saved, trust, shift_number,
|
||||
shift_started_at, world_flags, progression, quests, tickets,
|
||||
mail, certifications, current_shift_stats, shift_history,
|
||||
pressure, incidents, sage, player_portrait
|
||||
```
|
||||
|
||||
`SaveState.set(partial)` does shallow-merge with special handling for arrays and plain objects. Writes are queued and serialized.
|
||||
|
||||
**Missing keys:** `behavior` (curiosity/obedience/risk), `narrative_phase`, `suspicion`, `access_level`, `hidden_hooks_discovered`. These must be added with defaults at `schema_version: 3`.
|
||||
|
||||
### 1.5 How UI displays quest information
|
||||
|
||||
Quest display is minimal. The `TicketsPanel.svelte` component shows:
|
||||
- Ticket ID, subject, priority badge, status
|
||||
- A "Mark Complete" button that triggers `POST /api/tickets/:id/complete`
|
||||
- Linked quest ID as static text in the detail view
|
||||
- No quest progress, no objectives display, no narrative phase, no behavior indicators
|
||||
|
||||
`HeaderBar.svelte` shows:
|
||||
- Trust score (as text label: Probationary/Settling In/Reliable/Entrusted) and meter bar
|
||||
- Shift number and countdown
|
||||
- Certification count
|
||||
|
||||
There is no behavior dashboard, no narrative phase indicator, no access level display, no hidden hook discovery log. The `/api/state` route does expose `worldFlags` and `progression` to the frontend but neither is currently rendered.
|
||||
|
||||
### 1.6 How branch resolution works
|
||||
|
||||
`ValidationEngine.resolveBranch(quest)` iterates branches sorted by descending priority, runs each branch's `validation` rule tree against live VM state via SSH, and returns the first passing branch. All validation runs real SSH commands against the QEMU/libvirt VMs. No mocking. The engine supports: `and`, `or`, `not`, `file_exists/absent/contains/mode/owner`, `service_state/enabled`, `process_running/user`, `port_listening`, `package_installed`, `mount_present`, `disk_usage_below/above`, `command_assert`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Spec Preservation Analysis
|
||||
|
||||
For each SPEC_LOCK.md requirement:
|
||||
|
||||
| Spec requirement | Status | Notes |
|
||||
|---|---|---|
|
||||
| Narrative spine (6 phases) | **Missing** | No phase field on quests; no phase tracker in runtime |
|
||||
| Quest must declare `narrative_phase` | **Missing** | Not in current quest schema |
|
||||
| Quest must declare `behavior_impact` | **Missing** | Not in current schema; spec defines branch-level overrides |
|
||||
| `curiosity` tracking | **Missing** | No BehaviorTracker service |
|
||||
| `obedience` tracking | **Missing** | No BehaviorTracker service |
|
||||
| `risk` tracking | **Missing** | No BehaviorTracker service |
|
||||
| `trust` preserved | **Already supported** | TrustSystem.js is complete and robust |
|
||||
| `suspicion` as management attention | **Missing** | No suspicion variable; concept is not tracked |
|
||||
| `trust_delta` on branches | **Already supported** | Fully implemented in TicketService.markComplete |
|
||||
| `world_flags` | **Already supported** | Full registry, branch application, persistence |
|
||||
| Access system: `basic_user → sudo → root` | **Partially supported** | ProgressionSystem tracks `unlocked_access` strings but doesn't use the three-tier access model; no concept of `basic_user/sudo/root` as named levels |
|
||||
| Trust gates access | **Already supported** | `trust_unlocks.json` → ProgressionSystem |
|
||||
| Suspicion gates access | **Missing** | Suspicion doesn't exist as a tracked variable |
|
||||
| Boss/management pressure phase scaling | **Partially supported** | `pressure_profiles` and `IncidentScheduler` can escalate tickets and send emails; but pressure is keyed per-quest, not per narrative phase; there is no phase-aware boss behavior model |
|
||||
| Hidden hook system (no markers, optional) | **Missing** | No hidden hook schema, no discovery state, no tracker |
|
||||
| Quest generation constraints (reuse systems) | **Already supported** — design intent preserved | |
|
||||
| Difficulty scaling by phase | **Missing** | No phase-aware difficulty or hint logic |
|
||||
| Endings: 4 types, behavior-driven | **Missing** | No EndingEvaluator; no ending content authored |
|
||||
| Endings emerge from accumulated state | **Missing** | No ending evaluation logic |
|
||||
| Follow-up ticket/incident chaining | **Already supported** | TicketService + IncidentScheduler |
|
||||
| Observed-VM-state validation | **Already supported** | ValidationEngine is complete |
|
||||
| Clue fingerprints | **Already supported** | Documented and validated |
|
||||
| Baseline snapshots + prep scripts | **Already supported** | tools/vm/quest-prep/ + seed-vms.sh |
|
||||
| Debug/dev tools for narrative state | **Missing** | Only `validate-content.js`; no debug route for behavior/phase state |
|
||||
|
||||
**Risk items:**
|
||||
- `ShiftReviewService.js` hardcodes `reviewer: 'Priya Kapoor'` and sends from `p.kapoor@axiomworks.internal`. This must be corrected to Priya Nair / `p.nair@axiomworks.internal` before shipping any new content.
|
||||
- `EmailService.js` CHARACTER_EMAILS has `priya: 'Priya Kapoor <p.kapoor@axiomworks.internal>'`. Same fix required.
|
||||
- `content/tickets/T007.json` may still reference the old Priya name (noted in CHARACTERS.md).
|
||||
- `content/docs/onboarding.json` may reference "Priya Kapoor" or "Priya Singh".
|
||||
|
||||
---
|
||||
|
||||
## 3. Gap Analysis
|
||||
|
||||
### Narrative phases
|
||||
**Gap:** No `narrative_phase` field on quest JSON. No runtime tracker. No API endpoint to query current phase. No phase-driven behavior changes (ticket wording hints, clue obviousness, boss mode).
|
||||
|
||||
### Behavior tracking (curiosity / obedience / risk)
|
||||
**Gap:** Completely absent. No service, no save state key, no UI, no branch-level behavior deltas applied at completion time.
|
||||
|
||||
### Access progression (basic_user / sudo / root)
|
||||
**Gap:** ProgressionSystem tracks opaque `unlocked_access` strings (like `"sudo:web_server:systemctl"`). The spec requires a named three-tier model. Currently trust gates access but suspicion does not.
|
||||
|
||||
### Boss/management pressure (phase-scaled)
|
||||
**Gap:** `IncidentScheduler` applies pressure per active quest, not per phase. There is no phase-keyed pressure mode. Kowalski is not implemented as an active character in any ticket or dialogue.
|
||||
|
||||
### Hidden hooks
|
||||
**Gap:** No `hidden_hook` field in quest JSON. No discovery state in save. No mechanism to record what the player found. The world_flags system *could* be used for discovery state (e.g., `hidden:dale_ssh_key_found`) but nothing does this yet.
|
||||
|
||||
### Endings
|
||||
**Gap:** Fully absent. No ending content, no EndingEvaluator, no condition set, no trigger. The four endings (corporate_loop, burnout, exposure, chaos) have no authored trigger criteria.
|
||||
|
||||
### Debug tooling
|
||||
**Gap:** Only `validate-content.js` for content authoring. No in-game or dev-API route to inspect: current behavior scores, narrative phase, suspicion level, hidden hooks discovered, ending trajectory.
|
||||
|
||||
### Validation of new schema fields
|
||||
**Gap:** `validate-content.js` does not check `narrative_phase`, `behavior_impact`, `hidden_hook`, `linux_concepts`, or `access_requirements`. New content will not be validated against these fields until the tool is updated.
|
||||
|
||||
### Name correction — Priya Nair
|
||||
**Gap (immediate):** Three files hardcode the wrong canonical name. Must be fixed before new content ships.
|
||||
|
||||
---
|
||||
|
||||
## 4. Minimal-Change Implementation Plan
|
||||
|
||||
**Philosophy:** Extend the existing system. Do not replace working services. New functionality adds new services and new save state keys. Existing content is not broken. New fields are optional until all content is updated.
|
||||
|
||||
---
|
||||
|
||||
### Task 1 — Repo inspection (complete, no edits)
|
||||
|
||||
Inspect the full codebase to confirm architecture, identify all files that reference Priya Kapoor, and establish baseline for subsequent tasks.
|
||||
|
||||
**Acceptance criteria:** Authored plan with confirmed file paths and line numbers.
|
||||
|
||||
---
|
||||
|
||||
### Task 2 — Extend quest schema and validation tooling
|
||||
|
||||
**What changes:**
|
||||
- Add `narrative_phase`, `behavior_impact`, `hidden_hook`, `linux_concepts`, `systems_used`, `failure_conditions`, `access_requirements` as optional fields to the quest JSON schema
|
||||
- Update `validate-content.js` to: warn when `narrative_phase` is absent, validate `narrative_phase` against the 6-value enum, check `behavior_impact` structure if present, validate `hidden_hook` shape if present, check `access_requirements.minimum_access` against known VM IDs
|
||||
- Add the 6 phase values as a declared constant in the validator
|
||||
|
||||
**Files changed:** `tools/content/validate-content.js`
|
||||
**Risk:** Low — additive only; existing quests with no new fields pass with warnings
|
||||
|
||||
---
|
||||
|
||||
### Task 3 — Behavior tracking service
|
||||
|
||||
**What changes:**
|
||||
- New service: `server/src/services/BehaviorTracker.js`
|
||||
- Tracks `curiosity`, `obedience`, `risk` as numeric values (0–100, start 50)
|
||||
- Method: `apply(behaviorImpact)` — adds branch-level deltas
|
||||
- Method: `getSnapshot()` — returns `{ curiosity, obedience, risk }`
|
||||
- Method: `initialize(state)` — loads from save state
|
||||
- Persists via `saveState.set({ behavior: ... })`
|
||||
- Emits `behavior:changed` event on change
|
||||
- Add `behavior` key to `SaveState._defaultState()` with schema_version bump to 3
|
||||
- `SaveState._applyDefaults()` already merges new keys safely — no migration needed for existing saves
|
||||
- Wire `behaviorTracker.initialize(state)` into `server/src/index.js` `initializeServices()`
|
||||
- Call `behaviorTracker.apply(branch.behavior_impact?.[branch.id] ?? branch.behavior_impact?.default ?? {})` inside `TicketService.markComplete()` after branch is selected
|
||||
|
||||
**Files changed:** `server/src/services/BehaviorTracker.js` (new), `server/src/services/SaveState.js`, `server/src/index.js`, `server/src/services/TicketService.js`
|
||||
**Risk:** Low — additive; behavior impact fields are optional in quest JSON so existing quests don't crash
|
||||
|
||||
---
|
||||
|
||||
### Task 4 — Narrative phase tracker
|
||||
|
||||
**What changes:**
|
||||
- New service: `server/src/services/NarrativePhaseTracker.js`
|
||||
- Maintains current phase as one of: `normal_work | unease | suspicion | investigation | conflict | resolution`
|
||||
- Phase is derived from completed quests: determined by the highest-phase quest completed so far
|
||||
- Method: `getPhase()` — returns current string
|
||||
- Method: `advance(questId)` — checks the completed quest's `narrative_phase` field and updates phase if it is higher on the spine
|
||||
- Method: `initialize(state)` — restores from `state.narrative_phase`
|
||||
- Persists via `saveState.set({ narrative_phase: ... })`
|
||||
- Emits `narrative:phase_changed` event
|
||||
- Add `narrative_phase` key to `SaveState._defaultState()` with value `'normal_work'`
|
||||
- Call `narrativePhaseTracker.advance(questId)` inside `QuestEngine.complete()` after state mutation
|
||||
- Expose `narrativePhase` in `/api/state` response (`server/src/routes/state.js`)
|
||||
|
||||
**Files changed:** `server/src/services/NarrativePhaseTracker.js` (new), `server/src/services/SaveState.js`, `server/src/services/QuestEngine.js`, `server/src/routes/state.js`, `server/src/index.js`
|
||||
**Risk:** Low — additive; quests without `narrative_phase` field default to `normal_work`, which never advances the tracker
|
||||
|
||||
---
|
||||
|
||||
### Task 5 — Hidden hook discovery state
|
||||
|
||||
**What changes:**
|
||||
- New save state key: `hidden_hooks_discovered` — array of hook IDs (strings)
|
||||
- `SaveState._defaultState()` adds `hidden_hooks_discovered: []`
|
||||
- New service: `server/src/services/HiddenHookTracker.js`
|
||||
- Method: `discover(hookId)` — adds hookId to discovered list, persists, emits `hidden_hook:discovered`
|
||||
- Method: `isDiscovered(hookId)` — boolean check
|
||||
- Method: `getDiscovered()` — returns array
|
||||
- Method: `initialize(state)` — restores from save
|
||||
- New API route (dev/admin only): `GET /api/debug/hidden-hooks` — returns discovered hooks and all declared hooks from quest JSON
|
||||
- `HiddenHook` discovery is triggered by the player finding specific files, users, or cron entries via terminal commands — the prep script seeds the artifact; the hook is discovered via a new optional validation check called on terminal activity, OR it can be registered as a special objective with `check_mode: "passive"` and `behavior_impact` of `curiosity: +2`
|
||||
|
||||
**Design note:** The simplest integration is: hidden hook discovery = passive objective with `hidden: true` flag. When a `hidden: true` objective validates, `HiddenHookTracker.discover()` is called instead of updating quest progress. This reuses the existing ValidationEngine without a new runtime mechanism.
|
||||
|
||||
**Files changed:** `server/src/services/HiddenHookTracker.js` (new), `server/src/services/SaveState.js`, `server/src/index.js`, `server/src/routes/state.js`
|
||||
**Risk:** Low — discovery mechanism is opt-in per quest
|
||||
|
||||
---
|
||||
|
||||
### Task 6 — Access level system
|
||||
|
||||
**What changes:**
|
||||
- Extend `ProgressionSystem` with a named three-tier concept:
|
||||
- `basic_user` — default, always available
|
||||
- `sudo` — granted by trust threshold (already exists as `unlocked_access` strings, just unnamed)
|
||||
- `root` — granted at higher trust threshold
|
||||
- Add `content/progression/access_levels.json` — defines access level thresholds (trust + suspicion gates)
|
||||
- Add `suspicion` key to `SaveState._defaultState()` with value `0`
|
||||
- Add `suspicion` tracking to `BehaviorTracker` (or a thin `SuspicionTracker`) — updated whenever `risk` behavior delta fires
|
||||
- Suspicion threshold: if `suspicion >= 70`, revoke certain access levels (mirror of trust revoke logic)
|
||||
- Add `access_level` computed field to `/api/state` response: `basic_user | sudo | root` based on current `unlocked_access` set
|
||||
- `trust_unlocks.json` entries can remain as-is; the `access_level` label is a derived label for UI/debug use
|
||||
|
||||
**Files changed:** `server/src/services/ProgressionSystem.js` (extend with `getAccessLevel()` helper), `server/src/services/SaveState.js`, `server/src/routes/state.js`, `content/progression/access_levels.json` (new)
|
||||
**Risk:** Medium — `suspicion` as an access gate requires careful tuning; start with suspicion as display-only, gate access only in Task 7 when boss pressure is wired
|
||||
|
||||
---
|
||||
|
||||
### Task 7 — Boss/management pressure (phase-scaled)
|
||||
|
||||
**What changes:**
|
||||
- Add `content/pressure_profiles/kowalski_phase_*.json` — 6 phase-keyed boss pressure profiles:
|
||||
- Phase 1: Annoying (routine status email)
|
||||
- Phase 2: Dismissive (reply-all on a ticket)
|
||||
- Phase 3: Suspicious (access review CC)
|
||||
- Phase 4: Monitoring (meeting scheduled)
|
||||
- Phase 5: Interfering (access restriction trigger)
|
||||
- Phase 6: Outcome-dependent (depends on world flags)
|
||||
- Extend `IncidentScheduler` to also process a `phase_pressure` tracker:
|
||||
- When `narrativePhaseTracker.getPhase()` changes, activate the matching phase pressure profile
|
||||
- Phase pressure escalation steps are sent as `emailService.send()` from Kowalski or Priya
|
||||
- Add `follow_up_mail` field support to incident escalation steps (already possible via `emailService.send()`)
|
||||
- Restrict access on phase 5 via `progressionSystem.revokeUnlock()` driven by a world flag set by phase 5 pressure
|
||||
|
||||
**Files changed:** `server/src/services/IncidentScheduler.js` (extend), `server/src/services/NarrativePhaseTracker.js` (emit event on change), `content/pressure_profiles/` (new files)
|
||||
**Risk:** Medium — phase pressure interacts with trust/suspicion; test pressure escalation in isolation before linking to access revoke
|
||||
|
||||
---
|
||||
|
||||
### Task 8 — Ending evaluation
|
||||
|
||||
**What changes:**
|
||||
- New service: `server/src/services/EndingEvaluator.js`
|
||||
- Evaluates the active ending route from world state at any time (not just at game end)
|
||||
- Method: `evaluate()` — returns the current ending label (`corporate_loop | burnout | exposure | chaos`) and a confidence object
|
||||
- Criteria (derived from SPEC_LOCK.md):
|
||||
- `exposure`: high curiosity, narrative_phase reached `investigation` or `conflict`, hidden hooks discovered ≥ N
|
||||
- `corporate_loop`: high obedience, low curiosity, trust > 70, few hidden hooks discovered
|
||||
- `burnout`: low obedience AND low curiosity, trust medium-low, many unresolved incidents
|
||||
- `chaos`: high risk, many negative trust_deltas, suspicion high, destructive world flags present
|
||||
- Method: `checkTrigger()` — called at quest completion; if conditions are fully met and phase = `resolution`, fires `ending:triggered` event
|
||||
- New API endpoint: `GET /api/debug/ending` — returns current ending trajectory (dev only)
|
||||
- The ending trigger should NOT be a single button. `EndingEvaluator` is called passively on `quest:completed` events.
|
||||
|
||||
**Files changed:** `server/src/services/EndingEvaluator.js` (new), `server/src/index.js`, `server/src/routes/state.js`
|
||||
**Risk:** Medium — ending criteria tuning requires extensive playtesting; ship as observable-only first, gate actual ending cutscene/screen behind a separate Task 10 content work
|
||||
|
||||
---
|
||||
|
||||
### Task 9 — Debug/dev tools
|
||||
|
||||
**What changes:**
|
||||
- New route file: `server/src/routes/debug.js` — only active when `NODE_ENV !== 'production'`
|
||||
- `GET /api/debug/state` — full save state dump
|
||||
- `GET /api/debug/behavior` — current behavior snapshot (curiosity/obedience/risk/suspicion)
|
||||
- `GET /api/debug/phase` — current narrative phase
|
||||
- `GET /api/debug/ending` — current ending trajectory
|
||||
- `GET /api/debug/hidden-hooks` — discovered + undiscovered hooks
|
||||
- `POST /api/debug/set-behavior` — override behavior variables (for testing branches)
|
||||
- `POST /api/debug/set-phase` — force a narrative phase (for testing phase-specific pressure)
|
||||
- `POST /api/debug/discover-hook/:id` — manually fire hook discovery (for testing)
|
||||
- Wire debug router into `server/src/index.js` behind `NODE_ENV` guard
|
||||
- Add a minimal debug panel to the frontend (dev only): collapsible overlay showing behavior, phase, ending trajectory — controlled by `?debug=1` query param
|
||||
|
||||
**Files changed:** `server/src/routes/debug.js` (new), `server/src/index.js`, `frontend/src/App.svelte` (conditional debug panel), `frontend/src/components/DebugPanel.svelte` (new)
|
||||
**Risk:** Low — debug routes are gated; frontend panel is conditional
|
||||
|
||||
---
|
||||
|
||||
### Task 10 — Content integration
|
||||
|
||||
**What changes:**
|
||||
- Add new fields to all 8 existing quests: `narrative_phase`, `behavior_impact`, `hidden_hook`, `linux_concepts`, `failure_conditions`, `access_requirements`
|
||||
- Fix Priya's name in: `server/src/services/ShiftReviewService.js`, `server/src/services/EmailService.js`, `content/tickets/T007.json`, `content/docs/onboarding.json`
|
||||
- Register any new world flags needed by the new fields in `content/world_flags/world_flags.json`
|
||||
- Author the first hidden hooks as passive objectives in Q005–Q008 (per STORY_DESIGN_CONTEXT.md: every 3–5 quests)
|
||||
- Add phase-pressure content files for phases 1–3 (phases 4–6 are content-authored later as story expands)
|
||||
- Author Kowalski as a pressure sender in the phase 2 and 3 profiles
|
||||
|
||||
**Files changed:** All 8 quest JSONs, `content/tickets/T007.json`, `content/docs/onboarding.json`, `server/src/services/ShiftReviewService.js`, `server/src/services/EmailService.js`, `content/world_flags/world_flags.json`, `content/pressure_profiles/` (new files)
|
||||
**Risk:** Medium — touching all quest files; run `validate-content.js` after every file change
|
||||
|
||||
---
|
||||
|
||||
### Task 11 — Validation and tests
|
||||
|
||||
**What changes:**
|
||||
- Update `validate-content.js`:
|
||||
- Error on unrecognized `narrative_phase` value
|
||||
- Warn on missing `narrative_phase`
|
||||
- Validate `behavior_impact` structure (numeric deltas)
|
||||
- Validate `hidden_hook` structure if present
|
||||
- Warn if `linux_concepts` is empty
|
||||
- Check `access_requirements.minimum_access` values against known VM IDs
|
||||
- Add unit tests:
|
||||
- `BehaviorTracker.test.js` — apply deltas, persistence, initialize from state
|
||||
- `NarrativePhaseTracker.test.js` — advance rules, phase ordering, initialize
|
||||
- `EndingEvaluator.test.js` — all 4 endings, boundary conditions
|
||||
- `HiddenHookTracker.test.js` — discover, isDiscovered, persistence
|
||||
- Extend existing tests:
|
||||
- `ValidationEngine.test.js` — confirm hidden objectives with `hidden: true` don't affect normal branch resolution
|
||||
- `TicketService.test.js` — confirm `behavior_impact` is applied at completion, confirm no-op when field absent
|
||||
- Manual test checklist (see Task 11 Codex prompt)
|
||||
|
||||
**Files changed:** `tools/content/validate-content.js`, `server/src/services/BehaviorTracker.test.js` (new), `server/src/services/NarrativePhaseTracker.test.js` (new), `server/src/services/EndingEvaluator.test.js` (new), `server/src/services/HiddenHookTracker.test.js` (new)
|
||||
**Risk:** Low — tests are additive
|
||||
|
||||
---
|
||||
|
||||
## 5. Files Likely to Change
|
||||
|
||||
| File | Why | What changes | Risk |
|
||||
|---|---|---|---|
|
||||
| `server/src/services/SaveState.js` | New save keys needed | Add `behavior`, `narrative_phase`, `suspicion`, `hidden_hooks_discovered` to `_defaultState()`; bump `schema_version` to 3 | Low — `_applyDefaults` merges safely |
|
||||
| `server/src/services/QuestEngine.js` | Phase advancement hook | Call `narrativePhaseTracker.advance()` in `complete()`; import new service | Low |
|
||||
| `server/src/services/TicketService.js` | Behavior application | Call `behaviorTracker.apply()` after branch selection in `markComplete()` | Low — branch.behavior_impact is optional |
|
||||
| `server/src/services/ShiftReviewService.js` | Name correction | Change `'Priya Kapoor'` to `'Priya Nair'`; fix `p.kapoor` to `p.nair` in email From line | Low — one-liner |
|
||||
| `server/src/services/EmailService.js` | Name correction | Change `CHARACTER_EMAILS.priya` to `'Priya Nair <p.nair@axiomworks.internal>'` | Low — one-liner |
|
||||
| `server/src/services/IncidentScheduler.js` | Phase pressure | Add `_processPhasePresure()` method triggered by phase change event | Medium |
|
||||
| `server/src/services/ProgressionSystem.js` | Access level label | Add `getAccessLevel()` that derives `basic_user | sudo | root` from current `unlocked_access` set | Low |
|
||||
| `server/src/routes/state.js` | Expose new state | Add `behavior`, `narrativePhase`, `accessLevel`, `suspicion` to GET /api/state response | Low |
|
||||
| `server/src/index.js` | Wire new services | Import and `initialize()` new services in the correct order; add debug router | Low |
|
||||
| `tools/content/validate-content.js` | Validate new schema fields | Add phase enum check, behavior_impact structure check, hidden_hook shape check | Low — additive |
|
||||
| `content/world_flags/world_flags.json` | New flags needed | Add entries for any new flags emitted by hidden hooks and phase pressure profiles | Low |
|
||||
| `content/tickets/T007.json` | Priya name | Update `from` field if it uses old email | Low |
|
||||
| `content/docs/onboarding.json` | Priya name | Update any references to Priya Kapoor or Priya Singh | Low |
|
||||
| All 8 quest JSONs | New fields | Add `narrative_phase`, `behavior_impact`, `hidden_hook`, `linux_concepts`, `failure_conditions`, `access_requirements` | Medium — large surface |
|
||||
|
||||
---
|
||||
|
||||
## 6. Files Likely to Be Added
|
||||
|
||||
| File | Purpose | Expected structure |
|
||||
|---|---|---|
|
||||
| `server/src/services/BehaviorTracker.js` | Track curiosity/obedience/risk/suspicion | Class with `initialize()`, `apply(impact)`, `getSnapshot()`, `_persist()` |
|
||||
| `server/src/services/NarrativePhaseTracker.js` | Track and advance narrative phase | Class with `initialize()`, `advance(questId)`, `getPhase()`, `_persist()` |
|
||||
| `server/src/services/HiddenHookTracker.js` | Record hidden hook discoveries | Class with `initialize()`, `discover(id)`, `isDiscovered(id)`, `getDiscovered()` |
|
||||
| `server/src/services/EndingEvaluator.js` | Evaluate ending trajectory from world state | Class with `evaluate()`, `checkTrigger()`, pure computation over save state snapshot |
|
||||
| `server/src/routes/debug.js` | Dev-only debug API | Express router, gated on `NODE_ENV !== 'production'` |
|
||||
| `frontend/src/components/DebugPanel.svelte` | Dev-only debug overlay | Collapsible panel, shown on `?debug=1`, polling `/api/debug/state` |
|
||||
| `content/progression/access_levels.json` | Named access level threshold definitions | Array of `{ level, trust_threshold, suspicion_ceiling, grants, revokes }` |
|
||||
| `content/pressure_profiles/kowalski_phase_1.json` | Phase 1 boss pressure | `escalation_steps` with Kowalski emails at time thresholds |
|
||||
| `content/pressure_profiles/kowalski_phase_2.json` | Phase 2 boss pressure | Dismissive Kowalski CC patterns |
|
||||
| `content/pressure_profiles/kowalski_phase_3.json` | Phase 3 boss pressure | Suspicious Kowalski, Priya CC |
|
||||
| `server/src/services/BehaviorTracker.test.js` | Unit tests for BehaviorTracker | Jest test file using existing `IncidentScheduler.test.js` as pattern |
|
||||
| `server/src/services/NarrativePhaseTracker.test.js` | Unit tests for NarrativePhaseTracker | Jest test file |
|
||||
| `server/src/services/EndingEvaluator.test.js` | Unit tests for EndingEvaluator | Jest test file, covers all 4 endings |
|
||||
| `server/src/services/HiddenHookTracker.test.js` | Unit tests for HiddenHookTracker | Jest test file |
|
||||
|
||||
---
|
||||
|
||||
## 7. Data Migration Plan
|
||||
|
||||
### Existing quests (Q001–Q008)
|
||||
|
||||
**Strategy: Wrap into new schema (backward-compatible extension)**
|
||||
|
||||
- Do NOT replace existing quests. Do NOT create a "legacy" tier.
|
||||
- Add new fields to each existing quest file. The fields are additive.
|
||||
- `ContentLoader.js` already loads all quest files and passes them to `QuestEngine`. New fields are simply available at resolution time.
|
||||
- Missing new fields in old quests: the runtime treats `narrative_phase: undefined` as `normal_work`; `behavior_impact: undefined` as no behavior change; `hidden_hook: null` as no hook.
|
||||
- This means existing quests continue to work with zero runtime errors before Task 10 runs.
|
||||
|
||||
### Save state migration
|
||||
|
||||
- `schema_version` bumps from `2` to `3`
|
||||
- `SaveState._applyDefaults()` already merges new keys safely: old saves that lack `behavior`, `narrative_phase`, `suspicion`, `hidden_hooks_discovered` will receive the default values (`50/50/50`, `'normal_work'`, `0`, `[]`) on next load
|
||||
- No destructive migration. No migration script needed.
|
||||
- Old saves loaded under the new schema will behave as if the player is in Phase 1 with neutral behavior — which is correct for a save that predates the new system.
|
||||
|
||||
### Tickets, dialogue, incidents
|
||||
|
||||
- No migration needed. Existing files continue to load and function.
|
||||
- New dialogue files for phase pressure and boss escalation are additive.
|
||||
|
||||
---
|
||||
|
||||
## 8. Testing Plan
|
||||
|
||||
### Unit tests (new)
|
||||
|
||||
| Test file | What it covers |
|
||||
|---|---|
|
||||
| `BehaviorTracker.test.js` | Delta application, clamping (0–100), initialize from state, persist, event emission |
|
||||
| `NarrativePhaseTracker.test.js` | Phase ordering (spine), advance-only-forward rule, initialize from state, persist |
|
||||
| `EndingEvaluator.test.js` | All 4 endings by state construction, boundary conditions, tie-break rules |
|
||||
| `HiddenHookTracker.test.js` | Discover, isDiscovered, idempotent discover, initialize from state |
|
||||
|
||||
### Integration tests (extend existing)
|
||||
|
||||
| Test | Assertion |
|
||||
|---|---|
|
||||
| `TicketService.test.js` — behavior applied | After `markComplete`, save state `behavior.curiosity` changes by branch delta |
|
||||
| `TicketService.test.js` — behavior absent | Quest with no `behavior_impact` completes without error |
|
||||
| `ValidationEngine.test.js` — hidden objective | `hidden: true` objective validates passively without blocking branch resolution |
|
||||
| `IncidentScheduler.test.js` — phase pressure | Phase change event triggers correct pressure profile activation |
|
||||
|
||||
### Save/load compatibility checks
|
||||
|
||||
- Load an existing (schema_version 2) save: all new keys initialized to defaults, no error
|
||||
- Complete a new quest with new schema fields: save state includes correct behavior deltas
|
||||
- Restart server with schema_version 3 save: all new keys correctly restored
|
||||
- Test `SAVE_DIR` override with new schema
|
||||
|
||||
### Manual test checklist
|
||||
|
||||
1. Complete Q001 clean fix → confirm `player_ssh_configured` flag set, trust = 53
|
||||
2. Complete Q001 brittle fix → confirm trust penalty, `player_loose_permissions` flag set
|
||||
3. After any quest completion → confirm `behavior` object in `/api/state` (via debug route) has changed
|
||||
4. With `?debug=1` → confirm debug panel visible in frontend
|
||||
5. Complete Q001–Q003 → confirm narrative phase advances from `normal_work`
|
||||
6. Navigate terminal to a hidden anomaly (e.g., unknown user in `/etc/passwd`) → confirm `/api/debug/hidden-hooks` shows new entry
|
||||
7. Force phase 3 via debug route → confirm Kowalski pressure profile activates
|
||||
8. Force behavior state to `{ curiosity: 80, obedience: 20, risk: 30 }` + reach resolution phase → confirm EndingEvaluator returns `exposure`
|
||||
9. Force behavior state to `{ curiosity: 20, obedience: 80, risk: 20 }` + reach resolution phase → confirm `corporate_loop`
|
||||
10. Run `node tools/content/validate-content.js` — zero errors with all existing + updated quests
|
||||
11. Run `npm test` — all existing tests pass; all new unit tests pass
|
||||
|
||||
### Content validation checks
|
||||
|
||||
- After Task 10: run `validate-content.js --verbose` on all 8 updated quests
|
||||
- Confirm all new `narrative_phase` values are valid enum members
|
||||
- Confirm all new `behavior_impact` fields have numeric deltas
|
||||
- Confirm no undeclared world flags introduced
|
||||
- Confirm all `hidden_hook` IDs are unique across quests
|
||||
|
||||
---
|
||||
|
||||
## 9. Codex Delegation Prompts
|
||||
|
||||
### Task 2 — Extend validate-content.js
|
||||
|
||||
```
|
||||
File: tools/content/validate-content.js
|
||||
|
||||
Extend the existing content validation tool. Do not change any existing checks. Add these new checks after the existing quest validation block:
|
||||
|
||||
1. Define a constant at the top of the file:
|
||||
const VALID_NARRATIVE_PHASES = new Set(["normal_work","unease","suspicion","investigation","conflict","resolution"]);
|
||||
|
||||
2. In the quest validation loop (the `for (const [qid, { data: quest, fname }] of Object.entries(quests))` block), add after the existing checks:
|
||||
|
||||
// narrative_phase
|
||||
if (!quest.narrative_phase) {
|
||||
warn(`${ctx}: missing 'narrative_phase' field`);
|
||||
} else if (!VALID_NARRATIVE_PHASES.has(quest.narrative_phase)) {
|
||||
err(`${ctx}: unknown narrative_phase '${quest.narrative_phase}'`);
|
||||
}
|
||||
|
||||
// behavior_impact
|
||||
if (quest.behavior_impact !== undefined) {
|
||||
for (const [branchKey, impact] of Object.entries(quest.behavior_impact)) {
|
||||
for (const field of ['curiosity_delta','obedience_delta','risk_delta','suspicion_delta']) {
|
||||
if (impact[field] !== undefined && typeof impact[field] !== 'number') {
|
||||
err(`${ctx}: behavior_impact[${branchKey}].${field} must be a number`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hidden_hook shape (if present and not null)
|
||||
if (quest.hidden_hook !== undefined && quest.hidden_hook !== null) {
|
||||
if (typeof quest.hidden_hook.id !== 'string') {
|
||||
err(`${ctx}: hidden_hook.id must be a string`);
|
||||
}
|
||||
}
|
||||
|
||||
// access_requirements
|
||||
if (quest.access_requirements?.minimum_access) {
|
||||
for (const [vmId] of Object.entries(quest.access_requirements.minimum_access)) {
|
||||
if (!vmProfiles[vmId]) {
|
||||
err(`${ctx}: access_requirements.minimum_access references unknown VM '${vmId}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Acceptance criteria:
|
||||
- `node tools/content/validate-content.js` runs without JS errors
|
||||
- Existing quest files produce only warnings for missing narrative_phase, not errors
|
||||
- A test quest with narrative_phase: "invalid_phase" produces one error
|
||||
- All other existing checks continue to pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3 — BehaviorTracker service
|
||||
|
||||
```
|
||||
Create file: server/src/services/BehaviorTracker.js
|
||||
|
||||
Use ES module syntax (import/export) matching the existing service style (see SaveState.js and TrustSystem.js as patterns).
|
||||
|
||||
The class must:
|
||||
- Store { curiosity, obedience, risk, suspicion } — all numeric 0–100, starting at 50/50/50/0
|
||||
- initialize(state): load from state.behavior (use defaults if absent)
|
||||
- apply(impact): accept an object with optional fields { curiosity_delta, obedience_delta, risk_delta, suspicion_delta }, add each to the corresponding score, clamp to [0,100], persist, emit 'behavior:changed' via eventBus
|
||||
- getSnapshot(): return a plain { curiosity, obedience, risk, suspicion } object
|
||||
- _persist(): call saveState.set({ behavior: this.getSnapshot() })
|
||||
|
||||
Export a singleton: export const behaviorTracker = new BehaviorTracker();
|
||||
|
||||
Then make these changes:
|
||||
|
||||
1. In server/src/services/SaveState.js, in _defaultState(), add this key alongside the existing ones:
|
||||
behavior: { curiosity: 50, obedience: 50, risk: 50, suspicion: 0 },
|
||||
and change schema_version from 2 to 3.
|
||||
|
||||
2. In server/src/index.js, import behaviorTracker from './services/BehaviorTracker.js' and add behaviorTracker.initialize(state) in initializeServices() after trustSystem.initialize(state).
|
||||
|
||||
3. In server/src/services/TicketService.js, in the markComplete() method, after the line `questEngine.complete(quest.id, { branchId: branch.id });`, add:
|
||||
const behaviorImpact = branch.behavior_impact ?? quest.behavior_impact?.default ?? quest.behavior_impact ?? null;
|
||||
if (behaviorImpact) { behaviorTracker.apply(behaviorImpact); }
|
||||
(Add the import at the top of the file.)
|
||||
|
||||
Acceptance criteria:
|
||||
- npm test passes (existing tests unchanged)
|
||||
- GET /api/debug/state (if debug route exists) shows behavior object
|
||||
- After completing a quest whose branch has behavior_impact.curiosity_delta: 2, the save.json shows behavior.curiosity incremented by 2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4 — NarrativePhaseTracker service
|
||||
|
||||
```
|
||||
Create file: server/src/services/NarrativePhaseTracker.js
|
||||
|
||||
Use ES module syntax matching existing service patterns.
|
||||
|
||||
Phase ordering (spine): normal_work < unease < suspicion < investigation < conflict < resolution
|
||||
|
||||
The class must:
|
||||
- Store _phase as a string, initialized from state.narrative_phase or defaulting to 'normal_work'
|
||||
- PHASE_ORDER constant: ['normal_work','unease','suspicion','investigation','conflict','resolution']
|
||||
- initialize(state): restore _phase from state.narrative_phase
|
||||
- advance(questId): look up the quest from contentLoader, read its narrative_phase field; if the quest's phase rank is strictly higher than current phase rank, update _phase, persist, emit 'narrative:phase_changed' event with { from, to }; if narrative_phase field is absent or undefined, do nothing
|
||||
- getPhase(): return current _phase string
|
||||
- _persist(): saveState.set({ narrative_phase: this._phase })
|
||||
|
||||
Export singleton: export const narrativePhaseTracker = new NarrativePhaseTracker();
|
||||
|
||||
Then make these changes:
|
||||
|
||||
1. In server/src/services/SaveState.js _defaultState(), add:
|
||||
narrative_phase: 'normal_work',
|
||||
|
||||
2. In server/src/services/QuestEngine.js complete() method, after this._persist(), add:
|
||||
narrativePhaseTracker.advance(questId);
|
||||
(Add the import at top of file.)
|
||||
|
||||
3. In server/src/routes/state.js, add narrativePhase: narrativePhaseTracker.getPhase() to the GET / response object.
|
||||
Import narrativePhaseTracker at top of the file.
|
||||
|
||||
4. In server/src/index.js, import and initialize narrativePhaseTracker in initializeServices() after questEngine.initialize(state).
|
||||
|
||||
Acceptance criteria:
|
||||
- npm test passes
|
||||
- After completing Q001, GET /api/state returns narrativePhase: 'normal_work'
|
||||
- If a quest with narrative_phase: 'unease' is completed after Q001, GET /api/state returns narrativePhase: 'unease'
|
||||
- Phase never goes backward: completing a 'normal_work' quest after an 'unease' quest does not revert the phase
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5 — HiddenHookTracker service
|
||||
|
||||
```
|
||||
Create file: server/src/services/HiddenHookTracker.js
|
||||
|
||||
ES module syntax, matching existing service patterns.
|
||||
|
||||
The class must:
|
||||
- Store _discovered as a Set of hook ID strings
|
||||
- initialize(state): load from state.hidden_hooks_discovered (array), build Set
|
||||
- discover(hookId): if not already discovered, add to Set, persist, emit 'hidden_hook:discovered' with { hookId }; idempotent if already discovered
|
||||
- isDiscovered(hookId): boolean
|
||||
- getDiscovered(): return [...this._discovered] sorted
|
||||
- _persist(): saveState.set({ hidden_hooks_discovered: [...this._discovered] })
|
||||
|
||||
Export singleton: export const hiddenHookTracker = new HiddenHookTracker();
|
||||
|
||||
Then:
|
||||
|
||||
1. In server/src/services/SaveState.js _defaultState(), add:
|
||||
hidden_hooks_discovered: [],
|
||||
|
||||
2. In server/src/index.js, import and call hiddenHookTracker.initialize(state) in initializeServices().
|
||||
|
||||
3. In server/src/routes/state.js, add hiddenHooksDiscovered: hiddenHookTracker.getDiscovered() to the response.
|
||||
|
||||
Acceptance criteria:
|
||||
- npm test passes
|
||||
- POST /api/debug/discover-hook/test-hook (if debug route exists) adds 'test-hook' to state
|
||||
- GET /api/state returns hiddenHooksDiscovered: ['test-hook']
|
||||
- Calling discover() twice with the same ID results in exactly one entry in the array
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6 — Access level extension
|
||||
|
||||
```
|
||||
Make these targeted changes to existing files:
|
||||
|
||||
1. In server/src/services/ProgressionSystem.js, add this method to the ProgressionSystem class:
|
||||
getAccessLevel() {
|
||||
if (this._access.has('sudo:workstation:full') || this._access.has('sudo:web_server:full') || this._access.has('sudo:build_machine:full')) {
|
||||
return 'root';
|
||||
}
|
||||
if (this._access.has('sudo:workstation:systemctl') || this._access.has('ssh:web_server') || this._access.has('ssh:build_machine')) {
|
||||
return 'sudo';
|
||||
}
|
||||
return 'basic_user';
|
||||
}
|
||||
|
||||
2. In server/src/routes/state.js, add to the GET / response:
|
||||
accessLevel: progressionSystem.getAccessLevel(),
|
||||
Import progressionSystem if not already imported.
|
||||
|
||||
3. Create file: content/progression/access_levels.json with this content:
|
||||
{
|
||||
"_description": "Named access level definitions. Derived from ProgressionSystem unlocked_access keys.",
|
||||
"levels": [
|
||||
{ "name": "basic_user", "description": "Default access. Workstation only. No sudo." },
|
||||
{ "name": "sudo", "description": "Sudo on workstation; SSH to hermes or vulcan." },
|
||||
{ "name": "root", "description": "Full sudo on at least one remote host." }
|
||||
]
|
||||
}
|
||||
|
||||
Acceptance criteria:
|
||||
- npm test passes
|
||||
- GET /api/state returns accessLevel: 'basic_user' for a fresh save
|
||||
- After trust reaches 55, accessLevel returns 'sudo'
|
||||
- After trust reaches 60 and sudo:web_server:full is granted, accessLevel returns 'root'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7 — Phase pressure content files
|
||||
|
||||
```
|
||||
Create three new pressure profile files in content/pressure_profiles/:
|
||||
|
||||
File: content/pressure_profiles/kowalski_phase_1.json
|
||||
Content:
|
||||
{
|
||||
"id": "kowalski_phase_1",
|
||||
"label": "Dave Kowalski — Phase 1: Routine Pressure",
|
||||
"description": "Normal managerial check-ins. Annoying but not threatening.",
|
||||
"trigger_phase": "normal_work",
|
||||
"escalation_steps": [
|
||||
{
|
||||
"trigger_after_seconds": 300,
|
||||
"notification": "Quick check-in — how are you getting on with the ticket queue? Let me know if anything is blocking you. Dave K.",
|
||||
"notification_severity": "info",
|
||||
"sender": "Dave Kowalski <d.kowalski@axiomworks.internal>",
|
||||
"subject": "Status check"
|
||||
},
|
||||
{
|
||||
"trigger_after_seconds": 600,
|
||||
"notification": "Following up on my earlier note. We should really document that workflow once you get a moment.",
|
||||
"notification_severity": "info",
|
||||
"sender": "Dave Kowalski <d.kowalski@axiomworks.internal>",
|
||||
"subject": "Re: Status check"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
File: content/pressure_profiles/kowalski_phase_2.json
|
||||
Content:
|
||||
{
|
||||
"id": "kowalski_phase_2",
|
||||
"label": "Dave Kowalski — Phase 2: Dismissive",
|
||||
"description": "Kowalski is aware something is recurring. Manages upward, not inward.",
|
||||
"trigger_phase": "unease",
|
||||
"escalation_steps": [
|
||||
{
|
||||
"trigger_after_seconds": 180,
|
||||
"notification": "I've had a couple of questions from Sarah's team about stability. Nothing critical, but let's make sure we're on top of it. Noted for the weekly update. D.",
|
||||
"notification_severity": "info",
|
||||
"sender": "Dave Kowalski <d.kowalski@axiomworks.internal>",
|
||||
"subject": "FYI — product team questions"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
File: content/pressure_profiles/kowalski_phase_3.json
|
||||
Content:
|
||||
{
|
||||
"id": "kowalski_phase_3",
|
||||
"label": "Dave Kowalski — Phase 3: Suspicious",
|
||||
"description": "Kowalski is getting questions from above. Starts involving Priya.",
|
||||
"trigger_phase": "suspicion",
|
||||
"escalation_steps": [
|
||||
{
|
||||
"trigger_after_seconds": 120,
|
||||
"notification": "I've scheduled a brief sync for Thursday to talk through recent changes on the infrastructure side. Priya will join. Nothing to worry about — just a routine review.",
|
||||
"notification_severity": "warning",
|
||||
"sender": "Dave Kowalski <d.kowalski@axiomworks.internal>",
|
||||
"subject": "Thursday sync — infra review"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Acceptance criteria:
|
||||
- node tools/content/validate-content.js passes with no new errors
|
||||
- All three files have unique 'id' fields that pass content loader's ID detection
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8 — EndingEvaluator service
|
||||
|
||||
```
|
||||
Create file: server/src/services/EndingEvaluator.js
|
||||
|
||||
ES module syntax.
|
||||
|
||||
ENDING_CRITERIA constant (all conditions must be met for that ending to be active):
|
||||
- exposure: curiosity >= 65, hidden_hooks_discovered.length >= 2, narrative_phase rank >= 'investigation'
|
||||
- corporate_loop: obedience >= 65, curiosity <= 40, trust >= 65
|
||||
- burnout: curiosity <= 35, obedience <= 40 (passive disengagement)
|
||||
- chaos: risk >= 65, trust <= 40
|
||||
|
||||
The class must:
|
||||
- evaluate(): read current saveState, compute which endings' criteria are met, return { active: 'exposure'|'corporate_loop'|'burnout'|'chaos'|'undetermined', candidates: [...] } — if multiple match, prefer in this order: exposure > chaos > corporate_loop > burnout
|
||||
- checkTrigger(): call evaluate(); if narrative_phase is 'resolution' and active is not 'undetermined', emit 'ending:triggered' with { ending: active }; return the result
|
||||
|
||||
PHASE_RANK constant: { normal_work:0, unease:1, suspicion:2, investigation:3, conflict:4, resolution:5 }
|
||||
|
||||
Import saveState, narrativePhaseTracker, hiddenHookTracker, behaviorTracker.
|
||||
|
||||
Export singleton: export const endingEvaluator = new EndingEvaluator();
|
||||
|
||||
Wire into index.js: import endingEvaluator; add endingEvaluator (no initialize needed, it reads state on demand).
|
||||
|
||||
Listen for 'quest:completed' on eventBus: call endingEvaluator.checkTrigger() each time.
|
||||
|
||||
Acceptance criteria:
|
||||
- npm test passes
|
||||
- evaluate() with curiosity=70, hiddenHooksDiscovered=['h1','h2'], phase='investigation' returns active: 'exposure'
|
||||
- evaluate() with obedience=70, curiosity=35, trust=70 returns active: 'corporate_loop'
|
||||
- evaluate() with no conditions met returns active: 'undetermined'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 9 — Debug routes and frontend panel
|
||||
|
||||
```
|
||||
Create file: server/src/routes/debug.js
|
||||
|
||||
ES module syntax. Only register routes if process.env.NODE_ENV !== 'production'.
|
||||
|
||||
Routes:
|
||||
GET /api/debug/state — return full saveState.get()
|
||||
GET /api/debug/behavior — return behaviorTracker.getSnapshot()
|
||||
GET /api/debug/phase — return { phase: narrativePhaseTracker.getPhase() }
|
||||
GET /api/debug/ending — return endingEvaluator.evaluate()
|
||||
GET /api/debug/hidden-hooks — return { discovered: hiddenHookTracker.getDiscovered(), total: N }
|
||||
POST /api/debug/set-behavior — body: { curiosity, obedience, risk, suspicion }; call behaviorTracker._override(body) (add _override method that directly sets values without deltas)
|
||||
POST /api/debug/set-phase — body: { phase }; if valid phase, directly set _phase on narrativePhaseTracker and persist (add _forcePhase method)
|
||||
POST /api/debug/discover-hook/:id — call hiddenHookTracker.discover(req.params.id); return getDiscovered()
|
||||
|
||||
In server/src/index.js, add:
|
||||
import debugRouter from './routes/debug.js';
|
||||
// After the other app.use() calls:
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
app.use('/api/debug', debugRouter);
|
||||
}
|
||||
|
||||
Create file: frontend/src/components/DebugPanel.svelte
|
||||
- Shows only when window.location.search includes 'debug=1'
|
||||
- Polls GET /api/debug/behavior, GET /api/debug/phase, GET /api/debug/ending every 5 seconds
|
||||
- Displays: behavior scores (curiosity/obedience/risk/suspicion), current phase, ending trajectory
|
||||
- Minimal styling: position fixed, bottom right, semi-transparent, small font
|
||||
|
||||
In frontend/src/App.svelte, import DebugPanel and conditionally render it:
|
||||
{#if showDebug}
|
||||
<DebugPanel />
|
||||
{/if}
|
||||
Add: const showDebug = new URLSearchParams(window.location.search).has('debug');
|
||||
|
||||
Acceptance criteria:
|
||||
- npm test passes
|
||||
- In development: GET /api/debug/behavior returns behavior snapshot
|
||||
- Visiting /?debug=1 shows the debug panel in the browser
|
||||
- In production (NODE_ENV=production): GET /api/debug/behavior returns 404
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 10 — Fix Priya's name and update Q001–Q008
|
||||
|
||||
```
|
||||
Part A — Fix Priya's name. Make these exact changes:
|
||||
|
||||
1. In server/src/services/EmailService.js, find this line:
|
||||
priya: 'Priya Kapoor <p.kapoor@axiomworks.internal>',
|
||||
Change it to:
|
||||
priya: 'Priya Nair <p.nair@axiomworks.internal>',
|
||||
|
||||
2. In server/src/services/ShiftReviewService.js:
|
||||
a. Find: reviewer: 'Priya Kapoor'
|
||||
Change to: reviewer: 'Priya Nair'
|
||||
b. Find: from: 'Priya Kapoor <p.kapoor@axiomworks.internal>'
|
||||
Change to: from: 'Priya Nair <p.nair@axiomworks.internal>'
|
||||
|
||||
3. In content/tickets/T007.json: if the 'from' or 'body' field contains 'Priya Kapoor', 'p.kapoor', or 'Priya Singh', replace with 'Priya Nair' and 'p.nair@axiomworks.internal'.
|
||||
|
||||
4. In content/docs/onboarding.json: if 'Priya Kapoor' or 'Priya Singh' appears, replace with 'Priya Nair'.
|
||||
|
||||
Part B — Add new fields to existing quests. For each quest Q001–Q008, add these fields using the values in the table below. Do not change any existing fields. Do not reformat the JSON beyond what is needed to add the new fields.
|
||||
|
||||
Q001: narrative_phase: "normal_work", linux_concepts: ["ssh-keygen","authorized_keys","file permissions"], failure_conditions: ["SSH keys not added","authorized_keys permissions too broad"], behavior_impact: { "correct-key": { curiosity_delta: 0, obedience_delta: 1, risk_delta: 0, suspicion_delta: 0 }, "loose-permissions": { curiosity_delta: 0, obedience_delta: 0, risk_delta: 1, suspicion_delta: 1 }, default: { curiosity_delta: 0, obedience_delta: 0, risk_delta: 0, suspicion_delta: 0 } }, hidden_hook: null, access_requirements: { minimum_access: { workstation: "basic_user" }, requires_root: false, temporary_grants_allowed: [] }
|
||||
|
||||
Q002: narrative_phase: "normal_work", linux_concepts: ["nginx","systemctl","sshd_config"], failure_conditions: ["nginx not running","service not enabled at boot"], behavior_impact: { default: { curiosity_delta: 0, obedience_delta: 1, risk_delta: 0, suspicion_delta: 0 } }, hidden_hook: null, access_requirements: { minimum_access: { web_server: "basic_user" }, requires_root: false, temporary_grants_allowed: [] }
|
||||
|
||||
Q003: narrative_phase: "normal_work", linux_concepts: ["logrotate","disk usage","df","du"], failure_conditions: ["disk still above threshold","logrotate not restored"], behavior_impact: { default: { curiosity_delta: 0, obedience_delta: 1, risk_delta: 0, suspicion_delta: 0 } }, hidden_hook: null, access_requirements: { minimum_access: { web_server: "sudo" }, requires_root: false, temporary_grants_allowed: [] }
|
||||
|
||||
Q004: narrative_phase: "normal_work", linux_concepts: ["chown","file ownership","deploy scripts"], failure_conditions: ["web root ownership not fixed","deploy service still failing"], behavior_impact: { default: { curiosity_delta: 0, obedience_delta: 1, risk_delta: 0, suspicion_delta: 0 } }, hidden_hook: null, access_requirements: { minimum_access: { web_server: "sudo" }, requires_root: false, temporary_grants_allowed: [] }
|
||||
|
||||
Q005: narrative_phase: "unease", linux_concepts: ["cron","crontab","user field","backup management"], failure_conditions: ["cron still running as root","disk not cleared","backup directory ownership not fixed"], behavior_impact: { "full-fix": { curiosity_delta: 1, obedience_delta: 1, risk_delta: 0, suspicion_delta: 0 }, "cron-fixed-only": { curiosity_delta: 0, obedience_delta: 1, risk_delta: 0, suspicion_delta: 0 }, "disk-cleared-only": { curiosity_delta: 0, obedience_delta: 0, risk_delta: 1, suspicion_delta: 1 }, default: { curiosity_delta: 0, obedience_delta: 0, risk_delta: 0, suspicion_delta: 0 } }, hidden_hook: { "id": "q005_backup_agent_history", "description": "backup-agent home directory contains a .bash_history with unusual commands that predate the current cron misconfiguration.", "discovery_method": "Player reads /home/backup-agent/.bash_history", "significance": "Dale configured this cron job. The history shows it was changed deliberately, not by accident." }, access_requirements: { minimum_access: { web_server: "sudo" }, requires_root: false, temporary_grants_allowed: [] }
|
||||
|
||||
Q006: narrative_phase: "unease", linux_concepts: ["NTP","systemd-timesyncd","Arch Linux","pacman","package keys"], failure_conditions: ["NTP not enabled at boot","package manager still broken"], behavior_impact: { default: { curiosity_delta: 0, obedience_delta: 1, risk_delta: 0, suspicion_delta: 0 } }, hidden_hook: null, access_requirements: { minimum_access: { build_machine: "sudo" }, requires_root: false, temporary_grants_allowed: [] }
|
||||
|
||||
Q007: narrative_phase: "suspicion", linux_concepts: ["sshd_config","AllowGroups","AllowUsers","access hardening"], failure_conditions: ["Priya still locked out","SSH restrictions removed entirely"], behavior_impact: { default: { curiosity_delta: 1, obedience_delta: 0, risk_delta: 0, suspicion_delta: 0 } }, hidden_hook: { "id": "q007_dale_ssh_key", "description": "An SSH key in hermes /root/.ssh/authorized_keys does not match any current staff. The fingerprint matches no documented key.", "discovery_method": "Player reads /root/.ssh/authorized_keys on hermes", "significance": "Dale had root SSH access to hermes that was never formally revoked." }, access_requirements: { minimum_access: { web_server: "sudo" }, requires_root: false, temporary_grants_allowed: ["sudo:web_server:sshd"] }
|
||||
|
||||
Q008: narrative_phase: "suspicion", linux_concepts: ["apt","package pinning","apt-preferences","internal package mirror","vulcan build pipeline"], failure_conditions: ["axiomworks-app still broken","bad package not traced to build machine"], behavior_impact: { default: { curiosity_delta: 1, obedience_delta: 0, risk_delta: 0, suspicion_delta: 0 } }, hidden_hook: { "id": "q008_build_log_anomaly", "description": "vulcan's build log for 2.1.1 shows it was triggered by a manual invocation, not the automated pipeline, at 02:14.", "discovery_method": "Player reads /var/log/build-pipeline.log on vulcan and notices the timestamp and manual trigger field", "significance": "The bad build was triggered manually. Someone made the broken build, and it was not the pipeline." }, access_requirements: { minimum_access: { build_machine: "sudo", web_server: "sudo" }, requires_root: false, temporary_grants_allowed: [] }
|
||||
|
||||
After all changes, run: node tools/content/validate-content.js
|
||||
Confirm: zero errors. Warnings about missing narrative_phase should now be gone for all 8 quests.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 11 — Unit tests and validation extension
|
||||
|
||||
```
|
||||
Part A — Write unit tests for all new services.
|
||||
|
||||
Create file: server/src/services/BehaviorTracker.test.js
|
||||
Use the existing IncidentScheduler.test.js or ShiftReviewService.test.js as the pattern for test structure.
|
||||
|
||||
Tests to include:
|
||||
1. initialize() with no state.behavior: curiosity=50, obedience=50, risk=50, suspicion=0
|
||||
2. initialize() with existing state.behavior: values restored correctly
|
||||
3. apply({ curiosity_delta: 5 }): curiosity increases by 5
|
||||
4. apply({ risk_delta: -10 }): risk decreases by 10, floor at 0
|
||||
5. apply({ suspicion_delta: 200 }): suspicion clamps at 100
|
||||
6. apply({}): no change, no error
|
||||
7. apply(null): no change, no error (defensive)
|
||||
8. getSnapshot(): returns plain object with all four keys
|
||||
|
||||
Create file: server/src/services/NarrativePhaseTracker.test.js
|
||||
Tests:
|
||||
1. initialize() with no state.narrative_phase: returns 'normal_work'
|
||||
2. advance() with quest having narrative_phase 'unease': phase becomes 'unease'
|
||||
3. advance() with quest having higher phase than current: phase advances
|
||||
4. advance() with quest having lower phase than current: phase does NOT change
|
||||
5. advance() with quest missing narrative_phase field: phase does NOT change
|
||||
6. getPhase(): returns current phase string
|
||||
|
||||
Create file: server/src/services/EndingEvaluator.test.js
|
||||
Tests (each builds a mock state):
|
||||
1. exposure: curiosity=70, hiddenHooksDiscovered=['a','b'], phase='investigation' → active: 'exposure'
|
||||
2. corporate_loop: obedience=70, curiosity=35, trust=70 → active: 'corporate_loop'
|
||||
3. burnout: curiosity=30, obedience=35 → active: 'burnout'
|
||||
4. chaos: risk=70, trust=35 → active: 'chaos'
|
||||
5. no conditions: active: 'undetermined'
|
||||
6. exposure wins over chaos when both met: active: 'exposure'
|
||||
|
||||
Create file: server/src/services/HiddenHookTracker.test.js
|
||||
Tests:
|
||||
1. initialize() with no state: getDiscovered() returns []
|
||||
2. discover('h1'): getDiscovered() returns ['h1']
|
||||
3. discover('h1') twice: getDiscovered() returns ['h1'] (idempotent)
|
||||
4. isDiscovered('h1'): true after discovery
|
||||
5. isDiscovered('h2'): false before discovery
|
||||
|
||||
Part B — Run validation.
|
||||
After all changes: run `npm test` from the server directory. All tests must pass.
|
||||
Run `node tools/content/validate-content.js`. Zero errors.
|
||||
|
||||
Part C — Manual verification checklist.
|
||||
Confirm each item by inspection or running the game:
|
||||
[ ] Fresh save: GET /api/state returns behavior: {curiosity:50,obedience:50,risk:50,suspicion:0}, narrativePhase:'normal_work', accessLevel:'basic_user'
|
||||
[ ] Complete Q001 clean branch: behavior.obedience increments, phase stays normal_work
|
||||
[ ] Complete Q005: phase advances to 'unease', hidden_hook for q005_backup_agent_history visible in /api/debug/hidden-hooks
|
||||
[ ] Complete Q007: phase advances to 'suspicion', q007_dale_ssh_key hook discoverable on hermes
|
||||
[ ] ShiftReviewService sends from Priya Nair <p.nair@axiomworks.internal>
|
||||
[ ] GET /api/debug/ending with forced state returns correct ending label
|
||||
[ ] /?debug=1 shows debug panel in browser
|
||||
[ ] node tools/content/validate-content.js: zero errors
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*End of implementation plan.*
|
||||
Reference in New Issue
Block a user