Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,38 @@ npm run build
4. Implement the feature
5. Ensure all tests pass: `pytest tests/ -v` / `npm test`
6. Commit with conventional commits: `feat:`, `fix:`, `docs:`
7. Open a Pull Request against `main`
7. **Validate locally**: `bash scripts/pr-validate.sh` (see below)
8. Open a Pull Request against `main`

## Pre-PR validation

Run every PR-blocking CI check locally before opening the PR:

```bash
bash scripts/pr-validate.sh # default: ~3-5 min
bash scripts/pr-validate.sh --quick # pre-commit + notebooks (~30s)
bash scripts/pr-validate.sh --full # default + e2e + all-extras (~10 min)
```

The script mirrors `.github/workflows/test.yml` and `notebooks.yml` so a
green local run lines up with green CI. Selective skips: `--skip-py`,
`--skip-ts`, `--skip-notebooks`, `--no-stop`.

First-time setup:

```bash
pip install pre-commit==3.8.0 # required
brew install gitleaks # optional — system fallback
```

If pre-commit's bundled tools don't work on your machine (hardened macOS,
Go OOM during gitleaks build), use the documented escape hatches:

```bash
PR_VALIDATE_SKIP_GITLEAKS=1 PR_VALIDATE_SKIP_NBSTRIPOUT=1 bash scripts/pr-validate.sh
```

CI always runs the full unaltered suite, so these only affect local runs.

## Code Style

Expand Down
125 changes: 125 additions & 0 deletions docs/DEVLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,131 @@ Newest entries at the top.

---

### [2026-04-28] — Notebook Docker bootstrap: code-review fixes

**Type:** fix
**Branch:** feat/notebook-docker-launcher

**What it does:**

Addresses every CRITICAL/HIGH/MEDIUM finding from the multi-agent review of the
initial Docker launcher commit:

- **Loopback-only port maps** in `docker-compose.yml` (`127.0.0.1:8888:8888` /
`127.0.0.1:8765:8765`) so JupyterLab is never exposed to the LAN.
- **Generated auth token by default.** `_setup._generate_jupyter_token()` writes
a 32-byte URL-safe secret to `~/.config/patter-notebooks/jupyter_token` (mode
`0o600`) on first run; subsequent runs reuse it. `start_docker()` injects it
via `JUPYTER_TOKEN` env var; the Dockerfile `CMD` reads it from env. Empty
token requires explicit opt-in via `PATTER_NOTEBOOKS_NO_TOKEN=1`.
- **Non-root container.** Dockerfile creates `patter` (UID 1000 by default,
override via `PUID`/`PGID` build args) and drops `--allow-root`.
- **Pinned deps.** New `examples/notebooks/python/requirements.txt` locks every
top-level dep at an exact version; Dockerfile installs from it.
- **`start_docker() -> bool`.** Every early-return now returns False so
notebook callers can branch; `subprocess.run(..., check=False,
capture_output=True)` surfaces stdout/stderr on compose failure instead of
swallowing them.
- **`detach=False` guard.** Refuses to launch (would hang the kernel) and
returns False with a banner pointing at the terminal.
- **Robust env-var truthiness.** `in_docker()` accepts `1/true/yes/on`
(case-insensitive), not just literal `"1"`.
- **Honest docstring.** `_setup.py` module docstring acknowledges the
TS-parity gap on the Docker helpers (tracked separately).
- **Unit tests.** `tests/test_docker_bootstrap.py` covers truthy/falsy env
parsing, `/.dockerenv` marker, every `start_docker()` early-return branch,
command argv assembly, and token persistence (19 tests, all green).

**Implementation details:**

- Generated token persists across `compose down`/`up` cycles so users don't
need to re-bookmark the URL after a restart.
- Top-level imports hoisted: `secrets` and `webbrowser` now at the top of
`_setup.py` (PEP 8). All other late imports stayed because they live with
domain-specific helpers further down the file.
- Tests mock at the subprocess + filesystem boundary only; real Path
resolution and real argv assembly run unchanged.

**Files changed:**

| File | Change |
|------|--------|
| `examples/notebooks/python/Dockerfile` | Non-root user, requirements.txt, JUPYTER_TOKEN from env |
| `examples/notebooks/python/docker-compose.yml` | Loopback ports, JUPYTER_TOKEN passthrough, PUID/PGID args |
| `examples/notebooks/python/requirements.txt` | New — pinned top-level deps |
| `examples/notebooks/python/_setup.py` | bool return, captured stderr, token gen, robust truthiness, detach guard |
| `examples/notebooks/python/tests/test_docker_bootstrap.py` | New — 19 unit tests |

**Tests added:**

- `examples/notebooks/python/tests/test_docker_bootstrap.py` — 19 cases.

**Breaking changes:** None — the launcher is still an opt-in commented cell.

**Docs to update:**

- [ ] Tracking issue for TypeScript notebook Docker launcher (filed separately).

---

### [2026-04-28] — Notebook Docker bootstrap: optional in-cell launcher

**Type:** feat
**Branch:** feat/notebook-series-skeleton

**What it does:**

Lets users run the Python notebook series inside a containerised JupyterLab
without leaving the notebook. Two new scoped files (`Dockerfile`,
`docker-compose.yml`) under `examples/notebooks/python/` build a Python 3.13
image with `getpatter` (pinned to `PATTER_VERSION`, default 0.5.4), the helper
deps from `pyproject.toml`, and JupyterLab. Compose mounts the parent
`examples/notebooks/` tree at `/notebooks` so `_setup.py` still finds `.env`
and `fixtures/`; ports 8888 (Lab) and 8765 (EmbeddedServer for T2/T4 cells)
are published. `env_file` is marked `required: false` so §1 cells run with
zero keys.

`_setup.py` gains two helpers: `in_docker()` (checks
`PATTER_NOTEBOOKS_IN_DOCKER=1` and `/.dockerenv`) and `start_docker(*, build,
detach, open_url)` which shells out to `docker compose up -d --build` from the
notebooks dir, no-ops when already inside the container or when the `docker`
CLI is absent. Each of the 12 Python notebooks gets an optional markdown +
commented code cell at the top — Run All on a fresh checkout still behaves
identically because the launcher is commented by default.

**Implementation details:**

- Container detection prefers an explicit env var over `/.dockerenv` so future
rootless/podman-equivalent setups can opt in by exporting the same flag.
- Insertion script is idempotent (skips notebooks that already contain the
`## Optional: run in Docker` marker), safe to re-run after future notebook
edits.
- Compose uses the v2 `env_file: [{path, required: false}]` form to avoid
failing when users haven't created `.env` yet.

**Files changed:**

| File | Change |
|------|--------|
| `examples/notebooks/python/Dockerfile` | New — Python 3.13-slim + getpatter + JupyterLab + helpers |
| `examples/notebooks/python/docker-compose.yml` | New — builds image, mounts `../`, optional `.env`, ports 8888/8765 |
| `examples/notebooks/python/_setup.py` | Added `in_docker()` and `start_docker()`; exposed `PYTHON_NOTEBOOKS_DIR` |
| `examples/notebooks/python/01_quickstart.ipynb` … `12_security.ipynb` | Inserted optional Docker markdown + code cell at the top of every notebook |

**Tests added:** None — bootstrap is a no-op until uncommented, and existing
notebook tests cover the helper module via import. Manual smoke: `docker
compose config` validates and `python -c "import _setup; _setup.in_docker()"`
returns False on host.

**Breaking changes:** None.

**Docs to update:**

- [ ] `examples/notebooks/README.md` — add a "Run in Docker" section pointing
at the optional cell + compose file.

---

### [2026-04-27] — Notebook series Phase 5: Polish — README, launcher, drift-cron

**Type:** docs / chore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6321,8 +6321,3 @@ Two execution options:
Recommended: **Inline for Phase 1 (Tasks 1–22)**, then **subagent-driven for Phases 2–5** once the foundation is green and tasks are independent.

Which approach?





Loading
Loading