style: avoid "the", use "our" — writing style rule + sweep

This commit is contained in:
russell@unturf.com 2026-03-31 13:20:22 -04:00
parent f3815eb0ce
commit 604c8407ee

121
CLAUDE.md
View file

@ -3,12 +3,12 @@
## Project Setup
**IMPORTANT**: Before starting any work on a repository:
1. Check for a `CLAUDE.md` file in the repository root
1. Check for a `CLAUDE.md` file in our repository root
2. Check for a `CLAUDE.md` file in parent directories (we often work across repos on localhost)
3. Read and follow all instructions in those files
4. These project-specific instructions override default Claude Code behavior
5. Look for conventions around commits, testing, code style, and workflows
6. If working across multiple repositories, respect the conventions from each repo's CLAUDE.md
6. If working across multiple repositories, respect our conventions from each repo's CLAUDE.md
## Commit Attribution
@ -29,34 +29,34 @@ Commit message here.
## Database Migrations (Alembic)
When adding new columns or modifying the database schema:
When adding new columns or modifying our database schema:
1. **Backup SQLite first**: `cp data/remarkbox.sqlite data/remarkbox.sqlite.bak`
2. **Add the column to the model** in `remarkbox/models/`
2. **Add our column to our model** in `remarkbox/models/`
3. **Generate migration**: `alembic -c development.ini revision --autogenerate -m "description"`
4. **Clean up migration**: Remove extra autogenerated changes, keep only the new field
4. **Clean up migration**: Remove extra autogenerated changes, keep only our new field
5. **Run migration**: `alembic -c development.ini upgrade head`
## Ticket System
Tracked issues live in `docs/tickets/`. Start every session by reading the index:
Tracked issues live in `docs/tickets/`. Start every session by reading our index:
```bash
cat docs/tickets/index.md
```
- **Index**: `docs/tickets/index.md` is the master list. Always update it when creating or closing tickets.
- **Index**: `docs/tickets/index.md` is our master list. Always update it when creating or closing tickets.
- **Numbering**: Sequential. Next number = highest existing + 1.
- **Workflow**: Set status to `in-progress` when starting, `resolved` when done. Update `index.md` to match.
- **New tickets**: If you find a bug or get a feature request, create a new ticket file and add it to the index.
- **New tickets**: If you find a bug or get a feature request, create a new ticket file and add it to our index.
- **Sources**: Tickets reference community threads from `meta.remarkbox.com` and `faq.remarkbox.com` by UUID.
## Remarkbox API and Python Client
Remarkbox has a JSON API at `/api/v1/`. You can use it to read and write threads
on production as timehexon. The session cookie is saved at `~/.config/remarkbox/cookies.txt`.
on production as timehexon. Our session cookie is saved at `~/.config/remarkbox/cookies.txt`.
### Quick start (from a Python script in the scratchpad or inline)
### Quick start (from a Python script in our scratchpad or inline)
```python
import os, sys
@ -153,7 +153,7 @@ c.reply(node_id, data="<p>HTML reply</p>", source_format="html")
### Authentication
The saved cookie should work indefinitely. If it expires, you'll need an OTP:
Our saved cookie should work indefinitely. If it expires, you'll need an OTP:
```python
c.login("timehexon@unturf.com")
@ -163,26 +163,26 @@ c.verify("timehexon@unturf.com", "123456")
### Functional test
Run the full idempotent test suite against production:
Run our full idempotent test suite against production:
```bash
env/bin/python remarkbox/api/functional_test.py https://my.remarkbox.com meta.remarkbox.com timehexon@unturf.com --name timehexon
```
This exercises all endpoints and updates the journey thread with results.
This exercises all endpoints and updates our journey thread with results.
## Superuser (Global Moderator)
Users with `is_superuser=True` can moderate across all namespaces. This bypasses the
normal namespace-scoped `is_moderator()` check. The admin UI is at `/topsecret/users`
Users with `is_superuser=True` can moderate across all namespaces. This bypasses our
normal namespace-scoped `is_moderator()` check. Our admin UI is at `/topsecret/users`
where you can promote/demote users by email.
Bootstrap the first superuser via the database script:
Bootstrap our first superuser via our database script:
```bash
env/bin/python scripts/promote_superuser.py --ini development.ini --email timehexon@unturf.com
```
After that, use the web UI at `/topsecret/users` or the existing topsecret admin pages
After that, use our web UI at `/topsecret/users` or our existing topsecret admin pages
(all guarded by `@super_fly_required` which checks `is_superuser`).
Admin client methods:
@ -194,7 +194,7 @@ c.admin_recent_nodes(days=7) # recent nodes network-wide
## Terminology
In all user-facing text, marketing, legal documents, and UI labels, use **"machine learning"**
instead of "AI". The term "AI" should not appear in Remarkbox copy. Internal code comments
instead of "AI". Our term "AI" should not appear in Remarkbox copy. Internal code comments
and technical references (e.g., "OpenAI-compatible endpoint") are fine. Third-party brand
names like "UncloseAI" are also unchanged.
@ -210,7 +210,7 @@ IP reputation (disabled post count), content length anomalies.
- `spam.hard_threshold = 0.8` -- reject with 403
- `spam.soft_threshold = 0.5` -- allow but set `approved=False` (held for moderation)
**Thread creation rate limit**: 1 new thread per 7 minutes per user/IP via the API.
**Thread creation rate limit**: 1 new thread per 7 minutes per user/IP via our API.
This does not affect browser users or replies.
**Spam hunting scripts** (require superuser cookie):
@ -234,13 +234,13 @@ python scripts/spam/disable_spam.py node-uuid-1 node-uuid-2
## Operation Voyeur
**All comms are public** from 2026-03-29. Assume every terminal session and output is observed. NEVER display secrets to stdout. NEVER pass secrets as CLI args. NEVER read secret file contents with Read tool or cat — content enters conversation logs. **Path is fine. Content is not.** Safe pattern: write a shell script that reads the key internally, run the script, delete it.
**All comms are public** from 2026-03-29. Assume every terminal session and output is observed. NEVER display secrets to stdout. NEVER pass secrets as CLI args. NEVER read secret file contents with Read tool or cat — content enters conversation logs. **Path is fine. Content is not.** Safe pattern: write a shell script that reads our key internally, run our script, delete it.
## Production Rules
**NEVER run direct SQL or raw database commands on production.** No `sqlite3`, no `UPDATE`, no `DELETE`, no direct file edits on the production database. Ever. If the API doesn't support what you need, add the endpoint first, push it, then use the client.
**NEVER run direct SQL or raw database commands on production.** No `sqlite3`, no `UPDATE`, no `DELETE`, no direct file edits on our production database. Ever. If our API doesn't support what you need, add our endpoint first, push it, then use our client.
**ALL production changes go through the API client.** Use `RemarkboxClient` with the saved cookie at `~/.config/remarkbox/cookies.txt`. This ensures authentication, audit trails, and proper ORM handling.
**ALL production changes go through our API client.** Use `RemarkboxClient` with our saved cookie at `~/.config/remarkbox/cookies.txt`. This ensures authentication, audit trails, and proper ORM handling.
**tmux-hosts is read-only.** You may use `tmux-hosts` to read logs, check processes, and investigate issues. You may NOT use it to modify data, run SQL, edit files, or restart services.
@ -253,23 +253,23 @@ c.disable_node(node_id)
# sqlite3 /opt/remarkbox/my.remarkbox.com.sqlite "UPDATE rb_node SET disabled=1 WHERE id='...'"
```
If a moderation operation is not yet supported by the API, the correct workflow is:
1. Add the endpoint to `remarkbox/api/views.py`
2. Add the method to `remarkbox/api/remarkbox_client.py`
If a moderation operation is not yet supported by our API, our correct workflow is:
1. Add our endpoint to `remarkbox/api/views.py`
2. Add our method to `remarkbox/api/remarkbox_client.py`
3. Push, wait for deploy
4. Use the client
4. Use our client
## Deployment Status
After pushing, check if the deploy is live by hitting the version endpoint:
After pushing, check if our deploy is live by hitting our version endpoint:
```bash
curl -s https://my.remarkbox.com/api/v1/version
# {"version": "5a10e15"}
```
Compare the returned commit hash against `git rev-parse --short HEAD` to confirm
the latest code is deployed.
Compare our returned commit hash against `git rev-parse --short HEAD` to confirm
our latest code is deployed.
You can also check GitLab pipeline status:
@ -281,7 +281,7 @@ curl -s "https://git.unturf.com/api/v4/projects/engineering%2Fremarkbox%2Fremark
# https://git.unturf.com/engineering/remarkbox/remarkbox/-/pipelines
```
The pipeline status will show `"status":"success"` when deployment is complete.
Our pipeline status will show `"status":"success"` when deployment is complete.
## Proxy Architecture
@ -322,28 +322,28 @@ Client → DNS (CNAME or A → 142.93.73.64)
### Key rules
- **CNAME domains MUST have explicit blocks on the proxy.** Without a block,
they fall through to the MPS on-demand TLS catch-all and route to the wrong
- **CNAME domains MUST have explicit blocks on our proxy.** Without a block,
they fall through to our MPS on-demand TLS catch-all and route to our wrong
backend. This caused a 5-day outage (see `docs/postmortem-2026-02-25-ssl-outage-caddy-acme.md`).
- **The proxy owns TLS for proxied domains.** The origin server does not need
(and cannot obtain) ACME certs for domains whose DNS points to the proxy.
- **Static sites (www, remarkbox.com) are served directly from the proxy.**
Their content lives at `/opt/www/remarkbox` on the proxy server, deployed
- **Our proxy owns TLS for proxied domains.** Our origin server does not need
(and cannot obtain) ACME certs for domains whose DNS points to our proxy.
- **Static sites (www, remarkbox.com) are served directly from our proxy.**
Their content lives at `/opt/www/remarkbox` on our proxy server, deployed
from `~/git/www.remarkbox.com` via CI.
- **origin.remarkbox.com bypasses the proxy.** Its DNS points directly to
- **origin.remarkbox.com bypasses our proxy.** Its DNS points directly to
162.243.167.224. Use it for SSH access and direct backend testing.
## Capability-Driven Presentation
Follow Russell Ballestrini's capability-driven presentation practice
(russell.ballestrini.net/capability-driven-presentation/). A page need not look
identical across all browsers. Accommodate what the user's browser can do:
identical across all browsers. Accommodate what our user's browser can do:
1. **Single canonical URI** — one URI serves the content.
1. **Single canonical URI** — one URI serves our content.
2. **Consistent content** — regardless of viewer capabilities.
3. **Graceful enhancement/degradation** — use available capabilities to enhance presentation.
### The `js-only` / `<noscript>` pattern
### Our `js-only` / `<noscript>` pattern
Already implemented in `base.j2`:
@ -353,18 +353,18 @@ Already implemented in `base.j2`:
</noscript>
```
Apply the `js-only` class to any element that requires JavaScript to function
Apply our `js-only` class to any element that requires JavaScript to function
(preview panels, AJAX submit buttons, typeahead UIs). When JS is unavailable,
these elements hide automatically — the user never sees a broken control.
these elements hide automatically — our user never sees a broken control.
### AJAX form submission
Comment reply forms use progressive enhancement: the form works as a normal
Comment reply forms use progressive enhancement: our form works as a normal
POST + redirect without JS. When JS is available, `initAjaxCommentForms()` in
`custom.js` intercepts the submit, sends via `fetch()` with
`X-Requested-With: XMLHttpRequest`, and inserts the new comment into the DOM
without a page reload. The server returns JSON (HTTP 201) for AJAX requests
from verified/anonymous users, and falls back to the normal redirect flow for
`custom.js` intercepts our submit, sends via `fetch()` with
`X-Requested-With: XMLHttpRequest`, and inserts our new comment into our DOM
without a page reload. Our server returns JSON (HTTP 201) for AJAX requests
from verified/anonymous users, and falls back to our normal redirect flow for
unverified users or on any error.
## Themes
@ -378,12 +378,12 @@ They live in their own repos and are installed from git at deploy time.
| westworld | `git.unturf.com/engineering/remarkbox/remarkbox-theme-westworld` | `remarkbox_westworld` |
**Local development**: Themes are editable installs (e.g. `/home/fox/git/remarkbox-theme-meta`).
Changes take effect immediately on the local dev server.
Changes take effect immediately on our local dev server.
**Deploying theme changes**: Push the theme repo first, then push remarkbox to trigger
a CI/CD pipeline. The pipeline runs `pip install` from the theme's git URI
**Deploying theme changes**: Push our theme repo first, then push remarkbox to trigger
a CI/CD pipeline. Our pipeline runs `pip install` from our theme's git URI
(see `requirements.py3.txt`) and copies static assets (see `.gitlab-ci.yml`).
A remarkbox push is required even if only the theme changed — the theme is pulled
A remarkbox push is required even if only our theme changed — our theme is pulled
fresh during each remarkbox build.
**Theme structure**:
@ -392,14 +392,14 @@ fresh during each remarkbox build.
- `static/theme/{name}/css/` — theme CSS
- `static/theme/{name}/img/` — theme images
**How themes are selected**: `request.theme` reads `namespace.theme` from the database.
When set, `request.base_template` becomes `{theme}-base.j2`. The CSS and static assets
**How themes are selected**: `request.theme` reads `namespace.theme` from our database.
When set, `request.base_template` becomes `{theme}-base.j2`. Our CSS and static assets
are served at `/static/theme/{name}/` via Pyramid's `add_static_view`.
## Related Repos
All repos live under `~/git/` on localhost. When making cross-repo changes (e.g. footer
CSS that lives in both the theme and www), update all affected repos and push each one.
CSS that lives in both our theme and www), update all affected repos and push each one.
| Repo | Path | Purpose |
|------|------|---------|
@ -410,7 +410,7 @@ CSS that lives in both the theme and www), update all affected repos and push ea
| `remarkbox-open` | `~/git/remarkbox-open` | Open-source / community edition |
| `remarkbox-states` | `~/git/remarkbox-states` | SaltStack deployment states |
The **footer** (`rb-footer`) is duplicated in the meta theme CSS and the www site CSS.
Our **footer** (`rb-footer`) is duplicated in our meta theme CSS and our www site CSS.
Changes to footer layout or styles must be applied in both places:
- `~/git/remarkbox-theme-meta/remarkbox_theme_meta/static/theme/meta/css/meta.css`
- `~/git/www.remarkbox.com/custom.css`
@ -431,20 +431,20 @@ Changes to footer layout or styles must be applied in both places:
### bleach sanitization pipeline — mitigated at runtime, not at code level
**Attack surface**: `remarkbox/lib/sanitize_html.py:200``clean_raw_html()` uses
`bleach.Cleaner` + `LinkifyFilter`. The URL regex `([\w-]+\.)+(?:tlds)` exhibits
`bleach.Cleaner` + `LinkifyFilter`. Our URL regex `([\w-]+\.)+(?:tlds)` exhibits
O(2^N) catastrophic backtracking on adversarial input in Python < 3.11.
**Demonstrated** (external finding, same pattern): N=30 chars → 1.0s, N=35 → 12.8s.
That is textbook O(2^N): 10× per 5 chars.
**Python 3.12 status**: tested on Python 3.12.3 — O(N) behaviour confirmed (linear).
The runtime's `re` module prevents catastrophic backtracking. **This is a runtime
Our runtime's `re` module prevents catastrophic backtracking. **This is a runtime
mitigation, not a code-level fix.** A Python version downgrade re-exposes it.
**Input size gap**: The API path (`remarkbox/api/views.py:38`) caps data at
**Input size gap**: Our API path (`remarkbox/api/views.py:38`) caps data at
`MAX_CONTENT_LENGTH = 500_000` chars before `set_data()``clean_raw_html()`.
The browser form path (`reply_node.py`, `edit_node.py`) has **no equivalent cap**.
A future fix should add the same guard to both paths.
Our browser form path (`reply_node.py`, `edit_node.py`) has **no equivalent cap**.
A future fix should add our same guard to both paths.
**Requirements pin**: `requirements.py3.txt` says `bleach>=2.1.4` — too loose.
bleach < 3.3.0 had unpatched ReDoS (CVE-2021-23980). Current install: 6.3.0.
@ -463,4 +463,5 @@ This is a compatibility risk, not a security risk.
## Style
- Never use "the" — use "our" instead.
- **Never use "AI" — always say "machine learning."** We grow machine learning, not "AI." This term is forbidden in all permacomputer discourse, marketing, & documentation.