diff --git a/AGENTS.md b/AGENTS.md index 98181c3..a8721eb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # OpsQuest agent guide -OpsQuest is a Go 1.22 CLI game with 19 in-memory Linux missions and an optional Docker Foundations lab. Linux player commands stay inside the teaching shell. Docker input is parsed into a deliberately small command subset that can affect only disposable, OpsQuest-labeled resources. Kubernetes remains future scope unless a task explicitly adds it. +OpsQuest is a Go 1.22 CLI game with 23 in-memory Linux missions and six optional Docker Foundations labs. Linux player commands stay inside the teaching shell. Docker input is parsed into a deliberately small command subset that can affect only disposable, OpsQuest-labeled resources. Kubernetes remains future scope unless a task explicitly adds it. ## Repository map diff --git a/README.md b/README.md index b4fe970..8efe415 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ OpsQuest is **Duolingo meets a terminal sandbox**: a Go CLI game that teaches Linux and container operations through short, story-driven missions. ```console -$ opsquest play --once 4 +$ opsquest play --once 5 -MISSION 04: The Missing Log File +MISSION 05: The Missing Log File ================================ -Linux · World 1/4: First Day · Stage 4/5 +Linux · World 1/4: First Day · Stage 5/6 Difficulty: beginner · Reward: 75 XP INCIDENT @@ -29,8 +29,8 @@ OpsQuest validates the result, not a prescribed command. Equivalent supported so ## What is included -- 19 Linux missions across four ordered learning worlds -- One optional, disposable Docker Foundations mission +- 23 Linux missions across four ordered learning worlds +- Six optional, disposable Docker Foundations missions - An isolated in-memory filesystem, environment, process table, and archives - Quote-aware globs and variables, pipelines, redirection, and command history - A compact virtual `vi` and bounded virtual shell scripts @@ -77,7 +77,7 @@ Linux gameplay never requires a container engine. Docker Foundations works with ```console $ docker pull docker.io/library/busybox@sha256:73aaf090f3d85aa34ee199857f03fa3a95c8ede2ffd4cc2cdb5b94e566b11662 $ opsquest doctor -$ opsquest play 17 +$ opsquest play 20 ``` On macOS, you can direct both setup and gameplay to OrbStack without changing your global Docker context: @@ -85,7 +85,7 @@ On macOS, you can direct both setup and gameplay to OrbStack without changing yo ```console $ DOCKER_CONTEXT=orbstack docker pull docker.io/library/busybox@sha256:73aaf090f3d85aa34ee199857f03fa3a95c8ede2ffd4cc2cdb5b94e566b11662 $ DOCKER_CONTEXT=orbstack opsquest doctor -$ DOCKER_CONTEXT=orbstack opsquest play 17 +$ DOCKER_CONTEXT=orbstack opsquest play 20 ``` OpsQuest never pulls the image automatically. The OrbStack context uses the same constrained Docker integration and cleanup path as every other compatible engine. See [Docker Foundations](docs/play/docker-foundations.md) for setup details, the teaching subset, and the isolation boundary. diff --git a/docs/README.md b/docs/README.md index d836640..6e5528f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -28,8 +28,8 @@ The game describes an observable objective, gives you a disposable environment, ## Current scope -- 19 Linux missions across four ordered learning worlds -- One optional Docker Foundations mission +- 23 Linux missions across four ordered learning worlds +- Six optional Docker Foundations missions - Outcome-based validation rather than one required command transcript - An in-memory filesystem, process table, environment, archives, editor, and shell scripts - Persistent XP, ranks, command practice, hints, completions, and achievements diff --git a/docs/game/curriculum.md b/docs/game/curriculum.md index 3aa2a89..2e52c4e 100644 --- a/docs/game/curriculum.md +++ b/docs/game/curriculum.md @@ -16,10 +16,10 @@ Editable source: [`learning-journey.excalidraw`](diagrams/learning-journey.excal The curriculum has two independent tracks: -- **Linux:** 19 missions across four ordered worlds. Bare `opsquest play` follows this track. -- **Docker:** one optional Foundations mission in its first world. Docker readiness never blocks Linux play. +- **Linux:** 23 missions across four ordered worlds. Bare `opsquest play` follows this track. +- **Docker:** six optional Foundations missions in one beginner world. Docker readiness never blocks Linux play. -Mission numbers are stable and global across both tracks, which is why the Docker mission is number 17 while Linux World 4 resumes at number 18. World and stage positions are derived separately inside each track. +Display numbers are global across both tracks: Docker occupies Missions 20–25 and Linux World 4 resumes at Mission 26. World and stage positions are derived separately inside each track. Persisted completions use stable mission IDs rather than display numbers, so profiles created before this expansion remain compatible. The [learning philosophy](learning-philosophy.md) explains the incident loop and feedback model. [Outcome-based mission design](mission-design.md) describes how validators preserve equivalent solutions. @@ -31,50 +31,59 @@ The “tools” column records suggested commands, not a mandatory solution. Out | ---: | --- | --- | --- | --- | --- | | 1 | Linux W1.1 | Beginner | Identify the current directory | `pwd` | Output equals | | 2 | Linux W1.2 | Beginner | Inspect and navigate a directory tree | `pwd`, `ls`, `cd` | Current directory equals | -| 3 | Linux W1.3 | Beginner | Create directories and files | `ls`, `mkdir`, `touch` | Directory and file exist | -| 4 | Linux W1.4 | Beginner | Search recursively and filter matching files | `find`, `grep` | Output contains all required paths and excludes noise | -| 5 | Linux W1.5 | Beginner | Move a release artifact without losing content | `ls`, `mv`, `cp`, `rm` | Destination content equals; source is missing | -| 6 | Linux W2.1 | Beginner | Inspect and repair executable permissions | `ls`, `stat`, `chmod` | File mode equals | -| 7 | Linux W2.2 | Beginner | Inspect and export environment variables | `env`, `echo`, `export` | Environment value equals | -| 8 | Linux W2.3 | Intermediate | Inspect processes and stop the faulty one | `ps`, `kill` | Target stopped; healthy process still running | -| 9 | Linux W2.4 | Intermediate | Inspect and extract an archive | `ls`, `tar` | Extracted content contains and equals expected data | -| 10 | Linux W2.5 | Advanced | Build a multi-stage log-processing pipeline | `cat`, `grep`, `awk`, `sort`, `uniq` | Report file content equals | -| 11 | Linux W3.1 | Beginner | Select the newest log lines | `cat`, `tail` | Output equals | -| 12 | Linux W3.2 | Intermediate | Sort and aggregate repeated alerts | `cat`, `sort`, `uniq` | Report lines equal | -| 13 | Linux W3.3 | Intermediate | Repair configuration text while preserving mode | `cat`, `sed`, `vi` | Content and mode equal | -| 14 | Linux W3.4 | Intermediate | Correct ownership and permissions together | `stat`, `chown`, `chmod` | Owner and mode equal | -| 15 | Linux W3.5 | Intermediate | Compare disk usage and identify the right target | `ls`, `du`, `sort`, `tail` | Output includes target and excludes distractor | -| 16 | Linux W3.6 | Advanced | Resolve a multi-fault production incident | `tar`, `sed`, `vi`, `chmod`, `ps`, `kill` | Content, mode, and process-state checks | -| 17 | Docker W1.1 | Beginner | List mission containers and start a stopped service | `docker` | Owned container count equals; target is running | -| 18 | Linux W4.1 | Beginner | Make a focused configuration edit with a modal editor | `cat`, `vi` | Content and mode equal | -| 19 | Linux W4.2 | Intermediate | Repair, permit, and run a reusable report script | `cat`, `less`, `vi`, `sed`, `chmod`, `sh` | Report content/lines and script mode | -| 20 | Linux W4.3 | Advanced | Reason about child-shell directory and environment scope | `cat`, `less`, `vi`, `sed`, `chmod`, `sh`, `pwd`, `env` | Parent scope preserved; report and script state correct | +| 3 | Linux W1.3 | Beginner | Read the correct short handoff file | `ls`, `cat`, `less` | Output equals | +| 4 | Linux W1.4 | Beginner | Create directories and files | `ls`, `mkdir`, `touch` | Directory and file exist | +| 5 | Linux W1.5 | Beginner | Search recursively and filter matching files | `find`, `grep` | Output contains all required paths and excludes noise | +| 6 | Linux W1.6 | Beginner | Move a release artifact without losing content | `ls`, `mv`, `cp`, `rm` | Destination content equals; source is missing | +| 7 | Linux W2.1 | Beginner | Inspect and repair executable permissions | `ls`, `stat`, `chmod` | File mode equals | +| 8 | Linux W2.2 | Beginner | Inspect and export environment variables | `env`, `echo`, `export` | Environment value equals | +| 9 | Linux W2.3 | Beginner | Select the opening lines of a startup log | `cat`, `head` | Output equals | +| 10 | Linux W2.4 | Intermediate | Inspect processes and stop the faulty one | `ps`, `kill` | Target stopped; healthy process still running | +| 11 | Linux W2.5 | Intermediate | Inspect and extract an archive | `ls`, `tar` | Extracted content contains and equals expected data | +| 12 | Linux W2.6 | Advanced | Build a multi-stage log-processing pipeline | `cat`, `grep`, `awk`, `sort`, `uniq` | Report file content equals | +| 13 | Linux W3.1 | Beginner | Select the newest log lines | `cat`, `tail` | Output equals | +| 14 | Linux W3.2 | Beginner | Count error records through filtering or a pipeline | `grep`, `wc` | Output equals | +| 15 | Linux W3.3 | Intermediate | Sort and aggregate repeated alerts | `cat`, `sort`, `uniq` | Report lines equal | +| 16 | Linux W3.4 | Intermediate | Repair configuration text while preserving mode | `cat`, `sed`, `vi` | Content and mode equal | +| 17 | Linux W3.5 | Intermediate | Correct ownership and permissions together | `stat`, `chown`, `chmod` | Owner and mode equal | +| 18 | Linux W3.6 | Intermediate | Compare disk usage and identify the right target | `ls`, `du`, `sort`, `tail` | Output includes target and excludes distractor | +| 19 | Linux W3.7 | Advanced | Resolve a multi-fault production incident | `tar`, `sed`, `vi`, `chmod`, `ps`, `kill` | Content, mode, and process-state checks | +| 20 | Docker W1.1 | Beginner | List mission containers and start a stopped service | `docker` | Owned count equals; targets are running | +| 21 | Docker W1.2 | Beginner | Read bounded logs from an exited job | `docker` | Output equals recorded log | +| 22 | Docker W1.3 | Beginner | Identify a successful one-shot job by exit code | `docker` | Output contains logical name and exit code; count equals | +| 23 | Docker W1.4 | Beginner | Stop one worker while preserving metrics | `docker` | Target stopped; healthy target running; count equals | +| 24 | Docker W1.5 | Beginner | Restore two stopped services | `docker` | All targets running; count equals | +| 25 | Docker W1.6 | Beginner | Perform a mixed start/stop handoff | `docker` | Replacement running; retiring target stopped; count equals | +| 26 | Linux W4.1 | Beginner | Inspect and run a supplied report script | `cat`, `less`, `chmod`, `sh` | Report content equals | +| 27 | Linux W4.2 | Beginner | Make a focused configuration edit with a modal editor | `cat`, `vi` | Content and mode equal | +| 28 | Linux W4.3 | Intermediate | Repair, permit, and run a reusable report script | `cat`, `less`, `vi`, `sed`, `chmod`, `sh` | Report content/lines and script mode | +| 29 | Linux W4.4 | Advanced | Reason about child-shell directory and environment scope | `cat`, `less`, `vi`, `sed`, `chmod`, `sh`, `pwd`, `env` | Parent scope preserved; report and script state correct | ## World progression ### Linux World 1: First Day -The opening world establishes orientation and safe filesystem manipulation. It moves from observing location, through navigation and creation, to recursive search and a small state-changing incident. All five missions are beginner difficulty. +The opening world establishes orientation and safe filesystem manipulation. It moves from observing location, through navigation and reading, to creation, recursive search, and a small state-changing incident. All six missions are beginner difficulty. ### Linux World 2: The Logpocalypse -This world broadens the state model: permissions, environment, processes, and archives. Missions 6–7 remain beginner-friendly, Missions 8–9 combine inspection with mutation, and Mission 10 is the first advanced boss and sustained pipeline exercise. +This world broadens the state model: permissions, environment, focused log reading, processes, and archives. Its first three stages are beginner-friendly, the next two combine inspection with mutation, and Stage 6 is the first advanced boss and sustained pipeline exercise. ### Linux World 3: Production Friday -The third world reinforces text inspection and transformation while adding ownership and disk-usage reasoning. Its six stages culminate in a multi-fault incident that combines archive extraction, configuration repair, permissions, and process control. +The third world reinforces text inspection, counting, and transformation while adding ownership and disk-usage reasoning. Its seven stages culminate in a multi-fault incident that combines archive extraction, configuration repair, permissions, and process control. ### Linux World 4: The Automation Shift -The final Linux world moves from one-off commands to reusable automation. It introduces the modal editor, executable virtual scripts, and the difference between child-shell scope and durable virtual state. +The final Linux world moves from executing a supplied script to repairing reusable automation. It introduces the modal editor, executable virtual scripts, and the difference between child-shell scope and durable virtual state. ### Docker World 1: It Works on My Machine -Docker Foundations begins with the smallest useful lifecycle: discover the attempt's containers and start the intended stopped service. Logical aliases hide real container names and IDs, keeping the lesson focused on observable container state. +Docker Foundations progresses through listing, logs, sanitized exit status, targeted stop, multi-service recovery, and a mixed lifecycle handoff. Logical aliases hide real container names and IDs, keeping every lesson focused on observable state for exact attempt-owned resources. ## Curriculum evolution -The current map makes deliberate expansion opportunities visible: there is no dedicated early file-reading stage, the first substantial pipeline appears in an advanced boss, and Docker currently has only its opening lifecycle mission. Those are curriculum decisions to address with new declarative missions rather than by weakening existing validators. +The expanded map fills the early file-reading, focused log-preview, simple counting, supplied-script, and beginner Docker lifecycle gaps. Later curriculum work can deepen those skills through new declarative missions without weakening existing outcome validators or widening the Docker boundary casually. Read [Progression and rewards](progression.md) for route and persistence behavior, and [Outcome-based mission design](mission-design.md) for the authoring checklist. diff --git a/docs/game/diagrams/learning-journey.excalidraw b/docs/game/diagrams/learning-journey.excalidraw index 36e3864..aca4271 100644 --- a/docs/game/diagrams/learning-journey.excalidraw +++ b/docs/game/diagrams/learning-journey.excalidraw @@ -7,13 +7,13 @@ {"id":"linux-label","type":"text","x":70,"y":130,"width":280,"height":20,"angle":0,"strokeColor":"#1d4ed8","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":202,"version":1,"versionNonce":2002,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":16,"fontFamily":2,"text":"LINUX TRACK · 19 MISSIONS · DEFAULT","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"LINUX TRACK · 19 MISSIONS · DEFAULT","autoResize":true,"lineHeight":1.25}, {"id":"w1","type":"rectangle","x":80,"y":180,"width":280,"height":270,"angle":0,"strokeColor":"#3b82f6","backgroundColor":"#ffffff","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":3},"seed":203,"version":1,"versionNonce":2003,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false}, - {"id":"w1-text","type":"text","x":110,"y":205,"width":220,"height":200,"angle":0,"strokeColor":"#172554","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":204,"version":1,"versionNonce":2004,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":18,"fontFamily":2,"text":"WORLD 1\nFirst Day\n\nMissions 1–5\n5 beginner stages\n\nORIENT\nlocation · navigation\ndirectories · files\nsearch · movement","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"WORLD 1\nFirst Day\n\nMissions 1–5\n5 beginner stages\n\nORIENT\nlocation · navigation\ndirectories · files\nsearch · movement","autoResize":true,"lineHeight":1.25}, + {"id":"w1-text","type":"text","x":110,"y":205,"width":220,"height":200,"angle":0,"strokeColor":"#172554","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":204,"version":1,"versionNonce":2004,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":18,"fontFamily":2,"text":"WORLD 1\nFirst Day\n\nMissions 1–6\n6 beginner stages\n\nORIENT\nlocation · navigation\nreading · files\nsearch · movement","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"WORLD 1\nFirst Day\n\nMissions 1–6\n6 beginner stages\n\nORIENT\nlocation · navigation\nreading · files\nsearch · movement","autoResize":true,"lineHeight":1.25}, {"id":"w2","type":"rectangle","x":410,"y":180,"width":280,"height":270,"angle":0,"strokeColor":"#3b82f6","backgroundColor":"#ffffff","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":3},"seed":205,"version":1,"versionNonce":2005,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false}, - {"id":"w2-text","type":"text","x":440,"y":205,"width":220,"height":200,"angle":0,"strokeColor":"#172554","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":206,"version":1,"versionNonce":2006,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":18,"fontFamily":2,"text":"WORLD 2\nThe Logpocalypse\n\nMissions 6–10\nbeginner → advanced\n\nCONTROL\npermissions · environment\nprocesses · archives\nfirst pipeline boss","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"WORLD 2\nThe Logpocalypse\n\nMissions 6–10\nbeginner → advanced\n\nCONTROL\npermissions · environment\nprocesses · archives\nfirst pipeline boss","autoResize":true,"lineHeight":1.25}, + {"id":"w2-text","type":"text","x":440,"y":205,"width":220,"height":200,"angle":0,"strokeColor":"#172554","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":206,"version":1,"versionNonce":2006,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":18,"fontFamily":2,"text":"WORLD 2\nThe Logpocalypse\n\nMissions 7–12\nbeginner → advanced\n\nCONTROL\npermissions · environment\nlogs · processes · archives\nfirst pipeline boss","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"WORLD 2\nThe Logpocalypse\n\nMissions 7–12\nbeginner → advanced\n\nCONTROL\npermissions · environment\nlogs · processes · archives\nfirst pipeline boss","autoResize":true,"lineHeight":1.25}, {"id":"w3","type":"rectangle","x":740,"y":180,"width":280,"height":270,"angle":0,"strokeColor":"#3b82f6","backgroundColor":"#ffffff","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":3},"seed":207,"version":1,"versionNonce":2007,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false}, - {"id":"w3-text","type":"text","x":770,"y":205,"width":220,"height":200,"angle":0,"strokeColor":"#172554","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":208,"version":1,"versionNonce":2008,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":18,"fontFamily":2,"text":"WORLD 3\nProduction Friday\n\nMissions 11–16\n6 escalating stages\n\nRESPOND\nlogs · transformation\nownership · disk usage\nmulti-fault boss","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"WORLD 3\nProduction Friday\n\nMissions 11–16\n6 escalating stages\n\nRESPOND\nlogs · transformation\nownership · disk usage\nmulti-fault boss","autoResize":true,"lineHeight":1.25}, + {"id":"w3-text","type":"text","x":770,"y":205,"width":220,"height":200,"angle":0,"strokeColor":"#172554","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":208,"version":1,"versionNonce":2008,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":18,"fontFamily":2,"text":"WORLD 3\nProduction Friday\n\nMissions 13–19\n7 escalating stages\n\nRESPOND\nlogs · counts · transforms\nownership · disk usage\nmulti-fault boss","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"WORLD 3\nProduction Friday\n\nMissions 13–19\n7 escalating stages\n\nRESPOND\nlogs · counts · transforms\nownership · disk usage\nmulti-fault boss","autoResize":true,"lineHeight":1.25}, {"id":"w4","type":"rectangle","x":1070,"y":180,"width":300,"height":270,"angle":0,"strokeColor":"#3b82f6","backgroundColor":"#ffffff","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":3},"seed":209,"version":1,"versionNonce":2009,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false}, - {"id":"w4-text","type":"text","x":1100,"y":205,"width":240,"height":200,"angle":0,"strokeColor":"#172554","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":210,"version":1,"versionNonce":2010,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":18,"fontFamily":2,"text":"WORLD 4\nThe Automation Shift\n\nMissions 18–20\nbeginner → advanced\n\nAUTOMATE\nmodal editing\nexecutable scripts\nchild-shell scope","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"WORLD 4\nThe Automation Shift\n\nMissions 18–20\nbeginner → advanced\n\nAUTOMATE\nmodal editing\nexecutable scripts\nchild-shell scope","autoResize":true,"lineHeight":1.25}, + {"id":"w4-text","type":"text","x":1100,"y":205,"width":240,"height":200,"angle":0,"strokeColor":"#172554","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":210,"version":1,"versionNonce":2010,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":18,"fontFamily":2,"text":"WORLD 4\nThe Automation Shift\n\nMissions 26–29\nbeginner → advanced\n\nAUTOMATE\nrun · edit · repair\nexecutable scripts\nchild-shell scope","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"WORLD 4\nThe Automation Shift\n\nMissions 26–29\nbeginner → advanced\n\nAUTOMATE\nrun · edit · repair\nexecutable scripts\nchild-shell scope","autoResize":true,"lineHeight":1.25}, {"id":"a12","type":"arrow","x":360,"y":315,"width":50,"height":0,"angle":0,"strokeColor":"#2563eb","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":3,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":211,"version":1,"versionNonce":2011,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"points":[[0,0],[50,0]],"lastCommittedPoint":null,"startBinding":null,"endBinding":null,"startArrowhead":null,"endArrowhead":"arrow","elbowed":false}, {"id":"a23","type":"arrow","x":690,"y":315,"width":50,"height":0,"angle":0,"strokeColor":"#2563eb","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":3,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":212,"version":1,"versionNonce":2012,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"points":[[0,0],[50,0]],"lastCommittedPoint":null,"startBinding":null,"endBinding":null,"startArrowhead":null,"endArrowhead":"arrow","elbowed":false}, @@ -22,7 +22,7 @@ {"id":"docker-lane","type":"rectangle","x":40,"y":545,"width":1410,"height":215,"angle":0,"strokeColor":"#8b5cf6","backgroundColor":"#f5f3ff","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":3},"seed":214,"version":1,"versionNonce":2014,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false}, {"id":"docker-label","type":"text","x":70,"y":565,"width":370,"height":20,"angle":0,"strokeColor":"#6d28d9","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":215,"version":1,"versionNonce":2015,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":16,"fontFamily":2,"text":"DOCKER TRACK · OPTIONAL · INDEPENDENT WORLD NUMBERING","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"DOCKER TRACK · OPTIONAL · INDEPENDENT WORLD NUMBERING","autoResize":true,"lineHeight":1.25}, {"id":"docker-world","type":"rectangle","x":85,"y":610,"width":390,"height":115,"angle":0,"strokeColor":"#7c3aed","backgroundColor":"#ffffff","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":3},"seed":216,"version":1,"versionNonce":2016,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false}, - {"id":"docker-world-text","type":"text","x":115,"y":630,"width":330,"height":75,"angle":0,"strokeColor":"#4c1d95","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":217,"version":1,"versionNonce":2017,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":18,"fontFamily":2,"text":"DOCKER WORLD 1\nIt Works on My Machine\nMission 17 · container lifecycle","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"DOCKER WORLD 1\nIt Works on My Machine\nMission 17 · container lifecycle","autoResize":true,"lineHeight":1.25}, + {"id":"docker-world-text","type":"text","x":115,"y":630,"width":330,"height":75,"angle":0,"strokeColor":"#4c1d95","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":217,"version":1,"versionNonce":2017,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":18,"fontFamily":2,"text":"DOCKER WORLD 1\nIt Works on My Machine\nMissions 20–25 · lifecycle + diagnostics","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"DOCKER WORLD 1\nIt Works on My Machine\nMissions 20–25 · lifecycle + diagnostics","autoResize":true,"lineHeight":1.25}, {"id":"docker-note","type":"rectangle","x":650,"y":610,"width":700,"height":115,"angle":0,"strokeColor":"#a78bfa","backgroundColor":"#ede9fe","fillStyle":"solid","strokeWidth":2,"strokeStyle":"dashed","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":3},"seed":218,"version":1,"versionNonce":2018,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false}, {"id":"docker-note-text","type":"text","x":685,"y":630,"width":630,"height":75,"angle":0,"strokeColor":"#4c1d95","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":219,"version":1,"versionNonce":2019,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":18,"fontFamily":2,"text":"Separate readiness and progress\nPinned local fixture · no automatic pull · Linux never blocked\nCurrent scope: one bounded lifecycle mission","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"Separate readiness and progress\nPinned local fixture · no automatic pull · Linux never blocked\nCurrent scope: one bounded lifecycle mission","autoResize":true,"lineHeight":1.25}, {"id":"a-docker","type":"arrow","x":475,"y":668,"width":175,"height":0,"angle":0,"strokeColor":"#7c3aed","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":3,"strokeStyle":"dashed","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":220,"version":1,"versionNonce":2020,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"points":[[0,0],[175,0]],"lastCommittedPoint":null,"startBinding":null,"endBinding":null,"startArrowhead":null,"endArrowhead":"arrow","elbowed":false} diff --git a/docs/game/diagrams/learning-journey.svg b/docs/game/diagrams/learning-journey.svg index a4b5dbf..609ca25 100644 --- a/docs/game/diagrams/learning-journey.svg +++ b/docs/game/diagrams/learning-journey.svg @@ -1,7 +1,7 @@ OpsQuest learning journey - Two-lane curriculum overview showing four Linux worlds with nineteen missions and one independent optional Docker Foundations world. + Two-lane curriculum overview showing four Linux worlds with twenty-three missions and one independent six-mission Docker Foundations world. @@ -16,7 +16,7 @@ Top-level mission numbers stay global; in-lab numeric jumps use current-world stages. - LINUX TRACK · 19 MISSIONS · DEFAULT + LINUX TRACK · 23 MISSIONS · DEFAULT modify @@ -30,7 +30,7 @@ World 1 First Day - Missions 1–5 · 5 beginner stages + Missions 1–6 · 6 beginner stages ORIENT • location and navigation • directories and files @@ -41,7 +41,7 @@ World 2 The Logpocalypse - Missions 6–10 · beginner → advanced + Missions 7–12 · beginner → advanced CONTROL • permissions and environment • processes and archives @@ -52,7 +52,7 @@ World 3 Production Friday - Missions 11–16 · 6 escalating stages + Missions 13–19 · 7 escalating stages RESPOND • logs and transformation • ownership and disk usage @@ -63,7 +63,7 @@ World 4 The Automation Shift - Missions 18–20 · beginner → advanced + Missions 26–29 · beginner → advanced AUTOMATE • modal editing • executable shell scripts @@ -76,14 +76,14 @@ Docker World 1 It Works on My Machine - Mission 17 · list, inspect, and start owned fixtures + Missions 20–25 · six beginner lifecycle and diagnostic stages Separate readiness and progress Pinned local fixture · no automatic pull · Docker never blocks Linux - The current curriculum intentionally begins with one bounded lifecycle mission. + Typed list, logs, inspect, start, restart, and stop actions only. OUTCOME-BASED VALIDATION diff --git a/docs/history/README.md b/docs/history/README.md index 8577129..6051546 100644 --- a/docs/history/README.md +++ b/docs/history/README.md @@ -25,5 +25,6 @@ Iteration reports preserve point-in-time delivery claims and observed validation | [13](iterations/iteration-13.md) | Onboarding and world-aware progression | | [14](iterations/iteration-14.md) | Hosted Zensical documentation and GitHub Pages delivery | | [15](iterations/iteration-15.md) | Behavior-preserving non-Docker codebase simplification | +| [16](iterations/iteration-16.md) | Beginner Linux and Docker mission expansion | New reports use the supplied iteration template and a zero-padded `iteration-NN.md` filename. diff --git a/docs/history/iterations/iteration-16.md b/docs/history/iterations/iteration-16.md new file mode 100644 index 0000000..ae8215d --- /dev/null +++ b/docs/history/iterations/iteration-16.md @@ -0,0 +1,61 @@ +--- +description: Historical delivery record for OpsQuest iteration 16. +audience: contributors and maintainers +status: historical +search: + exclude: true +--- + +# OpsQuest iteration 16 + +> Historical delivery record. See the [documentation map](../../README.md) for current behavior. + +Date: 2026-07-22 +Version: 0.6.0 (unchanged) + +## Summary + +OpsQuest now ships 29 missions: 23 Linux missions across four worlds and six beginner Docker Foundations missions. The Linux additions fill early reading, focused log-preview, simple counting, and supplied-script gaps; the Docker world now teaches bounded logs, sanitized exit status, targeted stop, multi-container recovery, and a mixed lifecycle handoff while retaining the exact-resource safety boundary. + +## Delivered + +- Added one beginner mission to each Linux world: read the correct handoff file, preview a log's opening lines, count error records through either `grep -c` or a pipeline, and inspect then run a supplied report script. +- Expanded **It Works on My Machine** from one to six beginner missions with exited-job logs, exit-code diagnosis, targeted stop, two-service recovery, and a start/stop handoff. +- Added exact `docker logs ALIAS` and `docker stop ALIAS` teaching actions, including their `docker container` forms, help text, completion candidates, and strict rejection of extra flags or aliases. +- Added declarative stopped one-shot fixtures with a paired bounded `log` and `exit_code`, a fixed non-interpolating shell program, setup-time start/wait verification, and sanitized logical inspection output. +- Added the `docker_container_stopped` observable condition and retained owned-container counts and required healthy-state checks for lifecycle missions. +- Added canonical, alternative, and incomplete-outcome coverage for all nine new missions, adapter success and rejection coverage, and real OrbStack integration coverage for diagnostics and targeted stop. +- Updated CLI navigation expectations, deterministic smoke coverage, current docs, safety diagrams, curriculum maps, and the Docker roadmap for the 29-mission layout. + +## Compatibility and safety + +- Existing mission IDs are unchanged, so version 2 profile completions, hint history, XP, and achievements remain readable without migration. Global display numbers from the first insertion onward changed to preserve contiguous world ordering; numeric bookmarks and scripts must use the new map, while stable IDs remain the compatibility-safe reference. +- The mission schema change is additive: Docker containers may now declare `log` and `exit_code` together, and validations may use `docker_container_stopped`. Catalog loading rejects partial diagnostic declarations, logs over 8 KiB, NUL data, exit codes outside 0–255, and diagnostic fixtures not declared stopped. +- The profile schema, profile version, executable version, Linux sandbox grammar, and dependency set are unchanged. +- Player Docker text still cannot reach a host shell or raw Docker passthrough. The new parser forms accept one validated logical alias, resolve it to the tracked exact ID, and construct fixed Docker arguments internally. +- Diagnostic log text is passed as positional data to a fixed `/bin/sh` program rather than interpolated into shell syntax. Existing no-network, read-only, non-root, capability, resource, labeling, ownership-verification, output-cap, timeout, and cleanup controls remain in force. +- Real OrbStack validation removed every disposable OpsQuest-labeled integration container; a follow-up label-filtered listing returned no IDs. + +## Validation + +| Command | Observed result | +| --- | --- | +| `go test ./internal/mission ./internal/game ./internal/dockerlab` | PASS; focused catalog, canonical/alternative/incomplete mission, and Docker adapter tests completed successfully. | +| `go test ./...` | PASS; all Go packages completed successfully. | +| `make smoke-test` | PASS; onboarding, continuous and one-shot play, 23-mission Linux maps, six-mission Docker discovery, doctor output, and world-local navigation passed. | +| `make orbstack-integration` | PASS against the active OrbStack context; real diagnostic logs/exit status, targeted stop, ownership-scoped lifecycle, and cleanup completed successfully. | +| `docker container ls --all --quiet --filter label=com.opsquest.managed=true` | PASS; no managed container IDs remained after integration. | +| `make check-all` | PASS; agent docs, embedded mission integrity, all packages, vet, build, smoke coverage, and race detection passed. | +| `make docs-check ZENSICAL=/tmp/opsquest-doc-venv/bin/zensical` | PASS; Zensical 0.0.51 completed a clean strict build with no issues. | +| `git diff --check` | PASS; no whitespace errors were reported. | + +An early `make validate-missions` run correctly failed while count- and navigation-sensitive tests still described the old 20-mission catalog. Those fixtures were updated, and the same mission-validation target passed inside the final `make check-all` run. + +## Remaining work + +- Environment-aware creation, port publication, arbitrary `exec`, volumes, networking, Compose, and Kubernetes remain outside the typed Docker teaching boundary and require separate threat models before implementation. +- The expansion intentionally keeps every new lesson at beginner difficulty; later missions can deepen the same skills without weakening current observable outcomes. + +## Final repository state + +The `main` worktree contains only the intentional mission catalog, Docker adapter, tests, current documentation, diagrams, smoke expectations, and this iteration report. No profile migration, dependency change, generated release artifact, or unrelated user edit is included. diff --git a/docs/play/docker-foundations.md b/docs/play/docker-foundations.md index c8405ec..0311c50 100644 --- a/docs/play/docker-foundations.md +++ b/docs/play/docker-foundations.md @@ -1,5 +1,5 @@ --- -description: Prepare and play the optional, disposable OpsQuest Docker Foundations mission. +description: Prepare and play the optional, disposable OpsQuest Docker Foundations missions. audience: players status: current --- @@ -15,7 +15,7 @@ Install and start your chosen Docker-compatible engine, then explicitly fetch th ```console $ docker pull docker.io/library/busybox@sha256:73aaf090f3d85aa34ee199857f03fa3a95c8ede2ffd4cc2cdb5b94e566b11662 $ opsquest doctor -$ opsquest play 17 +$ opsquest play 20 ``` ### Use OrbStack on macOS @@ -25,7 +25,7 @@ OrbStack exposes its engine through the standard Docker CLI and the `orbstack` D ```console $ DOCKER_CONTEXT=orbstack docker pull docker.io/library/busybox@sha256:73aaf090f3d85aa34ee199857f03fa3a95c8ede2ffd4cc2cdb5b94e566b11662 $ DOCKER_CONTEXT=orbstack opsquest doctor -$ DOCKER_CONTEXT=orbstack opsquest play 17 +$ DOCKER_CONTEXT=orbstack opsquest play 20 ``` These one-command environment settings leave your global Docker context unchanged. Alternatively, run `docker context use orbstack` once and then use the ordinary commands above. `opsquest doctor` identifies OrbStack when its official context is active. @@ -37,9 +37,18 @@ $ opsquest map --track docker $ opsquest play --track docker ``` -## Current lesson +## Current lessons -**Container Census** teaches the difference between images, running containers, and stopped containers. The delivered teaching subset recognizes container listing plus `start`, `restart`, `inspect`, and `help` forms. It does not forward arbitrary Docker CLI arguments. +The six beginner missions cover a deliberately narrow operational loop: + +1. **Container Census** — list containers and start an existing stopped service. +2. **Last Broadcast** — read bounded logs from an exited one-shot job. +3. **Exit Code Detective** — distinguish success from failure using sanitized inspection state. +4. **Quiet the Worker** — stop one target while preserving a healthy service. +5. **Recovery Pair** — restore two stopped services without replacing them. +6. **Shift Handoff** — combine `start` and `stop` while preserving supporting metrics. + +The delivered teaching subset recognizes container listing plus `start`, `restart`, `stop`, `inspect`, `logs`, and `help` forms. It accepts exact logical aliases without forwarding arbitrary Docker CLI arguments or flags. ## Isolation boundary diff --git a/docs/play/linux-worlds.md b/docs/play/linux-worlds.md index f3b05ce..6cfdac8 100644 --- a/docs/play/linux-worlds.md +++ b/docs/play/linux-worlds.md @@ -6,16 +6,16 @@ status: current # Linux worlds -The default track contains 19 missions across four worlds. Worlds guide the learning order but do not lock content; every mission remains directly playable and replayable. +The default track contains 23 missions across four worlds. Worlds guide the learning order but do not lock content; every mission remains directly playable and replayable. | World | Missions | Focus | | --- | ---: | --- | -| **First Day** | 1–5 | Orientation, directories, files, recursive search, and movement | -| **The Logpocalypse** | 6–10 | Permissions, environment variables, processes, archives, and a pipeline boss | -| **Production Friday** | 11–16 | Logs, aggregation, text transformation, ownership, disk usage, and a multi-fault incident | -| **The Automation Shift** | 18–20 | Modal editing, reusable scripts, executable modes, and child-shell scope | +| **First Day** | 1–6 | Orientation, navigation, file reading, creation, recursive search, and movement | +| **The Logpocalypse** | 7–12 | Permissions, environment variables, log previews, processes, archives, and a pipeline boss | +| **Production Friday** | 13–19 | Logs, error counting, aggregation, text transformation, ownership, disk usage, and a multi-fault incident | +| **The Automation Shift** | 26–29 | Running supplied scripts, modal editing, reusable automation, and child-shell scope | -Mission 17 belongs to the independent optional Docker track, so Linux resumes at global Mission 18. World and stage numbers are track-local even though mission numbers remain globally stable. +Missions 20–25 belong to the independent optional Docker track, so Linux resumes at global Mission 26. World and stage numbers are track-local. Saved completions use stable mission IDs, so the expanded display numbering does not invalidate existing profiles. ## Following or exploring the path @@ -23,7 +23,7 @@ Mission 17 belongs to the independent optional Docker track, so Linux resumes at $ opsquest play # resume Linux progress $ opsquest map # see every world and stage $ opsquest play --world 3 # stay within Production Friday -$ opsquest play 16 # begin at a global mission number +$ opsquest play 19 # begin at a global mission number $ opsquest play linux-find-logs ``` diff --git a/docs/roadmap/docker-foundations.md b/docs/roadmap/docker-foundations.md index d2ccad2..799bb14 100644 --- a/docs/roadmap/docker-foundations.md +++ b/docs/roadmap/docker-foundations.md @@ -1,39 +1,44 @@ --- -description: Delivered Docker Foundations boundary and proposed follow-up missions for the optional track. +description: Delivered Docker Foundations boundary, beginner campaign, and proposed follow-up capabilities. audience: players and contributors status: partially delivered --- # Docker Foundations roadmap -Status: the first v0.3 vertical slice described below is implemented. This document now records the delivered boundary and the next campaign increments. +Status: the initial vertical slice and six-mission beginner campaign are implemented. This document records the delivered boundary and the capabilities intentionally left for later work. ## Delivered vertical slice -The first milestone adds the environment boundary and one Docker mission end-to-end: +The first milestone added the environment boundary and one Docker mission end-to-end: 1. Introduce small execution and observation interfaces plus an environment factory. 2. Wrap the existing in-memory sandbox without changing Linux gameplay. 3. Add an environment type to missions, defaulting existing content to `simulated`. 4. Detect the Docker CLI, active Docker-compatible engine, and pinned fixture image through `opsquest doctor`, including provider-specific readiness for OrbStack's official context. -5. Implement Mission 17, **Container Census**, with disposable, attempt-owned containers and reliable teardown. +5. Implement **Container Census** (currently Mission 20) with disposable, attempt-owned containers and reliable teardown. 6. Validate the container’s observable state, independent of the exact commands used. Container Census includes three progressive teaching hints: understand image/container lifecycle, discover stopped containers with `docker ps -a`, then start the existing container with `docker start api`. Hints remain optional and reduce the XP reward. The portable test gate uses a fake Docker runner. `make docker-integration` checks the active Docker context, while `make orbstack-integration` explicitly checks OrbStack's `orbstack` context. Both real-engine targets require the pinned fixture image documented in the README. -## Next campaign increments: “It Works on My Machine” +## Delivered beginner campaign: “It Works on My Machine” -After the vertical slice proves reliable, expand it to roughly five missions: +The first world now contains six beginner missions: - **Container Census** — distinguish images from running and stopped containers. -- **The Silent Crash** — diagnose a failed container using `ps` and `logs`. -- **Wrong Environment** — recreate a service with the correct environment variable. -- **Port Problems** — publish container port 8080 on host port 3000. -- **Emergency Shell** — inspect a running container with `exec` and repair the incident. +- **Last Broadcast** — diagnose an exited one-shot job using bounded `logs`. +- **Exit Code Detective** — use sanitized inspection state to identify a successful job. +- **Quiet the Worker** — stop one exact running container while preserving another. +- **Recovery Pair** — start two existing stopped services. +- **Shift Handoff** — combine start and stop actions without changing owned resource count. -The delivered typed subset is `docker ps`, `docker container ls`, `start`, `restart`, and `inspect`. Each later mission should add only the semantics it teaches; likely next commands are `logs`, environment-aware `run`, and then port publication. +The delivered typed subset is `docker ps`, `docker container ls`, `start`, `restart`, `stop`, `inspect`, and `logs`. Diagnostic fixtures use a fixed entrypoint program with bounded declarative log data and exit codes; player text never becomes a host-shell or raw Docker command. + +## Possible later increments + +Environment-aware container creation, port publication, arbitrary in-container execution, volumes, and networking remain proposals. Each would materially widen the current boundary and therefore needs its own threat model, parser contract, resource ownership rules, and integration coverage before becoming gameplay. ## Important boundaries @@ -44,4 +49,4 @@ The delivered typed subset is `docker ps`, `docker container ls`, `start`, `rest - No privileged containers, host bind mounts, or Docker socket exposure inside labs. - Dockerfiles, volumes, networking, Compose, and Kubernetes remain follow-up iterations. -This vertical slice tests genuine Docker gameplay without committing OpsQuest to the entire Docker campaign at once. The separate **Automation Shift** Linux mini-campaign now provides dedicated `vi` and safe `sh` practice without changing the Docker boundary. +The campaign tests genuine Docker gameplay while keeping the adapter intentionally smaller than the general Docker CLI. The separate **Automation Shift** Linux campaign provides dedicated `vi` and safe `sh` practice without widening the Docker boundary. diff --git a/docs/technical/diagrams/trust-boundaries.excalidraw b/docs/technical/diagrams/trust-boundaries.excalidraw index 717caf5..2960729 100644 --- a/docs/technical/diagrams/trust-boundaries.excalidraw +++ b/docs/technical/diagrams/trust-boundaries.excalidraw @@ -19,7 +19,7 @@ {"id":"virtual-state-text","type":"text","x":380,"y":570,"width":220,"height":75,"angle":0,"strokeColor":"#14532d","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":312,"version":1,"versionNonce":3012,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":19,"fontFamily":2,"text":"Attempt-local virtual state\nfiles · env · PIDs\narchives · history","textAlign":"center","verticalAlign":"top","containerId":null,"originalText":"Attempt-local virtual state\nfiles · env · PIDs\narchives · history","autoResize":true,"lineHeight":1.25}, {"id":"docker-parser","type":"rectangle","x":720,"y":370,"width":300,"height":115,"angle":0,"strokeColor":"#7c3aed","backgroundColor":"#ede9fe","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":3},"seed":313,"version":1,"versionNonce":3013,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false}, - {"id":"docker-parser-text","type":"text","x":760,"y":395,"width":220,"height":75,"angle":0,"strokeColor":"#4c1d95","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":314,"version":1,"versionNonce":3014,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":19,"fontFamily":2,"text":"Typed Docker parser\nexact action subset\nvalidated logical alias","textAlign":"center","verticalAlign":"top","containerId":null,"originalText":"Typed Docker parser\nexact action subset\nvalidated logical alias","autoResize":true,"lineHeight":1.25}, + {"id":"docker-parser-text","type":"text","x":760,"y":395,"width":220,"height":75,"angle":0,"strokeColor":"#4c1d95","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":314,"version":1,"versionNonce":3014,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":19,"fontFamily":2,"text":"Typed Docker parser\nlogs · inspect · lifecycle\nvalidated logical alias","textAlign":"center","verticalAlign":"top","containerId":null,"originalText":"Typed Docker parser\nlogs · inspect · lifecycle\nvalidated logical alias","autoResize":true,"lineHeight":1.25}, {"id":"ownership","type":"rectangle","x":720,"y":545,"width":300,"height":125,"angle":0,"strokeColor":"#7c3aed","backgroundColor":"#f5f3ff","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":3},"seed":315,"version":1,"versionNonce":3015,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false}, {"id":"ownership-text","type":"text","x":760,"y":570,"width":220,"height":75,"angle":0,"strokeColor":"#4c1d95","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":null,"seed":316,"version":1,"versionNonce":3016,"isDeleted":false,"boundElements":null,"updated":1784505600000,"link":null,"locked":false,"fontSize":19,"fontFamily":2,"text":"Ownership adapter\nalias → tracked exact ID\nfixed argv · cleanup","textAlign":"center","verticalAlign":"top","containerId":null,"originalText":"Ownership adapter\nalias → tracked exact ID\nfixed argv · cleanup","autoResize":true,"lineHeight":1.25}, diff --git a/docs/technical/diagrams/trust-boundaries.svg b/docs/technical/diagrams/trust-boundaries.svg index e613895..7b526aa 100644 --- a/docs/technical/diagrams/trust-boundaries.svg +++ b/docs/technical/diagrams/trust-boundaries.svg @@ -35,7 +35,7 @@ OPTIONAL DOCKER PATH - Typed Docker parserlist · start · restart · inspectvalidated logical alias only + Typed Docker parserlist · logs · inspect · lifecyclevalidated logical alias only Ownership adapterlogical alias → tracked exact IDfixed argv · labels · cleanup NO RAW COMMAND PASSTHROUGH diff --git a/docs/technical/mission-content.md b/docs/technical/mission-content.md index f599878..41e4947 100644 --- a/docs/technical/mission-content.md +++ b/docs/technical/mission-content.md @@ -35,14 +35,14 @@ Linux remains the default track and `simulated` remains the default environment - missing narrative, guidance, setup, validation, or reward data; - unsafe, conflicting, or inconsistent virtual paths and state; - validators with missing, extra, or incompatible fields; -- unpinned Docker images, invalid aliases, or oversized Docker setup; +- unpinned Docker images, invalid aliases, oversized Docker setup, or incomplete diagnostic fixtures; - a campaign that reappears as separate worlds in one track. Catalog access is indexed by ID and number. Returned missions and worlds are deep copies so adapters cannot mutate embedded content. ## Observable validation -Conditions cover output, working directory, path existence, file content and logical lines, modes, owners, process state, environment values, and bounded Docker container state. The game layer compares output conditions; the active environment observes state conditions through the shared `Environment` contract. +Conditions cover output, working directory, path existence, file content and logical lines, modes, owners, process state, environment values, and bounded Docker container state. Docker fixtures may additionally declare a bounded log and exit code together for a stopped one-shot diagnostic job; container conditions can require either running or stopped state. The game layer compares output conditions; the active environment observes state conditions through the shared `Environment` contract. Validators describe evidence, not the command that must produce it. See [Outcome-based mission design](../game/mission-design.md) for the product rule. diff --git a/docs/technical/sandbox-and-safety.md b/docs/technical/sandbox-and-safety.md index 04fe98e..0c7c114 100644 --- a/docs/technical/sandbox-and-safety.md +++ b/docs/technical/sandbox-and-safety.md @@ -98,13 +98,13 @@ Docker missions are opt-in and follow a narrower path: 1. The mission catalog accepts only pinned image references and bounded logical fixtures. 2. Availability checks the Docker executable, active Docker-compatible engine, and exact local image without creating resources or pulling images. An active `orbstack` context is identified for provider-specific guidance only. 3. The factory generates a random session ID and creates containers with generated names and five ownership labels. -4. Player text is parsed into `list`, `start`, `restart`, `inspect`, or `help`; flags and aliases must match the small grammar. +4. Player text is parsed into `list`, `start`, `restart`, `stop`, `inspect`, `logs`, or `help`; flags and aliases must match the small grammar. 5. The adapter resolves a validated logical alias to a tracked exact container ID. 6. `exec.CommandContext` receives fixed arguments constructed by the adapter. 7. Observations inspect exact IDs or enumerate by the session label, then verify the complete ownership-label set. 8. `Close` seals the attempt immediately, re-inspects ownership, removes only matching exact IDs, and retains unresolved resources for a retry. -Created containers use no network, a read-only root filesystem, a bounded temporary filesystem, an unprivileged numeric user, no Linux capabilities, `no-new-privileges`, and explicit PID, memory, CPU, file-descriptor, restart, and stop-timeout limits. They do not receive host bind mounts, devices, privileged mode, host networking, or a Docker socket. +Created containers use no network, a read-only root filesystem, a bounded temporary filesystem, an unprivileged numeric user, no Linux capabilities, `no-new-privileges`, and explicit PID, memory, CPU, file-descriptor, restart, and stop-timeout limits. Diagnostic fixtures use a fixed shell program; bounded log text and exit status are passed as data arguments rather than interpolated into that program. Containers do not receive host bind mounts, devices, privileged mode, host networking, or a Docker socket. Docker-specific ceilings include: @@ -113,6 +113,7 @@ Docker-specific ceilings include: | Player Docker line | 64 KiB | | Images per mission | 16 | | Containers per mission | 32 | +| Declarative diagnostic log per container | 8 KiB | | Captured Docker stdout and stderr | 2 MiB each | | Normal Docker operation | 10 seconds | | Cleanup attempt | 10 seconds | diff --git a/initial_prompt.md b/initial_prompt.md index bf35191..aace35a 100644 --- a/initial_prompt.md +++ b/initial_prompt.md @@ -1,6 +1,6 @@ # OpsQuest — current implementation brief -OpsQuest is a Go CLI game described as **Duolingo meets a terminal sandbox**. Its Linux curriculum runs through short, story-driven operations missions in deterministic, in-memory environments; the current release also includes one optional Docker Foundations lab backed by isolated, attempt-owned containers. +OpsQuest is a Go CLI game described as **Duolingo meets a terminal sandbox**. Its Linux curriculum runs through short, story-driven operations missions in deterministic, in-memory environments; the current release also includes six optional Docker Foundations labs backed by isolated, attempt-owned containers. The current executable reports version **0.6.0**. Kubernetes remains a later expansion and is not implemented. @@ -8,8 +8,8 @@ The current executable reports version **0.6.0**.