diff --git a/AGENTS.md b/AGENTS.md index 2f1122cb..2bcb1d07 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Tina4 Python — Agent Instructions -v3.13.147. 140 cataloged features, zero dependencies. Python 3.12+. +v3.13.148. 140 cataloged features, zero dependencies. Python 3.12+. ## AI Skills diff --git a/CHANGELOG.md b/CHANGELOG.md index 7058e7c2..e3515a42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ https://tina4.com/python/36-releases This file records framework-specific changes. The release notes above remain the authority for shipped versions. +## 3.13.148 — 2026-10-05 +### Sessions +- An anonymous request no longer stores a session or sets a session cookie when nothing was ever put in it. A request that only marked the session changed without leaving data in it (deleting a key it never had, `clear()`, `regenerate()` on an empty session) is treated the same — a record with no data is not a session. Requests that store a value, or that arrive with a cookie for an already-stored session, behave as before, so session storage no longer grows with anonymous traffic (static files, 404s, health checks). Fixed across all four frameworks. + ## 3.13.147 — 2026-10-05 ### Dev MCP tools (fixes tina4-php#271) - `database_columns` reads schema metadata, so an empty table returns its columns; a missing table returns a clear `table not found` error. diff --git a/CLAUDE.md b/CLAUDE.md index 37bc88c4..25a64d13 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,7 @@ The full discipline lives in `.claude/skills/tina4-maintainer/SKILL.md`; this bl # Tina4 Python -Version 3.13.147 - Lightweight Python web framework. See https://tina4.com for full documentation. +Version 3.13.148 - Lightweight Python web framework. See https://tina4.com for full documentation. ## Build & Test @@ -939,7 +939,7 @@ uv run tina4python test # Discovers @tests in src/**/*.py - SSE/Streaming via `response.stream()` — Server-Sent Events support for real-time data push. Pass an async generator; framework handles chunked transfer encoding, `text/event-stream` content type, and connection keep-alive - MCP server (`tina4_python.mcp`): built-in dev tools auto-start when MCP is a capability of the deployment. Developer API: `McpServer`, `@mcp_tool`, `@mcp_resource`. JSON-RPC 2.0 over SSE. **Security is a two-layer gate (v3.13.40):** `is_enabled()` is a pure capability gate (explicit `TINA4_MCP` wins, else `TINA4_DEBUG`; host-independent), and `is_request_allowed(remote_ip, has_valid_token)` authorises each request on the RAW socket peer (`request.remote_ip`, never X-Forwarded-For): loopback always; a remote caller needs `TINA4_MCP_REMOTE=true` AND a token matching `TINA4_MCP_TOKEN` (never `TINA4_API_KEY`, ADR-0078; sent as Authorization Bearer / X-MCP-Token; no configured token means remote is always denied). Every `/__dev` request (reads too), the MCP endpoints and the `/__dev_reload` socket also require a loopback Host (`localhost`, `127.0.0.1`, `[::1]` or `TINA4_HOST`) and refuse `Sec-Fetch-Site: same-site`/`cross-site` (ADR-0078). `/health` omits `version` outside debug. All MCP surfaces (REST shim, JSON-RPC, SSE) 404 a disallowed caller. `database_query` is SELECT/WITH-only and rejects stacked statements; the file tools are sandboxed to the project root. `is_localhost()` is informational only, not the gate - Tests: 6,088 passing, 0 failures, **0 skipped** — measured 2026-09-17 on the lab (Ubuntu 24.04.4 LTS x86_64, Python 3.13.13, live services, `TINA4_REQUIRE_SERVICES=1`, 604s, run as root). **Firebird is NOT excluded.** The lab provisions a live Firebird 5 (`firebirdsql/firebird:5` on :3050, `TINA4_TEST_FIREBIRD_URL`) and those tests run. What IS deliberate is Firebird's absence from the `TINA4_REQUIRE_SERVICES` keyword gate in `tests/conftest.py`: GitHub CI does not provision Firebird, so a Firebird skip has to stay green *there*. Those are two different things and this line used to conflate them into "excluded by design", which read as "the Firebird tests do not run" — they do. **Nothing skips any more.** Reaching 0 skips now also needs the graph engines (Neo4j, Memgraph, ArangoDB, Ultipa, wired via `TINA4_TEST_NEO4J_URL`/`_MEMGRAPH_URL`/`_ARANGO_URL`/`_ULTIPA_URL` + the `graph` extra) and the lab Keycloak OIDC realm (`TINA4_REQUIRE_OIDC=1`), both added after the August baseline; without them `test_graph.py` (Feature 139) and the real OIDC contract test skip. The last skip was `tests/test_session_backend_failure.py` `[needs:no-dac-override]`: the suite runs as root, root holds `CAP_DAC_OVERRIDE`, so a write went straight through a 0400 file and no real `EACCES` was reachable — and as root the second `save()` returned `True`, so that skip was hiding a FAILING assertion, not merely an unrunnable one. The test now stops being root for the length of the failing write (`os.seteuid` to `nobody`; the saved uid stays 0, so a `finally` always restores it) and the kernel raises the genuine errno-13 the test asserts. Two parts of that are load-bearing: the log directory is handed to the same uid, because dropping the uid otherwise denies `Log.error` as well and the EACCES under test goes unrecorded (`_LogWriter.write` swallows `OSError` unless `TINA4_LOG_STRICT`); and the fixture root is its own 0755 `mkdtemp` rather than `tmp_path`, whose 0700 root-owned parents make every denial a directory traversal, which would pass for the wrong reason. Re-measure with `.venv/bin/python -m pytest tests/ -q` and quote the summary line, never the exit code -- Version: 3.13.147 +- Version: 3.13.148 ## Links diff --git a/pyproject.toml b/pyproject.toml index 3bafe3e3..2df34ce9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "tina4-python" -version = "3.13.147" +version = "3.13.148" description = "Tina4 Python v3 — Zero-dependency, lightweight web framework" authors = [ {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"} diff --git a/tina4_python/__init__.py b/tina4_python/__init__.py index 972875d4..92e1e7ef 100644 --- a/tina4_python/__init__.py +++ b/tina4_python/__init__.py @@ -57,7 +57,7 @@ def _resolve_version() -> str: # # test_version_constant.py now asserts this literal equals the pyproject # version, so the release bump cannot leave it behind again. - return "3.13.147" + return "3.13.148" __version__ = _resolve_version() diff --git a/uv.lock b/uv.lock index 53e1c730..b8b10de9 100644 --- a/uv.lock +++ b/uv.lock @@ -1648,7 +1648,7 @@ wheels = [ [[package]] name = "tina4-python" -version = "3.13.147" +version = "3.13.148" source = { editable = "." } [package.optional-dependencies]