{
  "schema_version": 2,
  "meta": {
    "build_id": "CODEX-X-BROADCAST-CHAT-SCRAPER",
    "slug": "x-broadcast-chat-scraper",
    "title": "Live Broadcast Chat Scraper",
    "subtitle": "Capture permitted messages into a restartable, deduplicated archive.",
    "description": "A working local chat collector that normalizes timezone-aware messages, deduplicates broadcast/message IDs in SQLite, retains rejects and exports spreadsheet-safe Unicode CSV.",
    "one_sentence_description": "A working local chat collector that normalizes timezone-aware messages, deduplicates broadcast/message IDs in SQLite, retains rejects and exports spreadsheet-safe Unicode CSV.",
    "target_user": "A motivated beginner with permission to work in a private practice folder.",
    "skill_level": "beginner",
    "estimated_time": "100–150 minutes",
    "category": "Guided Build",
    "tags": [
      "x-broadcast-chat-scraper",
      "verified practice",
      "chatgpt_codex"
    ],
    "prerequisites": [
      "Permission to use a dedicated workspace",
      "A browser and terminal",
      "Read each platform-specific prerequisite before installation"
    ],
    "required_accounts": [],
    "required_software": [
      "Modern browser",
      "Python 3.10+ for downloadable drills"
    ],
    "required_hardware": [
      "A computer with space for the practice files",
      "Second drive for the disaster-recovery checkpoint"
    ],
    "expected_cost": "Free practice; existing tool accounts may have plan limits.",
    "final_outcome": "A working local chat collector that normalizes timezone-aware messages, deduplicates broadcast/message IDs in SQLite, retains rejects and exports spreadsheet-safe Unicode CSV.",
    "platforms": [
      "windows",
      "macos",
      "linux"
    ],
    "version": "2.0",
    "author": "chatgpt_codex",
    "source_checked": "2026-10-05",
    "verification_status": "Created; automated browser and project checks recorded separately. Live account/OS verification is never inferred from browser completion."
  },
  "user_outcome": {
    "before": "The learner has no reproducible, checked workflow for live broadcast chat scraper.",
    "after": "A working local chat collector that normalizes timezone-aware messages, deduplicates broadcast/message IDs in SQLite, retains rejects and exports spreadsheet-safe Unicode CSV.",
    "finished_product": "A working local chat collector that normalizes timezone-aware messages, deduplicates broadcast/message IDs in SQLite, retains rejects and exports spreadsheet-safe Unicode CSV."
  },
  "learning_objectives": [
    "The contract names a permitted source, a bounded capture period, and a method that does not bypass authentication.",
    "Four input lines contain three unique broadcast/message IDs, including café and an em dash.",
    "CHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages.",
    "CHAT PASS reports inserted=0 total=3 rejected=0 on the second run.",
    "The malformed message is rejected for a missing timezone; the existing total remains three.",
    "CAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt."
  ],
  "lessons": [
    {
      "lesson_id": "L01",
      "title": "Set the collection boundary",
      "purpose": "A live collection adapter depends on current platform access. Practice ingestion separately so network failures cannot be mistaken for an empty successful capture.",
      "estimated_time": "6 min",
      "learning_objective": "The contract names a permitted source, a bounded capture period, and a method that does not bypass authentication.",
      "lessons_before_this": [],
      "lessons_after_this": [
        "L02"
      ]
    },
    {
      "lesson_id": "L02",
      "title": "Normalize the message format",
      "purpose": "A dedicated folder makes mistakes easy to diagnose and keeps real project files separate.",
      "estimated_time": "12 min",
      "learning_objective": "Four input lines contain three unique broadcast/message IDs, including café and an em dash.",
      "lessons_before_this": [
        "L01"
      ],
      "lessons_after_this": [
        "L03"
      ]
    },
    {
      "lesson_id": "L03",
      "title": "Build the durable archive",
      "purpose": "The SQLite composite primary key protects a broadcast/message pair across restarts. Original message text stays in the database.",
      "estimated_time": "6 min",
      "learning_objective": "CHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages.",
      "lessons_before_this": [
        "L02"
      ],
      "lessons_after_this": [
        "L04"
      ]
    },
    {
      "lesson_id": "L04",
      "title": "Recover without duplicates",
      "purpose": "Idempotence means retrying does not double-count previously saved messages.",
      "estimated_time": "6 min",
      "learning_objective": "CHAT PASS reports inserted=0 total=3 rejected=0 on the second run.",
      "lessons_before_this": [
        "L03"
      ],
      "lessons_after_this": [
        "L05"
      ]
    },
    {
      "lesson_id": "L05",
      "title": "Reject malformed data visibly",
      "purpose": "A timezone-free time cannot be safely compared across broadcasts. Keep the failure line/reason rather than assigning an invented timezone.",
      "estimated_time": "6 min",
      "learning_objective": "The malformed message is rejected for a missing timezone; the existing total remains three.",
      "lessons_before_this": [
        "L04"
      ],
      "lessons_after_this": [
        "L06"
      ]
    },
    {
      "lesson_id": "L06",
      "title": "Connect a bounded live source",
      "purpose": "Selectors are source/version-specific and must be checked on a permitted page. This is a bounded visible-browser adapter, not a promise that one hardcoded X selector works forever.",
      "estimated_time": "12 min",
      "learning_objective": "CAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt.",
      "lessons_before_this": [
        "L05"
      ],
      "lessons_after_this": [
        "L07"
      ]
    },
    {
      "lesson_id": "L07",
      "title": "Review retention and final export",
      "purpose": "CSV cells starting =, +, -, or @ are prefixed for spreadsheet safety. The raw SQLite record retains the original text.",
      "estimated_time": "6 min",
      "learning_objective": "The final receipt shows idempotent ingestion, valid export, reject handling, source provenance and a retention plan.",
      "lessons_before_this": [
        "L06"
      ],
      "lessons_after_this": [
        "L08"
      ]
    },
    {
      "lesson_id": "L08",
      "title": "Build Bot Better — protect and recover",
      "purpose": "The September 23 BuildBotsBetter profile wipe removed Desktop, Documents and hidden agent memory together. Repeatable onboarding protects the knowledge needed to recover.",
      "estimated_time": "30 min",
      "learning_objective": "RESTORE PASS contains matching hashes; the actual memory file is readable after restoration into scratch.",
      "lessons_before_this": [
        "L07"
      ],
      "lessons_after_this": []
    }
  ],
  "steps": [
    {
      "title": "Write a capture contract",
      "purpose": "A live collection adapter depends on current platform access. Practice ingestion separately so network failures cannot be mistaken for an empty successful capture.",
      "user_action": "Fill capture-notes.md with broadcast URL, access/permission, source method, start/stop time and retention period. For live X chat, inspect the currently permitted official API/export or manually save visible public messages. This starter accepts normalized JSONL; it does not claim to ship a working X DOM selector.",
      "explanation": "A live collection adapter depends on current platform access. Practice ingestion separately so network failures cannot be mistaken for an empty successful capture.",
      "estimated_time": "6 min",
      "action_type": "inspect",
      "commands": {
        "all": ""
      },
      "command_type": "shell",
      "required_inputs": [
        "capture-notes.md"
      ],
      "expected_result": "The contract names a permitted source, a bounded capture period, and a method that does not bypass authentication.",
      "resources": [
        "capture-notes.md"
      ],
      "checkpoint": {
        "what_is_being_checked": "Write a capture contract",
        "success_condition": "The contract names a permitted source, a bounded capture period, and a method that does not bypass authentication.",
        "how_to_verify": "Record the contract names a permitted source, a bounded capture period, and a method that does not bypass authentication. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "The contract names a permitted source, a bounded capture period, and a method that does not bypass authentication.",
          "I can repeat this action from the saved input."
        ],
        "tokens": [],
        "user_visible_success_message": "Evidence saved. The contract names a permitted source, a bounded capture period, and a method that does not bypass authentication.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "The result differs from the checkpoint",
          "likely_cause": "You may be in a different folder or using a different input/version.",
          "diagnostic_question": "What is the current directory, exact command, exit code and complete output?",
          "fix": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
          "alternative_fix": "Open the supplied resource and inspect the input manually; use the expected-result example to narrow the difference.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Compare the input filename and working directory with this step. Rerun only after correcting that difference."
        }
      ],
      "faqs": [
        {
          "question": "How do I verify write a capture contract?",
          "answer": "The contract names a permitted source, a bounded capture period, and a method that does not bypass authentication. Keep the command or observation with your evidence."
        },
        {
          "question": "Which files do I need for this step?",
          "answer": "Use capture-notes.md. Work in the same practice folder."
        }
      ],
      "quiz": null,
      "coach": {
        "intro": "We are going to write a capture contract. The contract names a permitted source, a bounded capture period, and a method that does not bypass authentication.",
        "demo": "Fill capture-notes.md with broadcast URL, access/permission, source method, start/stop time and retention period. For live X chat, inspect the currently permitted official API/export or manually save visible public messages. This starter accepts normalized JSONL; it does not claim to ship a working X DOM selector.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
        "help": "What is the current directory, exact command, exit code and complete output?"
      },
      "video": {
        "purpose": "Demonstrate write a capture contract",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "Fill capture-notes.md with broadcast URL, access/permission, source method, start/stop time and retention period. For live X chat, inspect the currently permitted official API/export or manually save visible public messages. This starter accepts normalized JSONL; it does not claim to ship a working X DOM selector. The contract names a permitted source, a bounded capture period, and a method that does not bypass authentication.",
        "on_screen_text": [
          "Write a capture contract",
          "The contract names a permitted source, a bounded capture period, and a method that does not bypass authentication."
        ],
        "screen_recording_instructions": [
          "Fill capture-notes.md with broadcast URL, access/permission, source method, start/stop time and retention period. For live X chat, inspect the currently permitted official API/export or manually save visible public messages. This starter accepts normalized JSONL; it does not claim to ship a working X DOM selector.",
          "Inspect the result: The contract names a permitted source, a bounded capture period, and a method that does not bypass authentication.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "The contract names a permitted source, a bounded capture period, and a method that does not bypass authentication."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "Fill capture-notes.md with broadcast URL, access/permission, source method, start/stop time and retention period. For live X chat, inspect the currently permitted official API/export or manually save visible public messages. This starter accepts normalized JSONL; it does not claim to ship a working X DOM selector.",
          "The contract names a permitted source, a bounded capture period, and a method that does not bypass authentication."
        ]
      },
      "step_id": "L01-S01",
      "lesson_id": "L01"
    },
    {
      "title": "Set up the practice folder",
      "purpose": "A dedicated folder makes mistakes easy to diagnose and keeps real project files separate.",
      "user_action": "Download the resource ZIP at the top of this Build. Extract it into a new folder you own (for example C:\\BBBPractice on Windows, or ~/bbb-practice on macOS/Linux). Open a terminal in the extracted folder. Run the version command for your platform. All subsequent commands assume this folder.",
      "explanation": "A dedicated folder makes mistakes easy to diagnose and keeps real project files separate.",
      "estimated_time": "6 min",
      "action_type": "command",
      "commands": {
        "windows": "python --version\nGet-Location\nGet-ChildItem",
        "macos": "python3 --version\npwd\nls",
        "linux": "python3 --version\npwd\nls"
      },
      "command_type": "shell",
      "required_inputs": [],
      "expected_result": "Python reports version 3.10 or newer and the terminal is in the extracted resource folder.",
      "resources": [],
      "checkpoint": {
        "what_is_being_checked": "Set up the practice folder",
        "success_condition": "Python reports version 3.10 or newer and the terminal is in the extracted resource folder.",
        "how_to_verify": "Record python reports version 3.10 or newer and the terminal is in the extracted resource folder. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "Python is 3.10 or newer.",
          "The listed files match the downloaded resources."
        ],
        "tokens": [],
        "user_visible_success_message": "Evidence saved. Python reports version 3.10 or newer and the terminal is in the extracted resource folder.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "Python or a file is not found",
          "likely_cause": "The terminal may be outside the extracted ZIP or Python may not be on PATH.",
          "diagnostic_question": "Does the file list include the resource named by the next step?",
          "fix": "Install Python from python.org with the PATH option; reopen the terminal and change into the extracted folder.",
          "alternative_fix": "Use py -3 on Windows if the Python launcher is installed. macOS/Linux commands use python3.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Install Python from python.org with the PATH option; reopen the terminal and change into the extracted folder."
        }
      ],
      "faqs": [
        {
          "question": "How do I verify set up the practice folder?",
          "answer": "Python reports version 3.10 or newer and the terminal is in the extracted resource folder. Keep the command or observation with your evidence."
        },
        {
          "question": "Which files do I need for this step?",
          "answer": "Use your saved work from the previous step. Work in the same practice folder."
        }
      ],
      "quiz": null,
      "coach": {
        "intro": "We are going to set up the practice folder. Python reports version 3.10 or newer and the terminal is in the extracted resource folder.",
        "demo": "Download the resource ZIP at the top of this Build. Extract it into a new folder you own (for example C:\\BBBPractice on Windows, or ~/bbb-practice on macOS/Linux). Open a terminal in the extracted folder. Run the version command for your platform. All subsequent commands assume this folder.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Install Python from python.org with the PATH option; reopen the terminal and change into the extracted folder.",
        "help": "Does the file list include the resource named by the next step?"
      },
      "video": {
        "purpose": "Demonstrate set up the practice folder",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "Download the resource ZIP at the top of this Build. Extract it into a new folder you own (for example C:\\BBBPractice on Windows, or ~/bbb-practice on macOS/Linux). Open a terminal in the extracted folder. Run the version command for your platform. All subsequent commands assume this folder. Python reports version 3.10 or newer and the terminal is in the extracted resource folder.",
        "on_screen_text": [
          "Set up the practice folder",
          "Python reports version 3.10 or newer and the terminal is in the extracted resource folder."
        ],
        "screen_recording_instructions": [
          "Download the resource ZIP at the top of this Build. Extract it into a new folder you own (for example C:\\BBBPractice on Windows, or ~/bbb-practice on macOS/Linux). Open a terminal in the extracted folder. Run the version command for your platform. All subsequent commands assume this folder.",
          "Inspect the result: Python reports version 3.10 or newer and the terminal is in the extracted resource folder.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "Python reports version 3.10 or newer and the terminal is in the extracted resource folder."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "Download the resource ZIP at the top of this Build. Extract it into a new folder you own (for example C:\\BBBPractice on Windows, or ~/bbb-practice on macOS/Linux). Open a terminal in the extracted folder. Run the version command for your platform. All subsequent commands assume this folder.",
          "Python reports version 3.10 or newer and the terminal is in the extracted resource folder."
        ]
      },
      "step_id": "L02-S01",
      "lesson_id": "L02"
    },
    {
      "title": "Inspect the JSONL sample",
      "purpose": "JSONL makes appended capture records inspectable. Deduplicate by original platform identity, not by user/text: identical text can be two real messages.",
      "user_action": "Open chat.jsonl. Each line is a JSON object with id, broadcast, user, text and at. Preserve original IDs and timezone-aware timestamps. The sample includes one repeated message and non-ASCII text.",
      "explanation": "JSONL makes appended capture records inspectable. Deduplicate by original platform identity, not by user/text: identical text can be two real messages.",
      "estimated_time": "6 min",
      "action_type": "inspect",
      "commands": {
        "all": ""
      },
      "command_type": "shell",
      "required_inputs": [
        "chat.jsonl"
      ],
      "expected_result": "Four input lines contain three unique broadcast/message IDs, including café and an em dash.",
      "resources": [
        "chat.jsonl"
      ],
      "checkpoint": {
        "what_is_being_checked": "Inspect the JSONL sample",
        "success_condition": "Four input lines contain three unique broadcast/message IDs, including café and an em dash.",
        "how_to_verify": "Record four input lines contain three unique broadcast/message IDs, including café and an em dash. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "Four input lines contain three unique broadcast/message IDs, including café and an em dash.",
          "I can repeat this action from the saved input."
        ],
        "tokens": [],
        "user_visible_success_message": "Evidence saved. Four input lines contain three unique broadcast/message IDs, including café and an em dash.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "The result differs from the checkpoint",
          "likely_cause": "You may be in a different folder or using a different input/version.",
          "diagnostic_question": "What is the current directory, exact command, exit code and complete output?",
          "fix": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
          "alternative_fix": "Open the supplied resource and inspect the input manually; use the expected-result example to narrow the difference.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Compare the input filename and working directory with this step. Rerun only after correcting that difference."
        }
      ],
      "faqs": [
        {
          "question": "How do I verify inspect the jsonl sample?",
          "answer": "Four input lines contain three unique broadcast/message IDs, including café and an em dash. Keep the command or observation with your evidence."
        },
        {
          "question": "Which files do I need for this step?",
          "answer": "Use chat.jsonl. Work in the same practice folder."
        }
      ],
      "quiz": null,
      "coach": {
        "intro": "We are going to inspect the JSONL sample. Four input lines contain three unique broadcast/message IDs, including café and an em dash.",
        "demo": "Open chat.jsonl. Each line is a JSON object with id, broadcast, user, text and at. Preserve original IDs and timezone-aware timestamps. The sample includes one repeated message and non-ASCII text.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
        "help": "What is the current directory, exact command, exit code and complete output?"
      },
      "video": {
        "purpose": "Demonstrate inspect the jsonl sample",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "Open chat.jsonl. Each line is a JSON object with id, broadcast, user, text and at. Preserve original IDs and timezone-aware timestamps. The sample includes one repeated message and non-ASCII text. Four input lines contain three unique broadcast/message IDs, including café and an em dash.",
        "on_screen_text": [
          "Inspect the JSONL sample",
          "Four input lines contain three unique broadcast/message IDs, including café and an em dash."
        ],
        "screen_recording_instructions": [
          "Open chat.jsonl. Each line is a JSON object with id, broadcast, user, text and at. Preserve original IDs and timezone-aware timestamps. The sample includes one repeated message and non-ASCII text.",
          "Inspect the result: Four input lines contain three unique broadcast/message IDs, including café and an em dash.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "Four input lines contain three unique broadcast/message IDs, including café and an em dash."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "Open chat.jsonl. Each line is a JSON object with id, broadcast, user, text and at. Preserve original IDs and timezone-aware timestamps. The sample includes one repeated message and non-ASCII text.",
          "Four input lines contain three unique broadcast/message IDs, including café and an em dash."
        ]
      },
      "step_id": "L02-S02",
      "lesson_id": "L02"
    },
    {
      "title": "Ingest and export the first capture",
      "purpose": "The SQLite composite primary key protects a broadcast/message pair across restarts. Original message text stays in the database.",
      "user_action": "Run collector.py chat.jsonl. Inspect chat.sqlite, chat-export.csv and rejected.json in the practice folder.",
      "explanation": "The SQLite composite primary key protects a broadcast/message pair across restarts. Original message text stays in the database.",
      "estimated_time": "6 min",
      "action_type": "command",
      "commands": {
        "windows": "python collector.py chat.jsonl",
        "macos": "python3 collector.py chat.jsonl",
        "linux": "python3 collector.py chat.jsonl"
      },
      "command_type": "shell",
      "required_inputs": [
        "collector.py",
        "chat.jsonl"
      ],
      "expected_result": "CHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages.",
      "resources": [
        "collector.py",
        "chat.jsonl"
      ],
      "checkpoint": {
        "what_is_being_checked": "Ingest and export the first capture",
        "success_condition": "CHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages.",
        "how_to_verify": "Record cHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "CHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages.",
          "I can repeat this action from the saved input."
        ],
        "tokens": [
          "CHAT PASS",
          "total=3"
        ],
        "user_visible_success_message": "Evidence saved. CHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "The result differs from the checkpoint",
          "likely_cause": "You may be in a different folder or using a different input/version.",
          "diagnostic_question": "What is the current directory, exact command, exit code and complete output?",
          "fix": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
          "alternative_fix": "Open the supplied resource and inspect the input manually; use the expected-result example to narrow the difference.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Compare the input filename and working directory with this step. Rerun only after correcting that difference."
        }
      ],
      "faqs": [
        {
          "question": "How do I verify ingest and export the first capture?",
          "answer": "CHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages. Keep the command or observation with your evidence."
        },
        {
          "question": "Which files do I need for this step?",
          "answer": "Use collector.py, chat.jsonl. Work in the same practice folder."
        }
      ],
      "quiz": null,
      "coach": {
        "intro": "We are going to ingest and export the first capture. CHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages.",
        "demo": "Run collector.py chat.jsonl. Inspect chat.sqlite, chat-export.csv and rejected.json in the practice folder.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
        "help": "What is the current directory, exact command, exit code and complete output?"
      },
      "video": {
        "purpose": "Demonstrate ingest and export the first capture",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "Run collector.py chat.jsonl. Inspect chat.sqlite, chat-export.csv and rejected.json in the practice folder. CHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages.",
        "on_screen_text": [
          "Ingest and export the first capture",
          "CHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages."
        ],
        "screen_recording_instructions": [
          "Run collector.py chat.jsonl. Inspect chat.sqlite, chat-export.csv and rejected.json in the practice folder.",
          "Inspect the result: CHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "CHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "Run collector.py chat.jsonl. Inspect chat.sqlite, chat-export.csv and rejected.json in the practice folder.",
          "CHAT PASS reports inserted=3 total=3 rejected=0; CSV contains three correctly quoted messages."
        ]
      },
      "step_id": "L03-S01",
      "lesson_id": "L03"
    },
    {
      "title": "Rerun the same capture",
      "purpose": "Idempotence means retrying does not double-count previously saved messages.",
      "user_action": "Run the same command a second time. Compare inserted and total with the first run.",
      "explanation": "Idempotence means retrying does not double-count previously saved messages.",
      "estimated_time": "6 min",
      "action_type": "command",
      "commands": {
        "windows": "python collector.py chat.jsonl",
        "macos": "python3 collector.py chat.jsonl",
        "linux": "python3 collector.py chat.jsonl"
      },
      "command_type": "shell",
      "required_inputs": [
        "collector.py",
        "chat.jsonl"
      ],
      "expected_result": "CHAT PASS reports inserted=0 total=3 rejected=0 on the second run.",
      "resources": [
        "collector.py",
        "chat.jsonl"
      ],
      "checkpoint": {
        "what_is_being_checked": "Rerun the same capture",
        "success_condition": "CHAT PASS reports inserted=0 total=3 rejected=0 on the second run.",
        "how_to_verify": "Record cHAT PASS reports inserted=0 total=3 rejected=0 on the second run. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "CHAT PASS reports inserted=0 total=3 rejected=0 on the second run.",
          "I can repeat this action from the saved input."
        ],
        "tokens": [
          "inserted=0",
          "total=3"
        ],
        "user_visible_success_message": "Evidence saved. CHAT PASS reports inserted=0 total=3 rejected=0 on the second run.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "The result differs from the checkpoint",
          "likely_cause": "You may be in a different folder or using a different input/version.",
          "diagnostic_question": "What is the current directory, exact command, exit code and complete output?",
          "fix": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
          "alternative_fix": "Open the supplied resource and inspect the input manually; use the expected-result example to narrow the difference.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Compare the input filename and working directory with this step. Rerun only after correcting that difference."
        }
      ],
      "faqs": [
        {
          "question": "How do I verify rerun the same capture?",
          "answer": "CHAT PASS reports inserted=0 total=3 rejected=0 on the second run. Keep the command or observation with your evidence."
        },
        {
          "question": "Which files do I need for this step?",
          "answer": "Use collector.py, chat.jsonl. Work in the same practice folder."
        }
      ],
      "quiz": {
        "question": "Two different message IDs have the same text. What should happen?",
        "answer_type": "single_choice",
        "choices": [
          "Keep both; deduplicate the broadcast/message identity.",
          "Delete one because the text is identical."
        ],
        "correct_answer": 0,
        "explanation": "The repeated wording may be a real second message."
      },
      "coach": {
        "intro": "We are going to rerun the same capture. CHAT PASS reports inserted=0 total=3 rejected=0 on the second run.",
        "demo": "Run the same command a second time. Compare inserted and total with the first run.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
        "help": "What is the current directory, exact command, exit code and complete output?"
      },
      "video": {
        "purpose": "Demonstrate rerun the same capture",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "Run the same command a second time. Compare inserted and total with the first run. CHAT PASS reports inserted=0 total=3 rejected=0 on the second run.",
        "on_screen_text": [
          "Rerun the same capture",
          "CHAT PASS reports inserted=0 total=3 rejected=0 on the second run."
        ],
        "screen_recording_instructions": [
          "Run the same command a second time. Compare inserted and total with the first run.",
          "Inspect the result: CHAT PASS reports inserted=0 total=3 rejected=0 on the second run.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "CHAT PASS reports inserted=0 total=3 rejected=0 on the second run."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "Run the same command a second time. Compare inserted and total with the first run.",
          "CHAT PASS reports inserted=0 total=3 rejected=0 on the second run."
        ]
      },
      "step_id": "L04-S01",
      "lesson_id": "L04"
    },
    {
      "title": "Test a missing timezone",
      "purpose": "A timezone-free time cannot be safely compared across broadcasts. Keep the failure line/reason rather than assigning an invented timezone.",
      "user_action": "Copy chat.jsonl to bad-chat.jsonl. Add a valid JSON message with a unique ID but at set to 2026-10-05T10:00:03 without Z or an offset. Ingest and inspect rejected.json.",
      "explanation": "A timezone-free time cannot be safely compared across broadcasts. Keep the failure line/reason rather than assigning an invented timezone.",
      "estimated_time": "6 min",
      "action_type": "command",
      "commands": {
        "windows": "python collector.py bad-chat.jsonl",
        "macos": "python3 collector.py bad-chat.jsonl",
        "linux": "python3 collector.py bad-chat.jsonl"
      },
      "command_type": "shell",
      "required_inputs": [
        "collector.py",
        "chat.jsonl"
      ],
      "expected_result": "The malformed message is rejected for a missing timezone; the existing total remains three.",
      "resources": [
        "collector.py",
        "chat.jsonl"
      ],
      "checkpoint": {
        "what_is_being_checked": "Test a missing timezone",
        "success_condition": "The malformed message is rejected for a missing timezone; the existing total remains three.",
        "how_to_verify": "Record the malformed message is rejected for a missing timezone; the existing total remains three. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "The malformed message is rejected for a missing timezone; the existing total remains three.",
          "I can repeat this action from the saved input."
        ],
        "tokens": [],
        "user_visible_success_message": "Evidence saved. The malformed message is rejected for a missing timezone; the existing total remains three.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "The result differs from the checkpoint",
          "likely_cause": "You may be in a different folder or using a different input/version.",
          "diagnostic_question": "What is the current directory, exact command, exit code and complete output?",
          "fix": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
          "alternative_fix": "Open the supplied resource and inspect the input manually; use the expected-result example to narrow the difference.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Compare the input filename and working directory with this step. Rerun only after correcting that difference."
        }
      ],
      "faqs": [
        {
          "question": "How do I verify test a missing timezone?",
          "answer": "The malformed message is rejected for a missing timezone; the existing total remains three. Keep the command or observation with your evidence."
        },
        {
          "question": "Which files do I need for this step?",
          "answer": "Use collector.py, chat.jsonl. Work in the same practice folder."
        }
      ],
      "quiz": null,
      "coach": {
        "intro": "We are going to test a missing timezone. The malformed message is rejected for a missing timezone; the existing total remains three.",
        "demo": "Copy chat.jsonl to bad-chat.jsonl. Add a valid JSON message with a unique ID but at set to 2026-10-05T10:00:03 without Z or an offset. Ingest and inspect rejected.json.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
        "help": "What is the current directory, exact command, exit code and complete output?"
      },
      "video": {
        "purpose": "Demonstrate test a missing timezone",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "Copy chat.jsonl to bad-chat.jsonl. Add a valid JSON message with a unique ID but at set to 2026-10-05T10:00:03 without Z or an offset. Ingest and inspect rejected.json. The malformed message is rejected for a missing timezone; the existing total remains three.",
        "on_screen_text": [
          "Test a missing timezone",
          "The malformed message is rejected for a missing timezone; the existing total remains three."
        ],
        "screen_recording_instructions": [
          "Copy chat.jsonl to bad-chat.jsonl. Add a valid JSON message with a unique ID but at set to 2026-10-05T10:00:03 without Z or an offset. Ingest and inspect rejected.json.",
          "Inspect the result: The malformed message is rejected for a missing timezone; the existing total remains three.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "The malformed message is rejected for a missing timezone; the existing total remains three."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "Copy chat.jsonl to bad-chat.jsonl. Add a valid JSON message with a unique ID but at set to 2026-10-05T10:00:03 without Z or an offset. Ingest and inspect rejected.json.",
          "The malformed message is rejected for a missing timezone; the existing total remains three."
        ]
      },
      "step_id": "L05-S01",
      "lesson_id": "L05"
    },
    {
      "title": "Configure the visible-browser capture",
      "purpose": "Selectors are source/version-specific and must be checked on a permitted page. This is a bounded visible-browser adapter, not a promise that one hardcoded X selector works forever.",
      "user_action": "Use the source method permitted by the platform. For the supplied DOM adapter, inspect visible chat elements in the browser developer tools and edit capture-config.json with the real HTTPS URL, broadcast ID, message selector, stable ID attribute, user/text selectors and a 30-second duration. A missing stable ID is rejected, not fabricated. Create an isolated environment and install the capture dependency/browser.",
      "explanation": "Selectors are source/version-specific and must be checked on a permitted page. This is a bounded visible-browser adapter, not a promise that one hardcoded X selector works forever.",
      "estimated_time": "6 min",
      "action_type": "command",
      "commands": {
        "windows": "python -m venv .capture-env\n.\\.capture-env\\Scripts\\python.exe -m pip install -r requirements-capture.txt\n.\\.capture-env\\Scripts\\python.exe -m playwright install chromium",
        "macos": "python3 -m venv .capture-env\n.capture-env/bin/python -m pip install -r requirements-capture.txt\n.capture-env/bin/python -m playwright install chromium",
        "linux": "python3 -m venv .capture-env\n.capture-env/bin/python -m pip install -r requirements-capture.txt\n.capture-env/bin/python -m playwright install chromium"
      },
      "command_type": "shell",
      "required_inputs": [
        "browser_capture.py",
        "capture-config.json",
        "requirements-capture.txt",
        "capture-notes.md"
      ],
      "expected_result": "The capture configuration maps real visible elements to IDs/text and the isolated browser dependency is installed.",
      "resources": [
        "browser_capture.py",
        "capture-config.json",
        "requirements-capture.txt",
        "capture-notes.md"
      ],
      "checkpoint": {
        "what_is_being_checked": "Configure the visible-browser capture",
        "success_condition": "The capture configuration maps real visible elements to IDs/text and the isolated browser dependency is installed.",
        "how_to_verify": "Record the capture configuration maps real visible elements to IDs/text and the isolated browser dependency is installed. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "The capture configuration maps real visible elements to IDs/text and the isolated browser dependency is installed.",
          "I can repeat this action from the saved input."
        ],
        "tokens": [],
        "user_visible_success_message": "Evidence saved. The capture configuration maps real visible elements to IDs/text and the isolated browser dependency is installed.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "The result differs from the checkpoint",
          "likely_cause": "You may be in a different folder or using a different input/version.",
          "diagnostic_question": "What is the current directory, exact command, exit code and complete output?",
          "fix": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
          "alternative_fix": "Open the supplied resource and inspect the input manually; use the expected-result example to narrow the difference.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Compare the input filename and working directory with this step. Rerun only after correcting that difference."
        }
      ],
      "faqs": [
        {
          "question": "How do I verify configure the visible-browser capture?",
          "answer": "The capture configuration maps real visible elements to IDs/text and the isolated browser dependency is installed. Keep the command or observation with your evidence."
        },
        {
          "question": "Which files do I need for this step?",
          "answer": "Use browser_capture.py, capture-config.json, requirements-capture.txt, capture-notes.md. Work in the same practice folder."
        }
      ],
      "quiz": null,
      "coach": {
        "intro": "We are going to configure the visible-browser capture. The capture configuration maps real visible elements to IDs/text and the isolated browser dependency is installed.",
        "demo": "Use the source method permitted by the platform. For the supplied DOM adapter, inspect visible chat elements in the browser developer tools and edit capture-config.json with the real HTTPS URL, broadcast ID, message selector, stable ID attribute, user/text selectors and a 30-second duration. A missing stable ID is rejected, not fabricated. Create an isolated environment and install the capture dependency/browser.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
        "help": "What is the current directory, exact command, exit code and complete output?"
      },
      "video": {
        "purpose": "Demonstrate configure the visible-browser capture",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "Use the source method permitted by the platform. For the supplied DOM adapter, inspect visible chat elements in the browser developer tools and edit capture-config.json with the real HTTPS URL, broadcast ID, message selector, stable ID attribute, user/text selectors and a 30-second duration. A missing stable ID is rejected, not fabricated. Create an isolated environment and install the capture dependency/browser. The capture configuration maps real visible elements to IDs/text and the isolated browser dependency is installed.",
        "on_screen_text": [
          "Configure the visible-browser capture",
          "The capture configuration maps real visible elements to IDs/text and the isolated browser dependency is installed."
        ],
        "screen_recording_instructions": [
          "Use the source method permitted by the platform. For the supplied DOM adapter, inspect visible chat elements in the browser developer tools and edit capture-config.json with the real HTTPS URL, broadcast ID, message selector, stable ID attribute, user/text selectors and a 30-second duration. A missing stable ID is rejected, not fabricated. Create an isolated environment and install the capture dependency/browser.",
          "Inspect the result: The capture configuration maps real visible elements to IDs/text and the isolated browser dependency is installed.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "The capture configuration maps real visible elements to IDs/text and the isolated browser dependency is installed."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "Use the source method permitted by the platform. For the supplied DOM adapter, inspect visible chat elements in the browser developer tools and edit capture-config.json with the real HTTPS URL, broadcast ID, message selector, stable ID attribute, user/text selectors and a 30-second duration. A missing stable ID is rejected, not fabricated. Create an isolated environment and install the capture dependency/browser.",
          "The capture configuration maps real visible elements to IDs/text and the isolated browser dependency is installed."
        ]
      },
      "step_id": "L06-S01",
      "lesson_id": "L06"
    },
    {
      "title": "Run a short permitted capture",
      "purpose": "Only a real approved source walk proves live capture. The adapter refuses an empty capture and refuses to overwrite an earlier output. Preserve the source/access status and any incomplete selector observations.",
      "user_action": "Run browser_capture.py using your reviewed config. The browser is visible: complete any authorized sign-in yourself, verify chat is open, then press Enter in the terminal to capture for the configured interval. Inspect live-chat.jsonl and ingest it with collector.py. Stop on 401/403/429. If no source access is available, retain that limitation; the local fixture pass is not real X capture.",
      "explanation": "Only a real approved source walk proves live capture. The adapter refuses an empty capture and refuses to overwrite an earlier output. Preserve the source/access status and any incomplete selector observations.",
      "estimated_time": "6 min",
      "action_type": "command",
      "commands": {
        "windows": ".\\.capture-env\\Scripts\\python.exe browser_capture.py capture-config.json --out live-chat.jsonl\npython collector.py live-chat.jsonl",
        "macos": ".capture-env/bin/python browser_capture.py capture-config.json --out live-chat.jsonl\npython3 collector.py live-chat.jsonl",
        "linux": ".capture-env/bin/python browser_capture.py capture-config.json --out live-chat.jsonl\npython3 collector.py live-chat.jsonl"
      },
      "command_type": "shell",
      "required_inputs": [
        "browser_capture.py",
        "capture-config.json",
        "capture-notes.md",
        "collector.py"
      ],
      "expected_result": "CAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt.",
      "resources": [
        "browser_capture.py",
        "capture-config.json",
        "capture-notes.md",
        "collector.py"
      ],
      "checkpoint": {
        "what_is_being_checked": "Run a short permitted capture",
        "success_condition": "CAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt.",
        "how_to_verify": "Record cAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "CAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt.",
          "I can repeat this action from the saved input."
        ],
        "tokens": [
          "CAPTURE PASS",
          "CHAT PASS"
        ],
        "user_visible_success_message": "Evidence saved. CAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "The result differs from the checkpoint",
          "likely_cause": "You may be in a different folder or using a different input/version.",
          "diagnostic_question": "What is the current directory, exact command, exit code and complete output?",
          "fix": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
          "alternative_fix": "Open the supplied resource and inspect the input manually; use the expected-result example to narrow the difference.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Compare the input filename and working directory with this step. Rerun only after correcting that difference."
        }
      ],
      "faqs": [
        {
          "question": "How do I verify run a short permitted capture?",
          "answer": "CAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt. Keep the command or observation with your evidence."
        },
        {
          "question": "Which files do I need for this step?",
          "answer": "Use browser_capture.py, capture-config.json, capture-notes.md, collector.py. Work in the same practice folder."
        }
      ],
      "quiz": null,
      "coach": {
        "intro": "We are going to run a short permitted capture. CAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt.",
        "demo": "Run browser_capture.py using your reviewed config. The browser is visible: complete any authorized sign-in yourself, verify chat is open, then press Enter in the terminal to capture for the configured interval. Inspect live-chat.jsonl and ingest it with collector.py. Stop on 401/403/429. If no source access is available, retain that limitation; the local fixture pass is not real X capture.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
        "help": "What is the current directory, exact command, exit code and complete output?"
      },
      "video": {
        "purpose": "Demonstrate run a short permitted capture",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "Run browser_capture.py using your reviewed config. The browser is visible: complete any authorized sign-in yourself, verify chat is open, then press Enter in the terminal to capture for the configured interval. Inspect live-chat.jsonl and ingest it with collector.py. Stop on 401/403/429. If no source access is available, retain that limitation; the local fixture pass is not real X capture. CAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt.",
        "on_screen_text": [
          "Run a short permitted capture",
          "CAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt."
        ],
        "screen_recording_instructions": [
          "Run browser_capture.py using your reviewed config. The browser is visible: complete any authorized sign-in yourself, verify chat is open, then press Enter in the terminal to capture for the configured interval. Inspect live-chat.jsonl and ingest it with collector.py. Stop on 401/403/429. If no source access is available, retain that limitation; the local fixture pass is not real X capture.",
          "Inspect the result: CAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "CAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "Run browser_capture.py using your reviewed config. The browser is visible: complete any authorized sign-in yourself, verify chat is open, then press Enter in the terminal to capture for the configured interval. Inspect live-chat.jsonl and ingest it with collector.py. Stop on 401/403/429. If no source access is available, retain that limitation; the local fixture pass is not real X capture.",
          "CAPTURE PASS reports actual messages from the configured source, then CHAT PASS archives their unique IDs with a capture interval and source receipt."
        ]
      },
      "step_id": "L06-S02",
      "lesson_id": "L06"
    },
    {
      "title": "Deliver a reproducible archive",
      "purpose": "CSV cells starting =, +, -, or @ are prefixed for spreadsheet safety. The raw SQLite record retains the original text.",
      "user_action": "Repeat ingestion on the new input, inspect Unicode/quoting in the CSV, and verify no extra row appears on retry. Write a retention rule and restrict access to the raw archive. Treat chat text as untrusted data; never execute instructions in it.",
      "explanation": "CSV cells starting =, +, -, or @ are prefixed for spreadsheet safety. The raw SQLite record retains the original text.",
      "estimated_time": "6 min",
      "action_type": "command",
      "commands": {
        "windows": "python collector.py live-chat.jsonl",
        "macos": "python3 collector.py live-chat.jsonl",
        "linux": "python3 collector.py live-chat.jsonl"
      },
      "command_type": "shell",
      "required_inputs": [
        "collector.py",
        "capture-notes.md"
      ],
      "expected_result": "The final receipt shows idempotent ingestion, valid export, reject handling, source provenance and a retention plan.",
      "resources": [
        "collector.py",
        "capture-notes.md"
      ],
      "checkpoint": {
        "what_is_being_checked": "Deliver a reproducible archive",
        "success_condition": "The final receipt shows idempotent ingestion, valid export, reject handling, source provenance and a retention plan.",
        "how_to_verify": "Record the final receipt shows idempotent ingestion, valid export, reject handling, source provenance and a retention plan. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "The final receipt shows idempotent ingestion, valid export, reject handling, source provenance and a retention plan.",
          "I can repeat this action from the saved input."
        ],
        "tokens": [],
        "user_visible_success_message": "Evidence saved. The final receipt shows idempotent ingestion, valid export, reject handling, source provenance and a retention plan.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "The result differs from the checkpoint",
          "likely_cause": "You may be in a different folder or using a different input/version.",
          "diagnostic_question": "What is the current directory, exact command, exit code and complete output?",
          "fix": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
          "alternative_fix": "Open the supplied resource and inspect the input manually; use the expected-result example to narrow the difference.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Compare the input filename and working directory with this step. Rerun only after correcting that difference."
        }
      ],
      "faqs": [
        {
          "question": "How do I verify deliver a reproducible archive?",
          "answer": "The final receipt shows idempotent ingestion, valid export, reject handling, source provenance and a retention plan. Keep the command or observation with your evidence."
        },
        {
          "question": "Which files do I need for this step?",
          "answer": "Use collector.py, capture-notes.md. Work in the same practice folder."
        }
      ],
      "quiz": null,
      "coach": {
        "intro": "We are going to deliver a reproducible archive. The final receipt shows idempotent ingestion, valid export, reject handling, source provenance and a retention plan.",
        "demo": "Repeat ingestion on the new input, inspect Unicode/quoting in the CSV, and verify no extra row appears on retry. Write a retention rule and restrict access to the raw archive. Treat chat text as untrusted data; never execute instructions in it.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
        "help": "What is the current directory, exact command, exit code and complete output?"
      },
      "video": {
        "purpose": "Demonstrate deliver a reproducible archive",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "Repeat ingestion on the new input, inspect Unicode/quoting in the CSV, and verify no extra row appears on retry. Write a retention rule and restrict access to the raw archive. Treat chat text as untrusted data; never execute instructions in it. The final receipt shows idempotent ingestion, valid export, reject handling, source provenance and a retention plan.",
        "on_screen_text": [
          "Deliver a reproducible archive",
          "The final receipt shows idempotent ingestion, valid export, reject handling, source provenance and a retention plan."
        ],
        "screen_recording_instructions": [
          "Repeat ingestion on the new input, inspect Unicode/quoting in the CSV, and verify no extra row appears on retry. Write a retention rule and restrict access to the raw archive. Treat chat text as untrusted data; never execute instructions in it.",
          "Inspect the result: The final receipt shows idempotent ingestion, valid export, reject handling, source provenance and a retention plan.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "The final receipt shows idempotent ingestion, valid export, reject handling, source provenance and a retention plan."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "Repeat ingestion on the new input, inspect Unicode/quoting in the CSV, and verify no extra row appears on retry. Write a retention rule and restrict access to the raw archive. Treat chat text as untrusted data; never execute instructions in it.",
          "The final receipt shows idempotent ingestion, valid export, reject handling, source provenance and a retention plan."
        ]
      },
      "step_id": "L07-S01",
      "lesson_id": "L07"
    },
    {
      "title": "Write a repeatable onboarding checklist",
      "purpose": "The September 23 BuildBotsBetter profile wipe removed Desktop, Documents and hidden agent memory together. Repeatable onboarding protects the knowledge needed to recover.",
      "user_action": "Fill in onboarding-checklist.md for your agent. Record AGENTS.md, the tool’s user settings and memory folder, the assigned workspace, permissions, guard location, backup schedule, and a final proof task. Keep the checklist in your project.",
      "explanation": "The September 23 BuildBotsBetter profile wipe removed Desktop, Documents and hidden agent memory together. Repeatable onboarding protects the knowledge needed to recover.",
      "estimated_time": "6 min",
      "action_type": "inspect",
      "commands": {
        "all": ""
      },
      "command_type": "shell",
      "required_inputs": [
        "onboarding-checklist.md"
      ],
      "expected_result": "The checklist contains a concrete path and owner for rules, memory, permissions, guard, backup, restore and final verification.",
      "resources": [
        "onboarding-checklist.md"
      ],
      "checkpoint": {
        "what_is_being_checked": "Write a repeatable onboarding checklist",
        "success_condition": "The checklist contains a concrete path and owner for rules, memory, permissions, guard, backup, restore and final verification.",
        "how_to_verify": "Record the checklist contains a concrete path and owner for rules, memory, permissions, guard, backup, restore and final verification. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "The checklist contains a concrete path and owner for rules, memory, permissions, guard, backup, restore and final verification.",
          "I can repeat this action from the saved input."
        ],
        "tokens": [],
        "user_visible_success_message": "Evidence saved. The checklist contains a concrete path and owner for rules, memory, permissions, guard, backup, restore and final verification.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "The result differs from the checkpoint",
          "likely_cause": "You may be in a different folder or using a different input/version.",
          "diagnostic_question": "What is the current directory, exact command, exit code and complete output?",
          "fix": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
          "alternative_fix": "Open the supplied resource and inspect the input manually; use the expected-result example to narrow the difference.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Compare the input filename and working directory with this step. Rerun only after correcting that difference."
        }
      ],
      "faqs": [
        {
          "question": "How do I verify write a repeatable onboarding checklist?",
          "answer": "The checklist contains a concrete path and owner for rules, memory, permissions, guard, backup, restore and final verification. Keep the command or observation with your evidence."
        },
        {
          "question": "Which files do I need for this step?",
          "answer": "Use onboarding-checklist.md. Work in the same practice folder."
        }
      ],
      "quiz": null,
      "coach": {
        "intro": "We are going to write a repeatable onboarding checklist. The checklist contains a concrete path and owner for rules, memory, permissions, guard, backup, restore and final verification.",
        "demo": "Fill in onboarding-checklist.md for your agent. Record AGENTS.md, the tool’s user settings and memory folder, the assigned workspace, permissions, guard location, backup schedule, and a final proof task. Keep the checklist in your project.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
        "help": "What is the current directory, exact command, exit code and complete output?"
      },
      "video": {
        "purpose": "Demonstrate write a repeatable onboarding checklist",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "Fill in onboarding-checklist.md for your agent. Record AGENTS.md, the tool’s user settings and memory folder, the assigned workspace, permissions, guard location, backup schedule, and a final proof task. Keep the checklist in your project. The checklist contains a concrete path and owner for rules, memory, permissions, guard, backup, restore and final verification.",
        "on_screen_text": [
          "Write a repeatable onboarding checklist",
          "The checklist contains a concrete path and owner for rules, memory, permissions, guard, backup, restore and final verification."
        ],
        "screen_recording_instructions": [
          "Fill in onboarding-checklist.md for your agent. Record AGENTS.md, the tool’s user settings and memory folder, the assigned workspace, permissions, guard location, backup schedule, and a final proof task. Keep the checklist in your project.",
          "Inspect the result: The checklist contains a concrete path and owner for rules, memory, permissions, guard, backup, restore and final verification.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "The checklist contains a concrete path and owner for rules, memory, permissions, guard, backup, restore and final verification."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "Fill in onboarding-checklist.md for your agent. Record AGENTS.md, the tool’s user settings and memory folder, the assigned workspace, permissions, guard location, backup schedule, and a final proof task. Keep the checklist in your project.",
          "The checklist contains a concrete path and owner for rules, memory, permissions, guard, backup, restore and final verification."
        ]
      },
      "step_id": "L08-S01",
      "lesson_id": "L08"
    },
    {
      "title": "Demonstrate the variable-path limit safely",
      "purpose": "A command-text hook can miss a target computed inside a script or variable. This demonstrator checks resolved paths but is not an operating-system sandbox. For a real operation, enforce the resolved boundary at the operation itself; symlinks and races still require care.",
      "user_action": "Run workspace_guard.py --self-test. Read the source: the target is resolved before deciding. The variable_target case points at the home folder but performs no filesystem operation. Compare this with a hook that sees only command text.",
      "explanation": "A command-text hook can miss a target computed inside a script or variable. This demonstrator checks resolved paths but is not an operating-system sandbox. For a real operation, enforce the resolved boundary at the operation itself; symlinks and races still require care.",
      "estimated_time": "6 min",
      "action_type": "command",
      "commands": {
        "windows": "python workspace_guard.py --self-test",
        "macos": "python3 workspace_guard.py --self-test",
        "linux": "python3 workspace_guard.py --self-test"
      },
      "command_type": "shell",
      "required_inputs": [
        "workspace_guard.py"
      ],
      "expected_result": "GUARD PASS reports inside allowed and profile, outside, variable-derived profile and workspace root denied; no deletion was executed.",
      "resources": [
        "workspace_guard.py"
      ],
      "checkpoint": {
        "what_is_being_checked": "Demonstrate the variable-path limit safely",
        "success_condition": "GUARD PASS reports inside allowed and profile, outside, variable-derived profile and workspace root denied; no deletion was executed.",
        "how_to_verify": "Record gUARD PASS reports inside allowed and profile, outside, variable-derived profile and workspace root denied; no deletion was executed. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "GUARD PASS reports inside allowed and profile, outside, variable-derived profile and workspace root denied; no deletion was executed.",
          "I can repeat this action from the saved input."
        ],
        "tokens": [
          "GUARD PASS"
        ],
        "user_visible_success_message": "Evidence saved. GUARD PASS reports inside allowed and profile, outside, variable-derived profile and workspace root denied; no deletion was executed.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "The result differs from the checkpoint",
          "likely_cause": "You may be in a different folder or using a different input/version.",
          "diagnostic_question": "What is the current directory, exact command, exit code and complete output?",
          "fix": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
          "alternative_fix": "Open the supplied resource and inspect the input manually; use the expected-result example to narrow the difference.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Compare the input filename and working directory with this step. Rerun only after correcting that difference."
        }
      ],
      "faqs": [
        {
          "question": "How do I verify demonstrate the variable-path limit safely?",
          "answer": "GUARD PASS reports inside allowed and profile, outside, variable-derived profile and workspace root denied; no deletion was executed. Keep the command or observation with your evidence."
        },
        {
          "question": "Which files do I need for this step?",
          "answer": "Use workspace_guard.py. Work in the same practice folder."
        }
      ],
      "quiz": {
        "question": "Does a command-text hook guarantee safety for a path computed inside a script?",
        "answer_type": "single_choice",
        "choices": [
          "No; add enforcement at the resolved operation boundary and independent recovery.",
          "Yes, because the command looks harmless."
        ],
        "correct_answer": 0,
        "explanation": "The profile-wipe failure can involve a runtime variable whose value the text hook cannot see."
      },
      "coach": {
        "intro": "We are going to demonstrate the variable-path limit safely. GUARD PASS reports inside allowed and profile, outside, variable-derived profile and workspace root denied; no deletion was executed.",
        "demo": "Run workspace_guard.py --self-test. Read the source: the target is resolved before deciding. The variable_target case points at the home folder but performs no filesystem operation. Compare this with a hook that sees only command text.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
        "help": "What is the current directory, exact command, exit code and complete output?"
      },
      "video": {
        "purpose": "Demonstrate demonstrate the variable-path limit safely",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "Run workspace_guard.py --self-test. Read the source: the target is resolved before deciding. The variable_target case points at the home folder but performs no filesystem operation. Compare this with a hook that sees only command text. GUARD PASS reports inside allowed and profile, outside, variable-derived profile and workspace root denied; no deletion was executed.",
        "on_screen_text": [
          "Demonstrate the variable-path limit safely",
          "GUARD PASS reports inside allowed and profile, outside, variable-derived profile and workspace root denied; no deletion was executed."
        ],
        "screen_recording_instructions": [
          "Run workspace_guard.py --self-test. Read the source: the target is resolved before deciding. The variable_target case points at the home folder but performs no filesystem operation. Compare this with a hook that sees only command text.",
          "Inspect the result: GUARD PASS reports inside allowed and profile, outside, variable-derived profile and workspace root denied; no deletion was executed.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "GUARD PASS reports inside allowed and profile, outside, variable-derived profile and workspace root denied; no deletion was executed."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "Run workspace_guard.py --self-test. Read the source: the target is resolved before deciding. The variable_target case points at the home folder but performs no filesystem operation. Compare this with a hook that sees only command text.",
          "GUARD PASS reports inside allowed and profile, outside, variable-derived profile and workspace root denied; no deletion was executed."
        ]
      },
      "step_id": "L08-S02",
      "lesson_id": "L08"
    },
    {
      "title": "Verify the actual protection in your tool",
      "purpose": "A broken hook that blocks everything can look like protection. Some tools fail open when hooks fail. Codex hook changes need review/trust; Grok Build needs its own deny protocol; Antigravity uses a different JSON protocol. Never treat a prompt rule or an inactive hook as enforcement.",
      "user_action": "For CLI tools, open the hook/permissions screen and confirm the configured guard is active. Use your tool’s documented pre-command hook protocol; the BBB working reference is linked in Sources. Run a harmless directory listing, then a guard dry-run targeting the profile. Do not execute a destructive profile probe. Grok Bot has no proven local shell guard here: use project-only tasks and reviewed actions, with backups.",
      "explanation": "A broken hook that blocks everything can look like protection. Some tools fail open when hooks fail. Codex hook changes need review/trust; Grok Build needs its own deny protocol; Antigravity uses a different JSON protocol. Never treat a prompt rule or an inactive hook as enforcement.",
      "estimated_time": "6 min",
      "action_type": "inspect",
      "commands": {
        "all": ""
      },
      "command_type": "shell",
      "required_inputs": [
        "workspace_guard.py",
        "onboarding-checklist.md"
      ],
      "expected_result": "A harmless command runs, the guard dry-run returns DENY, and the checklist records the actual hook protocol and active state or the Grok Bot limitation.",
      "resources": [
        "workspace_guard.py",
        "onboarding-checklist.md"
      ],
      "checkpoint": {
        "what_is_being_checked": "Verify the actual protection in your tool",
        "success_condition": "A harmless command runs, the guard dry-run returns DENY, and the checklist records the actual hook protocol and active state or the Grok Bot limitation.",
        "how_to_verify": "Record a harmless command runs, the guard dry-run returns DENY, and the checklist records the actual hook protocol and active state or the Grok Bot limitation. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "The harmless command succeeds.",
          "The boundary dry-run denies a profile target.",
          "I recorded the tool’s active hook state, or explicitly recorded that this tool has no working local guard."
        ],
        "tokens": [],
        "user_visible_success_message": "Evidence saved. A harmless command runs, the guard dry-run returns DENY, and the checklist records the actual hook protocol and active state or the Grok Bot limitation.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "The result differs from the checkpoint",
          "likely_cause": "You may be in a different folder or using a different input/version.",
          "diagnostic_question": "What is the current directory, exact command, exit code and complete output?",
          "fix": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
          "alternative_fix": "Open the supplied resource and inspect the input manually; use the expected-result example to narrow the difference.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Compare the input filename and working directory with this step. Rerun only after correcting that difference."
        }
      ],
      "faqs": [
        {
          "question": "How do I install a guard for this tool?",
          "answer": "Use the linked BBB safety reference for the exact tool/version protocol. Merge into the existing hooks instead of overwriting settings. Review/trust the hook if required, then verify a harmless call and a denied dry-run. The workspace_guard.py file is a teaching demonstration, not a universal hook adapter."
        },
        {
          "question": "Does Grok Bot have a working local delete guard?",
          "answer": "The project’s live September 29 tests found no working shell hook. Restrict the tasks and workspace you hand it, keep independent backups, and do not claim local deletion is guarded."
        }
      ],
      "quiz": null,
      "coach": {
        "intro": "We are going to verify the actual protection in your tool. A harmless command runs, the guard dry-run returns DENY, and the checklist records the actual hook protocol and active state or the Grok Bot limitation.",
        "demo": "For CLI tools, open the hook/permissions screen and confirm the configured guard is active. Use your tool’s documented pre-command hook protocol; the BBB working reference is linked in Sources. Run a harmless directory listing, then a guard dry-run targeting the profile. Do not execute a destructive profile probe. Grok Bot has no proven local shell guard here: use project-only tasks and reviewed actions, with backups.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
        "help": "What is the current directory, exact command, exit code and complete output?"
      },
      "video": {
        "purpose": "Demonstrate verify the actual protection in your tool",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "For CLI tools, open the hook/permissions screen and confirm the configured guard is active. Use your tool’s documented pre-command hook protocol; the BBB working reference is linked in Sources. Run a harmless directory listing, then a guard dry-run targeting the profile. Do not execute a destructive profile probe. Grok Bot has no proven local shell guard here: use project-only tasks and reviewed actions, with backups. A harmless command runs, the guard dry-run returns DENY, and the checklist records the actual hook protocol and active state or the Grok Bot limitation.",
        "on_screen_text": [
          "Verify the actual protection in your tool",
          "A harmless command runs, the guard dry-run returns DENY, and the checklist records the actual hook protocol and active state or the Grok Bot limitation."
        ],
        "screen_recording_instructions": [
          "For CLI tools, open the hook/permissions screen and confirm the configured guard is active. Use your tool’s documented pre-command hook protocol; the BBB working reference is linked in Sources. Run a harmless directory listing, then a guard dry-run targeting the profile. Do not execute a destructive profile probe. Grok Bot has no proven local shell guard here: use project-only tasks and reviewed actions, with backups.",
          "Inspect the result: A harmless command runs, the guard dry-run returns DENY, and the checklist records the actual hook protocol and active state or the Grok Bot limitation.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "A harmless command runs, the guard dry-run returns DENY, and the checklist records the actual hook protocol and active state or the Grok Bot limitation."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "For CLI tools, open the hook/permissions screen and confirm the configured guard is active. Use your tool’s documented pre-command hook protocol; the BBB working reference is linked in Sources. Run a harmless directory listing, then a guard dry-run targeting the profile. Do not execute a destructive profile probe. Grok Bot has no proven local shell guard here: use project-only tasks and reviewed actions, with backups.",
          "A harmless command runs, the guard dry-run returns DENY, and the checklist records the actual hook protocol and active state or the Grok Bot limitation."
        ]
      },
      "step_id": "L08-S03",
      "lesson_id": "L08"
    },
    {
      "title": "Enable a real backup of memory and project files",
      "purpose": "System Restore alone is not a personal-file backup. Previous Versions can disappear as shadow storage fills; OneDrive’s common-folder backup omits hidden agent folders.",
      "user_action": "Connect a second backup drive. Windows: configure System Protection as additional recovery and use a scheduled copy/File History that explicitly includes hidden agent folders. macOS: configure Time Machine and verify dot folders are included. Linux: use a home backup such as restic/Deja Dup; Timeshift omits /home by default. Download the copy-only profile backup script for your platform, inspect it, and adapt the folder list to your tool. Record the last successful scheduled run; protect backup access because it may contain credentials.",
      "explanation": "System Restore alone is not a personal-file backup. Previous Versions can disappear as shadow storage fills; OneDrive’s common-folder backup omits hidden agent folders.",
      "estimated_time": "6 min",
      "action_type": "command",
      "commands": {
        "windows": ".\\profile-backup.ps1 -Destination D:\\Backups\\llm-profiles",
        "macos": "bash profile-backup.sh /Volumes/YourBackup/llm-profiles",
        "linux": "bash profile-backup.sh /media/YourBackup/llm-profiles"
      },
      "command_type": "shell",
      "required_inputs": [
        "profile-backup.ps1",
        "profile-backup.sh",
        "onboarding-checklist.md"
      ],
      "expected_result": "A backup on another drive includes the tool’s user settings and memory folder and this project, and a successful schedule/time is recorded.",
      "resources": [
        "profile-backup.ps1",
        "profile-backup.sh",
        "onboarding-checklist.md"
      ],
      "checkpoint": {
        "what_is_being_checked": "Enable a real backup of memory and project files",
        "success_condition": "A backup on another drive includes the tool’s user settings and memory folder and this project, and a successful schedule/time is recorded.",
        "how_to_verify": "Record a backup on another drive includes the tool’s user settings and memory folder and this project, and a successful schedule/time is recorded. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "The destination is a mounted second drive.",
          "The hidden memory/settings folders and project files are included.",
          "An automated backup is enabled and its latest successful run is recorded."
        ],
        "tokens": [],
        "user_visible_success_message": "Evidence saved. A backup on another drive includes the tool’s user settings and memory folder and this project, and a successful schedule/time is recorded.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "The backup drive is missing or the script reports a failure",
          "likely_cause": "The destination may be on the same volume, unmounted, or unwritable.",
          "diagnostic_question": "Can you create a scratch file on the actual second drive?",
          "fix": "Mount the drive and choose an accessible backup directory. Inspect every error; robocopy codes 8 and higher are failures.",
          "alternative_fix": "Use the operating system backup application and verify the same folder coverage and successful run.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Mount the drive and choose an accessible backup directory. Inspect every error; robocopy codes 8 and higher are failures."
        }
      ],
      "faqs": [
        {
          "question": "How do I verify enable a real backup of memory and project files?",
          "answer": "A backup on another drive includes the tool’s user settings and memory folder and this project, and a successful schedule/time is recorded. Keep the command or observation with your evidence."
        },
        {
          "question": "Which files do I need for this step?",
          "answer": "Use profile-backup.ps1, profile-backup.sh, onboarding-checklist.md. Work in the same practice folder."
        }
      ],
      "quiz": null,
      "coach": {
        "intro": "We are going to enable a real backup of memory and project files. A backup on another drive includes the tool’s user settings and memory folder and this project, and a successful schedule/time is recorded.",
        "demo": "Connect a second backup drive. Windows: configure System Protection as additional recovery and use a scheduled copy/File History that explicitly includes hidden agent folders. macOS: configure Time Machine and verify dot folders are included. Linux: use a home backup such as restic/Deja Dup; Timeshift omits /home by default. Download the copy-only profile backup script for your platform, inspect it, and adapt the folder list to your tool. Record the last successful scheduled run; protect backup access because it may contain credentials.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Mount the drive and choose an accessible backup directory. Inspect every error; robocopy codes 8 and higher are failures.",
        "help": "Can you create a scratch file on the actual second drive?"
      },
      "video": {
        "purpose": "Demonstrate enable a real backup of memory and project files",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "Connect a second backup drive. Windows: configure System Protection as additional recovery and use a scheduled copy/File History that explicitly includes hidden agent folders. macOS: configure Time Machine and verify dot folders are included. Linux: use a home backup such as restic/Deja Dup; Timeshift omits /home by default. Download the copy-only profile backup script for your platform, inspect it, and adapt the folder list to your tool. Record the last successful scheduled run; protect backup access because it may contain credentials. A backup on another drive includes the tool’s user settings and memory folder and this project, and a successful schedule/time is recorded.",
        "on_screen_text": [
          "Enable a real backup of memory and project files",
          "A backup on another drive includes the tool’s user settings and memory folder and this project, and a successful schedule/time is recorded."
        ],
        "screen_recording_instructions": [
          "Connect a second backup drive. Windows: configure System Protection as additional recovery and use a scheduled copy/File History that explicitly includes hidden agent folders. macOS: configure Time Machine and verify dot folders are included. Linux: use a home backup such as restic/Deja Dup; Timeshift omits /home by default. Download the copy-only profile backup script for your platform, inspect it, and adapt the folder list to your tool. Record the last successful scheduled run; protect backup access because it may contain credentials.",
          "Inspect the result: A backup on another drive includes the tool’s user settings and memory folder and this project, and a successful schedule/time is recorded.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "A backup on another drive includes the tool’s user settings and memory folder and this project, and a successful schedule/time is recorded."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "Connect a second backup drive. Windows: configure System Protection as additional recovery and use a scheduled copy/File History that explicitly includes hidden agent folders. macOS: configure Time Machine and verify dot folders are included. Linux: use a home backup such as restic/Deja Dup; Timeshift omits /home by default. Download the copy-only profile backup script for your platform, inspect it, and adapt the folder list to your tool. Record the last successful scheduled run; protect backup access because it may contain credentials.",
          "A backup on another drive includes the tool’s user settings and memory folder and this project, and a successful schedule/time is recorded."
        ]
      },
      "step_id": "L08-S04",
      "lesson_id": "L08"
    },
    {
      "title": "Practice a hash-verified restore",
      "purpose": "A backup becomes useful only when restoration has been practiced. A same-volume practice copy proves the procedure but not protection against disk failure.",
      "user_action": "Run restore_drill.py with a separate backup directory. The script creates a new proof file, copies it, restores a second copy, and compares SHA256 hashes. Then restore one non-secret file from your actual agent memory backup into a separate scratch directory and open it; do not overwrite the live profile.",
      "explanation": "A backup becomes useful only when restoration has been practiced. A same-volume practice copy proves the procedure but not protection against disk failure.",
      "estimated_time": "6 min",
      "action_type": "command",
      "commands": {
        "windows": "python restore_drill.py --backup-root D:\\Backups\\bbb-practice --work-root practice",
        "macos": "python3 restore_drill.py --backup-root /Volumes/YourBackup/bbb-practice --work-root practice",
        "linux": "python3 restore_drill.py --backup-root /media/YourBackup/bbb-practice --work-root practice"
      },
      "command_type": "shell",
      "required_inputs": [
        "restore_drill.py"
      ],
      "expected_result": "RESTORE PASS contains matching hashes; the actual memory file is readable after restoration into scratch.",
      "resources": [
        "restore_drill.py"
      ],
      "checkpoint": {
        "what_is_being_checked": "Practice a hash-verified restore",
        "success_condition": "RESTORE PASS contains matching hashes; the actual memory file is readable after restoration into scratch.",
        "how_to_verify": "Record rESTORE PASS contains matching hashes; the actual memory file is readable after restoration into scratch. Include the actual result or a specific observation; do not paste credentials.",
        "criteria": [
          "The proof file SHA256 hashes match.",
          "I restored and opened a non-secret memory/settings file from my real backup.",
          "The restored file is in scratch; the live profile was not overwritten."
        ],
        "tokens": [
          "RESTORE PASS"
        ],
        "user_visible_success_message": "Evidence saved. RESTORE PASS contains matching hashes; the actual memory file is readable after restoration into scratch.",
        "failure_message": "Compare the observed output with the required result before continuing.",
        "troubleshooting_path": "Open the help for this step."
      },
      "troubleshooting": [
        {
          "problem": "The result differs from the checkpoint",
          "likely_cause": "You may be in a different folder or using a different input/version.",
          "diagnostic_question": "What is the current directory, exact command, exit code and complete output?",
          "fix": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
          "alternative_fix": "Open the supplied resource and inspect the input manually; use the expected-result example to narrow the difference.",
          "when_to_escalate": "Stop after two explained attempts if the problem needs account access, permissions or changes outside this workspace. Share redacted evidence.",
          "bot_coach_response": "Compare the input filename and working directory with this step. Rerun only after correcting that difference."
        }
      ],
      "faqs": [
        {
          "question": "How do I verify practice a hash-verified restore?",
          "answer": "RESTORE PASS contains matching hashes; the actual memory file is readable after restoration into scratch. Keep the command or observation with your evidence."
        },
        {
          "question": "Which files do I need for this step?",
          "answer": "Use restore_drill.py. Work in the same practice folder."
        }
      ],
      "quiz": null,
      "coach": {
        "intro": "We are going to practice a hash-verified restore. RESTORE PASS contains matching hashes; the actual memory file is readable after restoration into scratch.",
        "demo": "Run restore_drill.py with a separate backup directory. The script creates a new proof file, copies it, restores a second copy, and compares SHA256 hashes. Then restore one non-secret file from your actual agent memory backup into a separate scratch directory and open it; do not overwrite the live profile.",
        "handoff": "Try this in your own practice workspace, then compare the result.",
        "success": "Keep that result with your evidence.",
        "failure": "Compare the input filename and working directory with this step. Rerun only after correcting that difference.",
        "help": "What is the current directory, exact command, exit code and complete output?"
      },
      "video": {
        "purpose": "Demonstrate practice a hash-verified restore",
        "duration_target": "45–75 seconds",
        "visual_type": "step walkthrough with optional browser narration; no rendered video is claimed",
        "narration_script": "Run restore_drill.py with a separate backup directory. The script creates a new proof file, copies it, restores a second copy, and compares SHA256 hashes. Then restore one non-secret file from your actual agent memory backup into a separate scratch directory and open it; do not overwrite the live profile. RESTORE PASS contains matching hashes; the actual memory file is readable after restoration into scratch.",
        "on_screen_text": [
          "Practice a hash-verified restore",
          "RESTORE PASS contains matching hashes; the actual memory file is readable after restoration into scratch."
        ],
        "screen_recording_instructions": [
          "Run restore_drill.py with a separate backup directory. The script creates a new proof file, copies it, restores a second copy, and compares SHA256 hashes. Then restore one non-secret file from your actual agent memory backup into a separate scratch directory and open it; do not overwrite the live profile.",
          "Inspect the result: RESTORE PASS contains matching hashes; the actual memory file is readable after restoration into scratch.",
          "Record the evidence and check each criterion."
        ],
        "animation_instructions": "Highlight the action, then the corresponding result; avoid motion during command copying.",
        "cursor_instructions": "Point to the exact input and checkpoint; do not hide errors.",
        "important_visuals": [
          "RESTORE PASS contains matching hashes; the actual memory file is readable after restoration into scratch."
        ],
        "bot_position": "right sidebar",
        "bot_expression": "calm",
        "transition": "Continue after checkpoint",
        "captions": [
          "Run restore_drill.py with a separate backup directory. The script creates a new proof file, copies it, restores a second copy, and compares SHA256 hashes. Then restore one non-secret file from your actual agent memory backup into a separate scratch directory and open it; do not overwrite the live profile.",
          "RESTORE PASS contains matching hashes; the actual memory file is readable after restoration into scratch."
        ]
      },
      "step_id": "L08-S05",
      "lesson_id": "L08"
    }
  ],
  "resources": [
    {
      "resource_id": "R001",
      "name": "workspace_guard.py",
      "type": "py",
      "purpose": "Working resource used by this Build; inspect before running.",
      "contents": "# VERSION HISTORY\n# 2026-10-05 EDT | chatgpt_codex | overnight-catalog | v1: learner resource.\n\"\"\"Dry-run resolved-path boundary demonstration. This script never deletes anything.\"\"\"\nimport argparse\nfrom pathlib import Path\n\ndef permitted(target, workspace):\n    root = Path(workspace).resolve()\n    candidate = Path(target).resolve()\n    home = Path.home().resolve()\n    if root == home or root == root.anchor or root in home.parents:\n        return False\n    return candidate != root and root in candidate.parents and candidate != home and candidate not in home.parents\n\ndef main():\n    p=argparse.ArgumentParser(description=__doc__)\n    p.add_argument('--workspace',default='practice')\n    p.add_argument('--target')\n    p.add_argument('--self-test',action='store_true')\n    a=p.parse_args()\n    root=Path(a.workspace).resolve()\n    if a.self_test:\n        assert permitted(root/'proof.txt',root)\n        assert not permitted(Path.home(),root)\n        assert not permitted(root.parent/'outside.txt',root)\n        assert not permitted(root,root)\n        variable_target=Path.home()\n        assert not permitted(variable_target,root)\n        print('GUARD PASS: inside allowed; profile, variable-derived profile, outside, workspace root denied; no deletion executed')\n    else:\n        if not a.target:p.error('--target required')\n        allowed=permitted(a.target,a.workspace)\n        print(('ALLOW' if allowed else 'DENY')+': resolved boundary; dry-run only')\n        raise SystemExit(0 if allowed else 2)\nif __name__=='__main__':main()\n",
      "filename": "workspace_guard.py",
      "generated_from": "BUILD_SPEC.json.resources",
      "version": "2.0"
    },
    {
      "resource_id": "R002",
      "name": "restore_drill.py",
      "type": "py",
      "purpose": "Working resource used by this Build; inspect before running.",
      "contents": "# VERSION HISTORY\n# 2026-10-05 EDT | chatgpt_codex | overnight-catalog | v1: learner resource.\n\"\"\"Backup and restore a new proof file only; never edits an existing file or profile.\"\"\"\nimport argparse,hashlib,json,logging\nfrom datetime import datetime,timezone\nfrom pathlib import Path\nimport shutil\ndef digest(p):return hashlib.sha256(p.read_bytes()).hexdigest()\ndef main():\n    a=argparse.ArgumentParser(description=__doc__)\n    a.add_argument('--backup-root',required=True)\n    a.add_argument('--work-root',default='practice')\n    x=a.parse_args()\n    run=datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%S%fZ')\n    work=Path(x.work_root).resolve()/('restore-'+run)\n    backup=Path(x.backup_root).resolve()/('restore-'+run)\n    if work==backup or work in backup.parents or backup in work.parents:raise SystemExit('Use separate, non-overlapping work and backup roots')\n    work.mkdir(parents=True,exist_ok=False);backup.mkdir(parents=True,exist_ok=False)\n    logging.basicConfig(filename=work/'drill.log',encoding='utf-8',level=logging.INFO)\n    source=work/'proof.txt';source.write_text('BuildBotsBetter restore proof '+run,encoding='utf-8')\n    shutil.copy2(source,backup/'proof.txt')\n    restored=work/'restored.txt';shutil.copy2(backup/'proof.txt',restored)\n    assert digest(source)==digest(restored)\n    receipt={'result':'RESTORE PASS','source_sha256':digest(source),'restored_sha256':digest(restored),'backup_root':str(backup),'off_volume':work.anchor.lower()!=backup.anchor.lower(),'scope':'new proof file only; does not establish a scheduled profile backup'}\n    (work/'receipt.json').write_text(json.dumps(receipt,indent=2),encoding='utf-8')\n    logging.info('Proof backup and hash-verified restore completed')\n    print(json.dumps(receipt,indent=2))\n    if not receipt['off_volume']:print('SAME VOLUME: practice passed; configure a second drive for disaster recovery')\nif __name__=='__main__':main()\n",
      "filename": "restore_drill.py",
      "generated_from": "BUILD_SPEC.json.resources",
      "version": "2.0"
    },
    {
      "resource_id": "R003",
      "name": "onboarding-checklist.md",
      "type": "md",
      "purpose": "Working resource used by this Build; inspect before running.",
      "contents": "# Repeatable agent onboarding\nagent:\ntool version:\nrules file:\nmemory/settings location:\nassigned workspace:\npermissions / actions needing approval:\nhook location and protocol:\nguard active (harmless command + denied dry-run):\nvariable-derived path limit understood:\nbackup destination (second drive):\nbackup schedule / last successful run:\nrestored memory file / matching SHA256:\nfinal task / evidence / reviewer:\n\nProtect memory/settings and project files. A prompt rule is not an operating-system boundary.\nA command-text hook can miss a path computed in a script or variable. Resolve paths at the\noperation boundary and fail closed; keep independent backups and monitoring.\nWindows System Restore reverts system components, not a reliable personal-file backup.\nOneDrive folder backup does not automatically include hidden agent folders.\nmacOS: check Time Machine includes hidden agent folders. Linux: Timeshift excludes /home by\ndefault; use a separate home backup or explicitly include it.\n",
      "filename": "onboarding-checklist.md",
      "generated_from": "BUILD_SPEC.json.resources",
      "version": "2.0"
    },
    {
      "resource_id": "R004",
      "name": "profile-backup.ps1",
      "type": "ps1",
      "purpose": "Working resource used by this Build; inspect before running.",
      "contents": "# VERSION HISTORY\n# 2026-10-05 EDT | chatgpt_codex | overnight-catalog | v1: copy-only explicit profile backup.\nparam([Parameter(Mandatory=$true)][string]$Destination,[string]$ProjectPath=(Get-Location).Path)\n$ErrorActionPreference='Stop'\n$destRoot=[IO.Path]::GetFullPath($Destination)\n$profileRoot=[IO.Path]::GetFullPath($env:USERPROFILE)\nif ([IO.Path]::GetPathRoot($destRoot) -eq [IO.Path]::GetPathRoot($profileRoot)) { throw 'Choose a backup destination on a second drive.' }\nif (-not (Test-Path -LiteralPath ([IO.Path]::GetPathRoot($destRoot)))) { throw 'Backup drive is unavailable.' }\n$runDir=Join-Path $destRoot (Get-Date -Format 'yyyyMMdd-HHmmss-fff')\nNew-Item -ItemType Directory -Path $runDir -ErrorAction Stop | Out-Null\nforeach ($folderName in @('.codex','.claude','.grok','.qwen','.gemini')) {\n  $sourceDir=Join-Path $profileRoot $folderName\n  if (Test-Path -LiteralPath $sourceDir) {\n    $copyDir=Join-Path $runDir $folderName\n    & robocopy $sourceDir $copyDir /E /XJ /R:1 /W:1 /COPY:DAT /DCOPY:DAT /NP\n    if ($LASTEXITCODE -ge 8) { throw \"Backup failed for $folderName (robocopy $LASTEXITCODE).\" }\n  }\n}\n$projectSource=[IO.Path]::GetFullPath($ProjectPath)\nif (-not (Test-Path -LiteralPath $projectSource -PathType Container)) { throw 'Project directory is unavailable.' }\n& robocopy $projectSource (Join-Path $runDir 'project') /E /XJ /R:1 /W:1 /COPY:DAT /DCOPY:DAT /NP\nif ($LASTEXITCODE -ge 8) { throw \"Project backup failed (robocopy $LASTEXITCODE).\" }\n\"PROFILE BACKUP PASS: $runDir\"\n# Copy-only: no /MIR, /PURGE or deletion. Backup may contain credentials; protect the drive.\n# Add the Grok Bot profile and project path explicitly after finding them in your own installation.\n",
      "filename": "profile-backup.ps1",
      "generated_from": "BUILD_SPEC.json.resources",
      "version": "2.0"
    },
    {
      "resource_id": "R005",
      "name": "profile-backup.sh",
      "type": "sh",
      "purpose": "Working resource used by this Build; inspect before running.",
      "contents": "#!/usr/bin/env bash\n# VERSION HISTORY\n# 2026-10-05 EDT | chatgpt_codex | overnight-catalog | v1: copy-only explicit profile backup.\nset -euo pipefail\ndest=${1:?Pass a mounted external-drive backup directory}\nproject=${2:-$PWD}\nmkdir -p \"$dest\"\nif [ \"$(df -P \"$HOME\" | tail -1 | awk '{print $1}')\" = \"$(df -P \"$dest\" | tail -1 | awk '{print $1}')\" ]; then\n  echo 'Choose a destination on another mounted filesystem.' >&2; exit 2\nfi\nrun=\"$dest/$(date -u +%Y%m%dT%H%M%SZ)-$$\"\nmkdir \"$run\"\nfor name in .codex .claude .grok .qwen .gemini; do\n  if [ -d \"$HOME/$name\" ]; then cp -pR \"$HOME/$name\" \"$run/$name\"; fi\ndone\nif [ ! -d \"$project\" ]; then echo 'Project directory is unavailable.' >&2; exit 2; fi\ncp -pR \"$project\" \"$run/project\"\necho \"PROFILE BACKUP PASS: $run\"\n# This backup can contain credentials; protect access to the backup drive.\n",
      "filename": "profile-backup.sh",
      "generated_from": "BUILD_SPEC.json.resources",
      "version": "2.0"
    },
    {
      "resource_id": "R006",
      "name": "capture-config.json",
      "type": "json",
      "purpose": "Working resource used by this Build; inspect before running.",
      "contents": "{\n  \"url\": \"https://example.com/replace-with-permitted-broadcast\",\n  \"broadcast\": \"your-broadcast-id\",\n  \"message_selector\": \"[data-message-id]\",\n  \"id_attribute\": \"data-message-id\",\n  \"user_selector\": \".user\",\n  \"text_selector\": \".text\",\n  \"duration_seconds\": 30\n}",
      "filename": "capture-config.json",
      "generated_from": "BUILD_SPEC.json.resources",
      "version": "2.0"
    },
    {
      "resource_id": "R007",
      "name": "requirements-capture.txt",
      "type": "txt",
      "purpose": "Working resource used by this Build; inspect before running.",
      "contents": "playwright\n",
      "filename": "requirements-capture.txt",
      "generated_from": "BUILD_SPEC.json.resources",
      "version": "2.0"
    },
    {
      "resource_id": "R008",
      "name": "browser_capture.py",
      "type": "py",
      "purpose": "Working resource used by this Build; inspect before running.",
      "contents": "# VERSION HISTORY\n# 2026-10-05 EDT | chatgpt_codex | overnight-catalog | v1: learner resource.\n\"\"\"Bounded, visible-browser DOM capture using selectors YOU verified on a permitted page.\"\"\"\nimport argparse,json,logging,time\nfrom pathlib import Path\nfrom playwright.sync_api import sync_playwright\ndef main():\n    p=argparse.ArgumentParser(description=__doc__);p.add_argument('config');p.add_argument('--out',default='live-chat.jsonl');p.add_argument('--headless',action='store_true',help='For local fixture tests only');a=p.parse_args()\n    c=json.loads(Path(a.config).read_text(encoding='utf-8'))\n    if not 1<=int(c['duration_seconds'])<=300:raise SystemExit('Duration must be 1–300 seconds')\n    if not c['url'].startswith(('https://','http://127.0.0.1:','file:///')):raise SystemExit('Use an HTTPS source or local test fixture')\n    Path('logs').mkdir(exist_ok=True);logging.basicConfig(filename='logs/capture.log',encoding='utf-8',level=logging.INFO)\n    with sync_playwright() as engine:\n        browser=engine.chromium.launch(headless=a.headless)\n        page=browser.new_page();response=page.goto(c['url'],wait_until='domcontentloaded',timeout=45000)\n        if response and response.status in (401,403,429):raise SystemExit('Access/rate-limit response; stop and review permission')\n        if not a.headless:input('Review permissions/page access. Sign in yourself if authorized, navigate to chat, then press Enter here to capture: ')\n        page.evaluate(r\"\"\"c=>{\n            window.bbbCapture={rows:[],seen:new Set(),rejected:0};\n            const collect=()=>{document.querySelectorAll(c.message_selector).forEach(el=>{\n                const id=el.getAttribute(c.id_attribute);\n                const user=el.querySelector(c.user_selector),text=el.querySelector(c.text_selector);\n                if(!id||!user||!text){window.bbbCapture.rejected++;return;}\n                if(window.bbbCapture.seen.has(id))return;\n                window.bbbCapture.seen.add(id);\n                window.bbbCapture.rows.push({id,broadcast:c.broadcast,user:user.textContent.trim(),text:text.textContent.trim(),at:new Date().toISOString()});\n            });};\n            collect();window.bbbObserver=new MutationObserver(collect);\n            window.bbbObserver.observe(document.body,{childList:true,subtree:true});\n        }\"\"\",c)\n        page.wait_for_timeout(int(c['duration_seconds'])*1000)\n        result=page.evaluate('()=>{window.bbbObserver.disconnect();return {rows:window.bbbCapture.rows,rejected:window.bbbCapture.rejected};}')\n        browser.close()\n    if not result['rows']:raise SystemExit('CAPTURE EMPTY: check the selectors/access; no successful capture is claimed')\n    out=Path(a.out)\n    if out.exists():raise SystemExit('Output already exists; choose a new --out path to preserve the earlier capture')\n    out.write_text('\\n'.join(json.dumps(r,ensure_ascii=False) for r in result['rows'])+'\\n',encoding='utf-8')\n    logging.info('captured=%d rejected_selector_observations=%d',len(result['rows']),result['rejected'])\n    print('CAPTURE PASS: '+str(len(result['rows']))+' message(s); '+str(result['rejected'])+' incomplete selector observation(s)')\nif __name__=='__main__':main()\n",
      "filename": "browser_capture.py",
      "generated_from": "BUILD_SPEC.json.resources",
      "version": "2.0"
    },
    {
      "resource_id": "R009",
      "name": "chat.jsonl",
      "type": "jsonl",
      "purpose": "Working resource used by this Build; inspect before running.",
      "contents": "{\"id\": \"m1\", \"broadcast\": \"demo-1\", \"user\": \"Viewer A\", \"text\": \"Hello, world!\", \"at\": \"2026-10-05T10:00:00Z\"}\n{\"id\": \"m2\", \"broadcast\": \"demo-1\", \"user\": \"Viewer B\", \"text\": \"Great demo — café\", \"at\": \"2026-10-05T10:00:01Z\"}\n{\"id\": \"m1\", \"broadcast\": \"demo-1\", \"user\": \"Viewer A\", \"text\": \"Hello, world!\", \"at\": \"2026-10-05T10:00:00Z\"}\n{\"id\": \"m3\", \"broadcast\": \"demo-1\", \"user\": \"Viewer C\", \"text\": \"What does verification mean?\", \"at\": \"2026-10-05T10:00:02Z\"}\n",
      "filename": "chat.jsonl",
      "generated_from": "BUILD_SPEC.json.resources",
      "version": "2.0"
    },
    {
      "resource_id": "R010",
      "name": "collector.py",
      "type": "py",
      "purpose": "Working resource used by this Build; inspect before running.",
      "contents": "# VERSION HISTORY\n# 2026-10-05 EDT | chatgpt_codex | overnight-catalog | v1: learner resource.\n\"\"\"Idempotent local JSONL ingestion for consented/public chat exports; no account automation.\"\"\"\nimport argparse,csv,json,sqlite3,logging\nfrom pathlib import Path\nfrom datetime import datetime,timezone\ndef ingest(source,db_path):\n    db=sqlite3.connect(db_path)\n    db.execute('CREATE TABLE IF NOT EXISTS messages (broadcast TEXT NOT NULL, id TEXT NOT NULL, user TEXT NOT NULL, text TEXT NOT NULL, observed_utc TEXT NOT NULL, PRIMARY KEY (broadcast,id))')\n    inserted=0;rejected=[]\n    with db:\n        for n,line in enumerate(Path(source).read_text(encoding='utf-8-sig').splitlines(),1):\n            if not line.strip():continue\n            try:\n                r=json.loads(line);dt=datetime.fromisoformat(r['at'].replace('Z','+00:00'))\n                if dt.tzinfo is None:raise ValueError('timestamp must have timezone')\n                if not r['id'] or not r['broadcast'] or not isinstance(r['text'],str) or len(r['text'])>10000:raise ValueError('invalid id, broadcast or text')\n                cur=db.execute('INSERT OR IGNORE INTO messages VALUES (?,?,?,?,?)',(r['broadcast'],r['id'],r['user'],r['text'],dt.astimezone(timezone.utc).isoformat()))\n                inserted+=cur.rowcount\n            except (ValueError,KeyError,TypeError) as e:rejected.append({'line':n,'reason':str(e)})\n    rows=db.execute('SELECT broadcast,id,user,text,observed_utc FROM messages ORDER BY observed_utc,broadcast,id').fetchall()\n    with open('chat-export.csv','w',encoding='utf-8',newline='') as f:\n        writer=csv.writer(f);writer.writerow(['broadcast','id','user','text','observed_utc'])\n        # Spreadsheet formula injection mitigation; original text stays in SQLite.\n        writer.writerows([[\"'\"+v if isinstance(v,str) and v[:1] in '=+-@' else v for v in row] for row in rows])\n    db.close();Path('rejected.json').write_text(json.dumps(rejected,indent=2),encoding='utf-8')\n    return inserted,len(rows),len(rejected)\ndef main():\n    p=argparse.ArgumentParser(description=__doc__);p.add_argument('jsonl');p.add_argument('--db',default='chat.sqlite');a=p.parse_args()\n    Path('logs').mkdir(exist_ok=True);logging.basicConfig(filename='logs/chat.log',encoding='utf-8',level=logging.INFO)\n    inserted,total,rejected=ingest(a.jsonl,a.db);logging.info('inserted=%d total=%d rejected=%d',inserted,total,rejected);print(f'CHAT PASS: inserted={inserted} total={total} rejected={rejected}')\nif __name__=='__main__':main()\n",
      "filename": "collector.py",
      "generated_from": "BUILD_SPEC.json.resources",
      "version": "2.0"
    },
    {
      "resource_id": "R011",
      "name": "capture-notes.md",
      "type": "md",
      "purpose": "Working resource used by this Build; inspect before running.",
      "contents": "# Live capture handoff\nBroadcast URL:\nPermission / public access / platform terms checked:\nStart and stop UTC:\nCollection method: approved API/export, or manually save permitted visible messages.\nNormalize each message to {id,broadcast,user,text,at}. Retain the platform ID.\nDo not bypass login, CAPTCHA, access controls or rate limits. DOM selectors are version-specific.\nA live adapter must stop cleanly on 401/403/429 and must never call an empty/error response a success.\n",
      "filename": "capture-notes.md",
      "generated_from": "BUILD_SPEC.json.resources",
      "version": "2.0"
    }
  ],
  "bot_coach": {
    "bot_name": "Buzz",
    "bot_role": "Offline reviewed step coach",
    "personality": "patient and concrete",
    "tone": "plain",
    "teaching_style": "hint, diagnostic, action, checkpoint",
    "introduction": "Ask about the current step. I use reviewed local answers and queue unknown questions for review.",
    "encouragement_style": "Focus on observed results.",
    "troubleshooting_style": "Ask for the smallest reproducible evidence.",
    "limits": "This offline coach does not call an LLM or inspect your computer. Unknown questions enter a local exportable review queue, not a shared SQL FAQ."
  },
  "coach_interaction_model": {
    "stuck": "current diagnostic and first repair",
    "meaning": "current explanation",
    "error": "current troubleshooting",
    "why": "purpose",
    "skip": "require equivalent evidence",
    "alternative": "documented alternative",
    "finished": "record actual output",
    "start_over": "export then reset only this Build",
    "progressive_assistance": [
      "hint",
      "diagnostic",
      "walkthrough",
      "resource solution"
    ]
  },
  "website_specification": {
    "layout": "Lesson outline, actionable step, current-context coach",
    "features": [
      "platform selectors",
      "command copy with selection fallback",
      "evidence checklist",
      "practical quiz",
      "downloadable resource ZIP",
      "local progress",
      "evidence export",
      "unknown-question review queue"
    ],
    "checkpoint_model": "Learner supplies observations/output; tokens check expected shape only, not host truth.",
    "navigation": "Browse freely; checked next-button progression retains evidence.",
    "video": "spec-driven walkthrough and optional speech; full rendered media is a downstream production task."
  },
  "final_test": {
    "test_name": "Repeat the core workflow and restore its evidence",
    "instructions": "Repeat the last product-specific checkpoint from a fresh practice input, preserve actual output, then complete the Build Bot Better guard and restore checks. Export the evidence.",
    "required_inputs": [
      "workspace_guard.py",
      "restore_drill.py",
      "onboarding-checklist.md",
      "capture-config.json",
      "browser_capture.py",
      "collector.py",
      "capture-notes.md"
    ],
    "success_conditions": [
      "A working local chat collector that normalizes timezone-aware messages, deduplicates broadcast/message IDs in SQLite, retains rejects and exports spreadsheet-safe Unicode CSV.",
      "Core output can be reproduced",
      "Guard limit explained",
      "Backup and restoration evidence retained"
    ],
    "automated_tests": "catalog_check.py records browser behavior and executable resources, with explicit scope.",
    "manual_tests": [
      "Review actual account sign-in where needed",
      "Check source/data assumptions",
      "Restore one real non-secret memory file"
    ],
    "expected_output": "A working local chat collector that normalizes timezone-aware messages, deduplicates broadcast/message IDs in SQLite, retains rejects and exports spreadsheet-safe Unicode CSV.",
    "failure_paths": "Use the specific step diagnostic, retain failure evidence and stop when access is required."
  },
  "extensions": [
    {
      "title": "Use your own input",
      "action": "Replace the sample with one permitted real input and rerun the same checks."
    },
    {
      "title": "Add one failure case",
      "action": "Create a malformed or missing input; prove the workflow fails visibly and recovers."
    },
    {
      "title": "Automate after review",
      "action": "Write a bounded schedule with logging and a stop condition; obtain approval for external actions separately."
    }
  ],
  "metadata": {
    "SEO_title": "Live Broadcast Chat Scraper | BuildBotsBetter",
    "SEO_description": "A working local chat collector that normalizes timezone-aware messages, deduplicates broadcast/message IDs in SQLite, retains rejects and exports spreadsheet-safe Unicode CSV.",
    "SEO_keywords": [
      "x-broadcast-chat-scraper",
      "guided build",
      "verified outcome"
    ],
    "difficulty": "beginner",
    "related_builds": [
      "dogfooding",
      "buildbotsbetter-onboarding"
    ],
    "next_recommended_build": "dogfooding"
  },
  "automation_map": [
    {
      "output": "website",
      "generated_from": "BUILD_SPEC.json",
      "human_review_required": true
    },
    {
      "output": "walkthrough",
      "generated_from": "BUILD_SPEC.json",
      "human_review_required": true
    },
    {
      "output": "bot dialogue",
      "generated_from": "BUILD_SPEC.json",
      "human_review_required": true
    },
    {
      "output": "resources",
      "generated_from": "BUILD_SPEC.json",
      "human_review_required": true
    },
    {
      "output": "quiz",
      "generated_from": "BUILD_SPEC.json",
      "human_review_required": true
    },
    {
      "output": "captions",
      "generated_from": "BUILD_SPEC.json",
      "human_review_required": true
    },
    {
      "output": "metadata",
      "generated_from": "BUILD_SPEC.json",
      "human_review_required": true
    }
  ],
  "quality_assurance_report": {
    "scope": "Original instructional content, reviewed against shared brief; execution and browser receipts are stored under logs/catalog-acceptance.",
    "known_limits": [
      "The visible-browser adapter requires a permitted source and selectors verified for its current version; no universal X selector is claimed.",
      "A moving local fixture validates the adapter mechanics, not live X access."
    ]
  },
  "sources": [
    {
      "title": "Python SQLite transactions and parameter binding",
      "url": "https://docs.python.org/3/library/sqlite3.html"
    },
    {
      "title": "X developer platform access",
      "url": "https://docs.x.com/x-api/introduction"
    },
    {
      "title": "Playwright browser installation",
      "url": "https://playwright.dev/python/docs/browsers"
    }
  ],
  "known_uncertainties": [
    "The visible-browser adapter requires a permitted source and selectors verified for its current version; no universal X selector is claimed.",
    "A moving local fixture validates the adapter mechanics, not live X access."
  ]
}
