docs(backup): document seed-vs-runtime ignore architecture + add logs/ default

Confirmed (via @backup) the two-layer ignore model and wrote it down so it stops
getting re-discovered:
- BUILTIN_IGNORES (patterns.py) = the SEED that generates a new project's
  .backupignore at register; never consulted at backup time.
- .backupignore (via load_spec) = the runtime source of truth. No static
  fallback exists, so the seed is safety-critical — an empty .backupignore backs
  up everything (.venv, node_modules, .git) and can crash the machine.

Added a 'How Ignores Work' README section + code comments on BUILTIN_IGNORES and
load_spec. Added logs/ to the seed so new projects exclude log dirs (prax .jsonl
output) by default, not just *.log files, with a test. seedgo @backup 100%.

td-27.
This commit is contained in:
AIOSAI
2026-06-24 09:21:34 -07:00
parent 451c8a0ee9
commit 70cf31eb3e
4 changed files with 28 additions and 7 deletions
+10
View File
@@ -64,6 +64,16 @@ PyPI version — not the changelog header.
a step to audit every open todo against the actual system (`ls`/`find`/`git
ls-files`/`grep`/`audit`) and close what's verifiably done — catching todos
finished in a past session but never closed.
- **Backup ignore architecture documented** — confirmed and written down the
two-layer model so it stops getting re-discovered: `BUILTIN_IGNORES` is the
**seed** that generates a new project's `.backupignore` at register and is
never consulted at backup time; `.backupignore` (via `load_spec`) is the
**runtime source of truth**. There's no static fallback, so the seed is
safety-critical — an empty `.backupignore` backs up everything and can crash
the machine. Added a "How Ignores Work" README section + code comments on
`BUILTIN_IGNORES` and `load_spec`. Also added `logs/` to the seed so new
projects exclude log directories (e.g. prax `.jsonl` output) by default, not
just `*.log` files (td-27).
## [2026-06-23]
+9 -7
View File
@@ -105,15 +105,17 @@ The root `.gitignore` covers all three with a single `.backup/` entry.
---
## `.backupignore`
## How Ignores Work
A true `.gitignore` for backups, using real pathspec/gitwildmatch semantics:
- `#` comments, blank lines ignored
- `!` negation (un-ignore a path)
- Trailing `/` for directory-only matching
- Last-match-wins ordering
Two layers — seed and runtime:
Lives at the **project root** and is the single source of truth governing snapshot, versioned, Drive sync, and mirror-cleanup operations. Generated from `BUILTIN_IGNORES` in `handlers/ignore/patterns.py` during `register`. The `.backup/` directory is included in `BUILTIN_IGNORES`, so the store self-excludes from its own backups.
1. **`BUILTIN_IGNORES`** (`handlers/ignore/patterns.py`) — the **seed**. Written into a new project's `.backupignore` at `register` time (`setup._build_backupignore()`). Never consulted at backup time.
2. **`.backupignore`** — the **runtime source of truth**. `load_spec()` reads it on every backup; `BUILTIN_IGNORES` is not applied. True pathspec/gitwildmatch semantics: `#` comments, `!` negation, trailing `/` for dirs, last-match-wins.
There is no static fallback. The seed IS the safety mechanism — an empty or missing `.backupignore` means back up everything (`.venv`, `node_modules`, `.git`), which can crash the machine. Keep `BUILTIN_IGNORES` sane.
- To change defaults for **new** projects → edit `BUILTIN_IGNORES` in `patterns.py`
- To change ignores for an **existing** project → edit its `.backupignore`
The repo-root `/.backupignore` ships intentionally as the curated default so users don't snapshot junk.
@@ -17,6 +17,9 @@ import pathspec
from ..json import json_handler
from ..path import builder
# Seed for new-project .backupignore (written at register by setup._build_backupignore).
# NOT applied at backup time — runtime ignores come from .backupignore via load_spec().
# Keep sane: empty seed = back up everything = crash.
BUILTIN_IGNORES = [
".backup/",
".git/",
@@ -40,6 +43,7 @@ BUILTIN_IGNORES = [
"build/",
"dist/",
"*.log",
"logs/",
".ruff_cache/",
".coverage",
]
@@ -48,6 +52,7 @@ BUILTIN_IGNORES = [
def load_spec(project_root: str) -> pathspec.PathSpec:
"""Load a PathSpec from .backupignore at the project root.
This is the runtime source of truth — BUILTIN_IGNORES is not consulted here.
Reads raw lines — pathspec handles #comments, blanks, !negation,
anchoring, dir-only trailing /, and last-match-wins natively.
@@ -250,6 +250,10 @@ class TestSeedTemplate:
"""Seed defaults include .coverage."""
assert ".coverage" in BUILTIN_IGNORES
def test_builtin_has_logs_dir(self):
"""Seed defaults exclude logs/ directories."""
assert "logs/" in BUILTIN_IGNORES
def test_build_backupignore_content(self):
"""Seed template includes ruff_cache, coverage, and pycache."""
from aipass.backup.apps.handlers.project.setup import _build_backupignore