From 6c0d56f17d4c77fff8ef02f5cc144ada0d06009f Mon Sep 17 00:00:00 2001 From: Andre van Zuydam Date: Wed, 30 Sep 2026 17:27:00 +0200 Subject: [PATCH] chore(release): stage 3.13.142 Signed-off-by: Andre van Zuydam Co-Authored-By: Claude Opus 4.8 Co-Authored-By: Tina4 <82961293+tina4stack@users.noreply.github.com> --- AGENTS.md | 2 +- CHANGELOG.md | 4 ++++ CLAUDE.md | 4 ++-- pyproject.toml | 2 +- tina4_python/__init__.py | 2 +- uv.lock | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a0bceced..e63c7521 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Tina4 Python — Agent Instructions -v3.13.141. 140 cataloged features, zero dependencies. Python 3.12+. +v3.13.142. 140 cataloged features, zero dependencies. Python 3.12+. ## AI Skills diff --git a/CHANGELOG.md b/CHANGELOG.md index b1f5c464..c0232de5 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.142 — 2026-09-30 + +A session that regenerates its id mid-request now re-emits the session cookie, so the browser is handed the new id instead of quietly keeping the old one -- the behaviour that defeats session fixation after a login or a privilege change, brought into parity with the other three frameworks (#253, #184). The rest of the release is internal cleanup that changes no behaviour: the last functions scoring a cyclomatic complexity of 40 or higher are gone, decomposed into named helpers across the ASGI app, the ORM save and create_table paths, the AI message translator, Frond resolution and the SQL statement splitter (#182), and duplicated logic is now shared once -- the LIMIT/OFFSET clause on the base adapter, the has_many paging loop, WebSocket room membership, rate-limit enforcement, and the queue dead-letter and retry path (#183). The canonical test env var TINA4_TEST_NATS_URL joins the shared set (ADR-0038, #186). A new `tina4 metrics --fail-on-regression` gate ratchets code quality in continuous integration: it compares against a committed baseline and fails the build when a file gets more complex or less maintainable (ADR-0002, #187). The framework still has no required runtime dependencies. + ## 3.13.141 — 2026-09-29 A committed symbolic link now fails the build. Git records a symlink with mode 120000, and Windows extraction -- 7-Zip, and so Composer on Windows -- refuses those "dangerous link paths", so a single leaked link breaks every Windows install. A new guard walks the git index, rejects any such file and names it, and a continuous-integration step runs it on every push and pull request. Its test is mutation-proof: it stages a real symlink in a real temporary repository and proves the guard bites. This is a small hardening release, parity with the tina4-php fix for the same Windows-Composer breakage, where a leaked container conf.d snapshot shipped 134 absolute symlinks. tina4-python carries none today; the guard keeps it that way. The framework still has no required runtime dependencies. diff --git a/CLAUDE.md b/CLAUDE.md index b4295b0e..277009c4 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.141 - Lightweight Python web framework. See https://tina4.com for full documentation. +Version 3.13.142 - 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.141 +- Version: 3.13.142 ## Links diff --git a/pyproject.toml b/pyproject.toml index 15dcade2..144b4bd6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "tina4-python" -version = "3.13.141" +version = "3.13.142" 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 aa23aa5c..a1782eea 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.141" + return "3.13.142" __version__ = _resolve_version() diff --git a/uv.lock b/uv.lock index 768f3263..8ff6e344 100644 --- a/uv.lock +++ b/uv.lock @@ -1648,7 +1648,7 @@ wheels = [ [[package]] name = "tina4-python" -version = "3.13.141" +version = "3.13.142" source = { editable = "." } [package.optional-dependencies]