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.153. 140 cataloged features, zero dependencies. Python 3.12+.
v3.13.154. 140 cataloged features, zero dependencies. Python 3.12+.

## AI Skills

Expand Down
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,34 @@ https://tina4.com/python/36-releases
This file records framework-specific changes. The release notes above remain the
authority for shipped versions.

## 3.13.154 - 2026-10-23

### BREAKING
- Realtime (WebRTC): `GET /api/rtc/config` (which mints ephemeral TURN
credentials) and the `/ws/rtc/{room}` signalling socket now require a valid
token by default (ADR-0101, #252). They were public, with no way for an app to
close them, so the TURN relay was an open resource. Opt out with
`TINA4_RTC_REQUIRE_AUTH=false` (or `0`/`no`/`off`) for anonymous calling; any
other value keeps them secured. If your app relied on the open routes, set
that env var.

### Fixed
- Frond: a bare reference to a zero-argument callable global is now invoked and
its return value used for both `{{ g }}` output and `{% if g %}` truthiness,
matching Node (ADR-0085, tina4-documentation#90). `g()` still calls exactly
once; a non-callable global and a callable that declares parameters are
unchanged.
- CSP: the per-response nonce is no longer injected into a `script-src` /
`style-src` directive that already carries `'unsafe-inline'` (ADR-0100,
tina4-php#330). A browser ignores `'unsafe-inline'` once a nonce joins the same
directive, so inline content an app deliberately allowed through `TINA4_CSP`
was silently blocked from 3.13.143 on. The app's explicit choice now wins; the
strict default policy is still nonced.

### Changed
- Skills: the maintainer skill gains an off-skill self-audit reflex, and four
bundled skill descriptions were refolded to valid YAML.

## 3.13.153 - 2026-10-21

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The full discipline lives in `.claude/skills/tina4-maintainer/SKILL.md`; this bl

# Tina4 Python

Version 3.13.153 - Lightweight Python web framework. See https://tina4.com for full documentation.
Version 3.13.154 - Lightweight Python web framework. See https://tina4.com for full documentation.

## Build & Test

Expand Down Expand Up @@ -944,7 +944,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. **`TINA4_DEV_ALLOWED_PEERS`** (opt-in, default none) is a comma-separated IP/CIDR allow-list of RAW socket peers (v4/v6, never a forwarded header) admitted to `/__dev` - the documented way to reach the dashboard from a Docker dev box whose requests arrive from the container-network gateway, e.g. `172.16.0.0/12` (#279). `/__dev/toolbar.css` and `/__dev/toolbar.js` are static and always load; a viewer the gate would refuse gets no toolbar injected
- 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.153
- Version: 3.13.154

## 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.153"
version = "3.13.154"
description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
authors = [
{name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
Expand Down
Loading
Loading