diff --git a/.claude/skills/tina4-maintainer/references/checklists/release.md b/.claude/skills/tina4-maintainer/references/checklists/release.md index 68e37eb9..08608e3c 100644 --- a/.claude/skills/tina4-maintainer/references/checklists/release.md +++ b/.claude/skills/tina4-maintainer/references/checklists/release.md @@ -1,5 +1,8 @@ # Checklist: cutting a framework release + +> The canonical, end-to-end release flow is documented at https://tina4.com/general/releasing - the four release tracks, the version-bearing files per repo, the skills bundle, EV signing and the gates. This checklist is the maintainer's working quick-reference; when the two differ, the documentation is the source of truth. + A release is tagged, not merged — **merging never publishes; the tag does.** A tag matching `[0-9]*.*.*` (e.g. `3.13.146`, no `v`) triggers the publish workflow from the protected `release` environment (required reviewer `tina4stack`). Do FEWER, BIGGER releases — bundle many diff --git a/.claude/skills/tina4-maintainer/references/checklists/signing.md b/.claude/skills/tina4-maintainer/references/checklists/signing.md index f103177e..03966f56 100644 --- a/.claude/skills/tina4-maintainer/references/checklists/signing.md +++ b/.claude/skills/tina4-maintainer/references/checklists/signing.md @@ -1,5 +1,8 @@ # Checklist: signing the skills installer (`install-skills.ps1`) + +> The release flow that frames this - where signing sits in the order and why it comes last - is documented at https://tina4.com/general/releasing. This checklist is the signing detail. + `install-skills.ps1` is served to users at a tag and must carry a VALID Code Infinity Authenticode (EV) signature. BOTH the CI gate (`.github/workflows/ci.yml`, which validates it on `windows-latest` with `Get-AuthenticodeSignature`) and the tina4.com runtime shim reject it diff --git a/AGENTS.md b/AGENTS.md index eaf9aaef..dedd4873 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Tina4 Python — Agent Instructions -v3.13.150. 140 cataloged features, zero dependencies. Python 3.12+. +v3.13.151. 140 cataloged features, zero dependencies. Python 3.12+. ## AI Skills diff --git a/CHANGELOG.md b/CHANGELOG.md index 05ad0419..06e362cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,28 @@ https://tina4.com/python/36-releases This file records framework-specific changes. The release notes above remain the authority for shipped versions. +## 3.13.151 - 2026-10-15 + +### Security +- Cross-Origin-Opener-Policy joins the default security-header set (`TINA4_COOP`, + default `same-origin-allow-popups`). It pairs with a COEP header to unlock + `SharedArrayBuffer` and WASM threads in the browser. +- S3 storage is a zero-dependency SigV4 client (no boto3). It now fails loud on a + missing credential, retries 429/5xx and dropped connections with capped backoff, + honours `Retry-After`, and keeps the endpoint path prefix. +- MongoDB keeps bound values out of the parsed SQL text. +- CRUD attributes are de-raw'd, the 302 redirect is guarded, CodeQL core findings + are cleared, and uploaded files are created owner-only (0600). + +### Performance +- The dispatch pipeline no longer blocks the asyncio event loop. Session load and + save, the WebSocket backplane publish, and realtime chat and upload I/O are + offloaded to threads, so concurrent requests that hit a blocking session backend + no longer serialise behind each other. + +### Docs +- The README built-in feature count is corrected (55 -> 140). + ## 3.13.150 — 2026-10-11 ### Dev admin diff --git a/CLAUDE.md b/CLAUDE.md index 11c94ad8..0d294f9e 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.150 - Lightweight Python web framework. See https://tina4.com for full documentation. +Version 3.13.151 - Lightweight Python web framework. See https://tina4.com for full documentation. ## Build & Test @@ -943,7 +943,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.150 +- Version: 3.13.151 ## Links diff --git a/pyproject.toml b/pyproject.toml index 17b5c935..bdcaba01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "tina4-python" -version = "3.13.150" +version = "3.13.151" 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 7aace794..84dfb6ba 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.150" + return "3.13.151" __version__ = _resolve_version() diff --git a/uv.lock b/uv.lock index 4ccd739b..55646818 100644 --- a/uv.lock +++ b/uv.lock @@ -1599,7 +1599,7 @@ wheels = [ [[package]] name = "tina4-python" -version = "3.13.150" +version = "3.13.151" source = { editable = "." } [package.optional-dependencies]