Skip to content

Releases: tina4stack/tina4-php

3.12.1

04 May 14:04

Choose a tag to compare

ci(publish): make contents:write explicit on the release job

PHP's release step works today because tina4-php's repo-level Workflow
permissions setting is "Read and write". That's a runtime click in
Settings → Actions, not version-controlled. If someone flipped that
setting tomorrow, every release would silently start 403'ing.

Declaring permissions: contents: write on the job is the bulletproof
form — the GITHUB_TOKEN gets the right scope regardless of repo
defaults. Aligned with the same fix landing in tina4-ruby and
tina4-nodejs (where this WAS biting us).

Also bumping softprops/action-gh-release v1 → v2 (v1 unmaintained).

3.12.0

04 May 13:45

Choose a tag to compare

release: v3.12.0 — env var TINA4_ prefix, bug fixes #36-39

BREAKING CHANGE. Every framework env var now requires TINA4_ prefix.
Hard rename, no fallback chain. Setting any of the 22 legacy un-prefixed
names (DATABASE_URL, SECRET, SMTP_HOST, HOST_NAME, etc.) makes the
framework refuse to boot — App::checkLegacyEnvVars() prints the rename
map and exit(2)s. Bypass via TINA4_ALLOW_LEGACY_ENV=true for migration.

PHP guard reads three sources (getenv(), $_ENV, $_SERVER) since CLI/FPM/
Apache populate them inconsistently. Stored as App::LEGACY_ENV_VARS
class constant, public for test introspection.

What's in this release:

  1. Hard env var rename (22 vars) + boot guard

    • Tina4/App.php: LEGACY_ENV_VARS constant + checkLegacyEnvVars(),
      hooked into start() as the first action
    • All 17 framework PHP files updated — getenv()/getEnv()/$ENV reads
      of legacy names removed; only TINA4
      prefixed names are read now
    • PORT/HOST/NODE_ENV/RACK_ENV/RUBY_ENV/ENVIRONMENT stay un-prefixed
  2. New regression test

    • tests/LegacyEnvGuardTest.php — 36 tests, 86 assertions:
      22-entry mapping shape, every legacy var trips guard, getenv/
      $_ENV/$_SERVER detection, TINA4_ALLOW_LEGACY_ENV bypass with
      truthy/falsy values, full error-message contents
  3. Bug fixes (originally tracked for 3.11.33, folded into 3.12.0)

    • #39 Landing + template auto-routing:
      • Tina4/Router.php: pages/-scoped resolveTemplate() + buildTemplateCache()
      • TINA4_TEMPLATE_ROUTING=off kills auto-routing
      • Tina4/StaticFiles.php: index.html resolution at / and /foo/
      • Tina4/App.php: landing page route only when TINA4_DEBUG=true
      • Tina4/Server.php: RFC 7231/9110 reason phrase table — no more
      "HTTP/1.1 404 OK"
    • #38 PostgreSQL UUID-PK transaction abort:
      • Tina4/Database/PostgresAdapter.php: SAVEPOINT _t4_lastval_probe
      wrap; UUID INSERTs no longer poison outer txn
    • #37 frond.form.submit redirect — verified shipped (frond v2.1.3)
    • #36 Session file handler — safeguards re-verified
  4. composer.json: 'version' field NOT included.
    Packagist reads from the git tag — having both breaks the crawler
    (see commit 171f37b for the full story).

  5. Frond v2.1.3 bundle (version-stamped footer)

Tests: 2502 tests, 5658 assertions, 0 failures, 11 skipped (pre-existing).
New test files:

  • tests/LandingPageTest.php (44 tests, 460 assertions)
  • tests/PostgresUuidPkTest.php (6 tests, 10 assertions, live PG)
  • tests/LegacyEnvGuardTest.php (36 tests, 86 assertions)

Migration:
$ tina4 env-migrate # rewrites .env automatically (Python CLI;
# use sed for now in PHP-only projects)
Or rename manually using the map in book chapter 33.

Coordinated release across all 4 frameworks at 3.12.0.

3.11.36

04 May 09:51

Choose a tag to compare

fix(packagist): remove hardcoded version field from composer.json

Packagist's crawler skipped every tag from 3.11.33 onwards with:
"Skipped tag X.Y.Z, tag (X.Y.Z.0) does not match version (3.11.32.0)"

The version field was re-added in 8b4ddc3 (v3.11.32 release) — it had
been removed in 52baf4e for this exact reason. Hardcoded versions in
composer.json on git-tagged packages always break Packagist sync; the
crawler reads the tag name as the canonical version.

Removing the field permanently. Future tag pushes will sync correctly.

Affected releases now sitting in GitHub but invisible on Packagist:
3.11.33 — security patch ($SERVER HTTP* leak)
3.11.34 — CI fix (Auth SECRET resolution)
3.11.35 — Firebird auto-reconnect on dead sockets

This release (3.11.36) carries the same content as 3.11.35 plus this
single composer.json one-liner — push exists primarily so Packagist
has a tag without the version field to ingest.

3.11.35

04 May 09:19

Choose a tag to compare

fix(firebird): one-shot reconnect on dead-socket errors

Idle Firebird connections die silently behind NAT timeouts, server-side
ConnectionIdleTimeout, or Docker network rotation. The next ibase_prepare
crashes with "Error writing data to the connection." (or "connection
shutdown", "Connection lost", etc.) and the request blows up.

FirebirdAdapter::executeInternal now wraps the underlying ibase calls in a
one-shot reconnect-and-retry: on a dead-connection error marker, close
the stale handle (silently), reopen using cached connection params, and
retry the statement once on a fresh handle. Skipped inside an explicit
transaction — atomicity beats resilience there; caller handles rollback.

Public API: FirebirdAdapter::isDeadConnection(?string) for spec/test reuse.

15 regression tests in FirebirdReconnectTest.php cover the matcher
(real-world wording, logical-error rejection, case insensitivity, null
input).

Parity: identical fix shipping to tina4-python and tina4-ruby in 3.11.35.

3.11.34

04 May 09:09

Choose a tag to compare

fix(ci): Auth SECRET resolution prefers getenv() over $_ENV

Auth::getToken and Auth::validToken resolved SECRET as
$_ENV['SECRET'] ?? getenv('SECRET'). Router::dispatch then read
getenv('SECRET') and passed it explicitly to validToken. When CI
loads SECRET into $_ENV (via .env or shell env) and a test calls
putenv("SECRET=test-value") to override, the two sources disagree:

  • getToken signs with $_ENV['SECRET'] (stale)
  • Router resolves getenv('SECRET') (the test's value)
  • validToken receives the explicit secret (the test's value)
  • Signature mismatch → 401

Manifested as RouterV3Test::testDispatchSecureRouteWithToken failing
on every push to v3 since 3.11.29 (401 vs 200).

Fix: flip the priority — getenv() ?: $_ENV[...]. putenv() at runtime
now consistently overrides, matching standard PHP idiom. DotEnv::load
already writes to BOTH sources via setVariable(), so .env-loaded values
are unaffected.

Also: Router::dispatch no longer pre-resolves the secret; it calls
Auth::validToken($token) with no secret arg, letting Auth resolve
once. One source of truth.

Tests: 4 new regression tests in AuthV3Test (getenv-overrides-$_ENV,
explicit-secret pattern, direct priority probe, JWT_ALGORITHM parity).
RouterV3Test setUp/tearDown now snapshot/clear SECRET in BOTH $_ENV
and getenv to prevent cross-test contamination on shared processes.

Total: 2434 tests passing.

3.11.33

04 May 08:57

Choose a tag to compare

fix: drop unset PK from save() payload + ORMV3 tests + entrypoint

ORM.php: when save() runs an INSERT and the primary key is null/0/empty
on the model, drop it from the column list so the database's auto-
increment / sequence assigns a value. Without this, the declared
public int $id = 0 default leaked into the INSERT as id = 0, SQLite
stored 0 literally, lastInsertId() returned 0, and the property never
synced back. (Issue #102 follow-up.)

tests/ORMV3Test.php: 170 lines of new regression coverage for save(),
PK auto-assignment, and the public-property → row round-trip.

index.php: $app->handle() → $app->run() (handle is for single-request
SAPI dispatch, run is the right entry under the built-in server).

3.11.32

29 Apr 10:12

Choose a tag to compare

release: v3.11.32 — pool/txn atomicity fix + parity alignment

Critical fix. Database with poolSize>0 silently broke transactions.
The pool's round-robin getNextAdapter() rotated to a different adapter
on every call — startTransaction() pinned its flag on adapter A, the
executes autocommitted on adapters B and C, and the final
commit()/rollback() landed on adapter D, which had nothing to commit.
Result: rollback() was a no-op, writes leaked through, no error or
log surfaced the problem.

The fix: per-instance pinned adapter in Database. While inside a
transaction, getNextAdapter() returns the pinned adapter so the
whole transaction runs on one connection. startTransaction() sets
the pin, commit() and rollback() clear it. PHP-FPM is one process
per request, so a plain instance property is the right primitive
(Python uses threading.local for its multi-threaded model).

  • fix (Tina4/Database/Database.php): adapter pinning across
    transaction scope. Every backend affected.
  • tests (tests/PoolTransactionAtomicityTest.php): new regression
    suite — six tests covering rollback under pool=4, commit under
    pool=4, pin release after commit, pin release after rollback,
    pool=0 no-regression, pin honoured across executes. All pass;
    full suite 2416 tests green.
  • composer.json: version field added (3.11.32). App::resolveVersion()
    reads from composer metadata at runtime.
  • parity: all 4 frameworks aligned at 3.11.32. Coordinated release
    across PyPI, Packagist, RubyGems, npm.

3.11.31

25 Apr 09:26

Choose a tag to compare

release: v3.11.31 — Live Docs (live API RAG) + dev-admin UX overhaul

The framework is now a live RAG. AI tools (Claude Code, Cursor,
dev-admin chat) hit /__dev/api/docs/* on the running server and get
ground-truth signatures for both framework public API AND the user's
own src/ code via Tina4\Docs reflection. No staleness, no cross-version
drift, no privacy concerns — the running server IS the source of
truth. Auto-discovery via .tina4/mcp.json so MCP-aware tools just find
it. See plan/v3/22-LIVE-API-RAG.md for the full design.

Major surface:

  • New \Tina4\Docs class — token-parser reflection over Tina4/* and
    the user's src/{orm,routes,app,services,services} dirs. Search,
    classSpec, methodSpec, index, drift detection, sync. 16 tests.
  • HTTP /__dev/api/docs/{search,class,method,index,.well-known.json}
    endpoints — thin wrappers around Docs.
  • MCP tools api_search / api_class / api_method registered alongside
    existing docs_* (markdown search) — same names across all 4
    frameworks for cross-framework AI-tool compatibility.
  • .tina4/mcp.json auto-discovery written on first server boot in
    debug mode. .gitignore amended automatically.

Dev-admin UX overhaul (cross-framework parity in the same release wave):

  • Dev toolbar now injects on 404 / 403 / 500 error pages — error
    pages are no longer dead-ends.
  • Error overlay HTML embeds the toolbar inline, with a one-click
    link to the dev-admin SPA.
  • Dev-admin overlay state persisted to localStorage so saving a file
    (which kicks the file watcher → page reload) doesn't dismiss the
    user's chat / plan / file tree. Auto-restored on next page paint.
  • opcache_reset() on server boot in debug mode + opcache_invalidate()
    per-file on /__dev/api/reload POST. Fixes the symptom where
    patched method calls report "Call to undefined method ::template()"
    long after the source has been corrected.

Documentation cleanup (CLAUDE.md):

  • Phantom methods removed: AI::detectAi/AI::detectAiNames/
    AI::installContext/AI::statusReport (replaced with the real
    isInstalled/showMenu/installSelected/installAll/generateContext
    surface), Api::addCustomHeaders → addHeaders,
    Api::setUsernamePassword → setBasicAuth,
    Migration::doMigration → migrate.
  • \Tina4\Debug::message rewritten to Log::debug/info/warning/error
    (the real class name).

PHPUnit: 2410 / 2410 still green. New: DocsTest (16 examples).
4 PHP-deprecation warnings remaining (down from 7) — all in test
files (setAccessible), out of scope for framework releases.

3.11.30

25 Apr 02:12

Choose a tag to compare

release: v3.11.30 — /image proxy + fuzzier intent regex in chat

The SPA's image-generation flow (aiGenerateImage) was POSTing to
/image/v1/images/generations expecting SDXL Turbo on the other end.
In Vite dev that path is proxied to andrevanzuydam.com:11436
directly; in deployed framework it hit PHP and got 404 ('No image
returned: 404 …'). Add a passthrough proxy in
DevAdmin so the deployed flow works the same as Vite dev. Reads
TINA4_IMAGE_URL (default http://andrevanzuydam.com:11436), 180s
timeout to cover SDXL's slow first-token.

Bundle refresh ships a fuzzier image-intent regex: 'pciture'
(typo), 'pic', 'photo', 'sketch', 'art' all match now. Bare
'draw ' or 'sketch ' also match without needing
the noun. Without this, 'make a pciture of a cow' fell through to
the LLM which (correctly) said it can't generate images — the user
read that as the system lying.

3.11.29

25 Apr 02:04

Choose a tag to compare

release: v3.11.29 — fputcsv $escape explicit (PHP 8.5)

PHP 8.5 deprecated using fputcsv() without an explicit $escape
argument; PHP 9 will change the default. Pass '\' explicitly so
existing CSV consumers see no behavioural change. Was producing a
deprecation warning on every DatabaseResult::toCsv() call.

Test deprecations: 5 → 4 (3 remaining are setAccessible() calls in
tests/ only, out of scope for framework releases).