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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 = "[email protected]"}
Expand Down
2 changes: 1 addition & 1 deletion tina4_python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading