Raw JSON report machine-readable
{
"data": {
"repo": {
"topics": [
"ai",
"claude",
"llm",
"openai",
"php",
"typo3",
"typo3-extension",
"anthropic",
"gemini",
"gpt",
"embeddings",
"ollama",
"provider-abstraction",
"streaming"
],
"is_fork": false,
"size_kb": 14310,
"has_wiki": false,
"homepage": "https://netresearch.github.io/t3x-nr-llm/",
"languages": {
"CSS": 61053,
"PHP": 8217638,
"HTML": 298555,
"Jinja": 29300,
"Shell": 53845,
"Python": 41180,
"Makefile": 4065,
"Dockerfile": 2092,
"JavaScript": 177680,
"TypeScript": 113902
},
"pushed_at": "2026-07-22T06:07:44Z",
"created_at": "2025-12-22T23:35:44Z",
"owner_type": "Organization",
"updated_at": "2026-07-22T06:08:15Z",
"description": "The shared AI foundation for TYPO3 — one LLM setup for every extension on your site",
"is_archived": false,
"is_disabled": false,
"license_spdx": "GPL-2.0",
"default_branch": "main",
"license_spdx_raw": "GPL-2.0",
"primary_language": "PHP",
"significant_languages": [
"PHP"
]
},
"owner": {
"blog": "https://www.netresearch.de/",
"name": "Netresearch DTT GmbH",
"type": "Organization",
"login": "netresearch",
"company": null,
"location": "Germany",
"followers": 40,
"avatar_url": "https://avatars.githubusercontent.com/u/151247?v=4",
"created_at": "2009-11-10T12:46:11Z",
"is_verified": null,
"public_repos": 278,
"account_age_days": 6097
},
"license": {
"state": "standard",
"spdx_id": "GPL-2.0",
"raw_spdx": "GPL-2.0",
"file_present": true,
"scorecard_found": true,
"profile_has_license": true
},
"activity": {
"releases": [
{
"tag": "v0.23.0",
"kind": "minor",
"published_at": "2026-07-20T11:22:07Z"
},
{
"tag": "v0.22.0",
"kind": "minor",
"published_at": "2026-07-18T02:56:44Z"
},
{
"tag": "v0.21.0",
"kind": "minor",
"published_at": "2026-07-17T11:21:04Z"
},
{
"tag": "v0.20.0",
"kind": "minor",
"published_at": "2026-07-16T14:22:16Z"
},
{
"tag": "v0.19.0",
"kind": "minor",
"published_at": "2026-07-14T18:53:18Z"
},
{
"tag": "v0.18.0",
"kind": "minor",
"published_at": "2026-07-14T03:29:15Z"
},
{
"tag": "v0.17.0",
"kind": "minor",
"published_at": "2026-07-13T09:42:35Z"
},
{
"tag": "v0.16.1",
"kind": "patch",
"published_at": "2026-07-10T13:59:24Z"
},
{
"tag": "v0.16.0",
"kind": "minor",
"published_at": "2026-07-10T09:25:28Z"
},
{
"tag": "v0.15.0",
"kind": "minor",
"published_at": "2026-07-09T15:33:21Z"
},
{
"tag": "v0.14.1",
"kind": "patch",
"published_at": "2026-07-05T12:42:07Z"
},
{
"tag": "v0.14.0",
"kind": "minor",
"published_at": "2026-07-04T15:05:58Z"
},
{
"tag": "v0.13.0",
"kind": "minor",
"published_at": "2026-06-26T16:34:55Z"
},
{
"tag": "v0.12.0",
"kind": "minor",
"published_at": "2026-06-11T15:55:33Z"
},
{
"tag": "v0.11.1",
"kind": "patch",
"published_at": "2026-06-10T09:44:51Z"
},
{
"tag": "v0.11.0",
"kind": "minor",
"published_at": "2026-06-10T07:38:56Z"
},
{
"tag": "v0.10.0",
"kind": "minor",
"published_at": "2026-06-09T07:09:41Z"
},
{
"tag": "v0.9.0",
"kind": "minor",
"published_at": "2026-06-08T20:26:40Z"
},
{
"tag": "v0.8.0",
"kind": "minor",
"published_at": "2026-06-02T15:40:18Z"
},
{
"tag": "v0.7.0",
"kind": "minor",
"published_at": "2026-04-22T13:39:24Z"
},
{
"tag": "v0.6.0",
"kind": "minor",
"published_at": "2026-03-24T14:59:14Z"
},
{
"tag": "v0.5.0",
"kind": "minor",
"published_at": "2026-03-09T12:39:08Z"
},
{
"tag": "v0.4.11",
"kind": "patch",
"published_at": "2026-03-08T00:04:26Z"
},
{
"tag": "v0.4.10",
"kind": "patch",
"published_at": "2026-03-07T23:43:46Z"
},
{
"tag": "v0.4.9",
"kind": "patch",
"published_at": "2026-03-07T22:41:57Z"
},
{
"tag": "v0.4.8",
"kind": "patch",
"published_at": "2026-03-07T20:28:21Z"
},
{
"tag": "v0.4.7",
"kind": "patch",
"published_at": "2026-03-07T19:16:39Z"
},
{
"tag": "v0.4.6",
"kind": "patch",
"published_at": "2026-03-06T21:59:31Z"
},
{
"tag": "v0.4.5",
"kind": "patch",
"published_at": "2026-03-06T20:21:26Z"
},
{
"tag": "v0.4.4",
"kind": "patch",
"published_at": "2026-03-06T19:21:05Z"
},
{
"tag": "v0.4.3",
"kind": "patch",
"published_at": "2026-03-06T14:12:38Z"
},
{
"tag": "v0.4.2",
"kind": "patch",
"published_at": "2026-03-06T11:56:58Z"
},
{
"tag": "v0.4.1",
"kind": "patch",
"published_at": "2026-03-06T11:20:10Z"
},
{
"tag": "v0.4.0",
"kind": "minor",
"published_at": "2026-03-06T10:49:08Z"
},
{
"tag": "v0.3.2",
"kind": "patch",
"published_at": "2026-03-04T00:06:01Z"
},
{
"tag": "v0.3.1",
"kind": "patch",
"published_at": "2026-03-02T02:26:43Z"
},
{
"tag": "v0.3.0",
"kind": "minor",
"published_at": "2026-03-01T10:21:13Z"
},
{
"tag": "v0.2.2",
"kind": "patch",
"published_at": "2026-03-01T08:40:58Z"
},
{
"tag": "v0.2.1",
"kind": "patch",
"published_at": "2026-02-28T20:27:58Z"
},
{
"tag": "v0.2.0",
"kind": "minor",
"published_at": "2026-02-28T20:01:00Z"
},
{
"tag": "v0.1.2",
"kind": "patch",
"published_at": "2026-01-11T23:10:46Z"
},
{
"tag": "v0.1.1",
"kind": "patch",
"published_at": "2026-01-11T22:42:29Z"
},
{
"tag": "v0.1.0",
"kind": "minor",
"published_at": "2026-01-11T22:13:53Z"
}
],
"recent_commits": [
{
"oid": "28ec3e2c5d66a08a075fc92d851837b2b0958898",
"body": "Patch release shipping the unified v14 3-color icon family (v13 legacy\ntiles) and the backend dark-mode fixes for\nPlayground/SetupWizard/Analytics\n([#488](https://github.com/netresearch/t3x-nr-llm/pull/488)).\nAdditive/non-breaking.",
"is_bot": false,
"headline": "chore: release v0.23.1 (#489)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T05:55:31Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "14906a07397348cb9318f79419d5ca3581a586f5",
"body": "…r (ADR-104) (#491)\n\n## What\n\nCompletes P1 #8 (queue epic) slices 3+4 — **ADR-104**: a worker\nheartbeat, a stale-run reaper, retry-per-failure-class, and a\ndead-letter terminus for queued agent runs. Closes the two gaps ADR-102\nleft open:\n\n- **A dead worker stranded its run RUNNING forever** — the l\n[…]\nership-guarded requeue, MAX+1 stream resume, reaper command\npaths, staleness-guarded reaper mutations, `FallbackChainExhausted`\nclassification.\n\nhttps://claude.ai/code/session_01S2HQiw1c1XCXGLeMJ917Mr",
"is_bot": false,
"headline": "feat(agent): worker heartbeat, stale-run reaper, retry and dead-lette…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T05:55:04Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9505e651d24153fb8eaabe5f00978245018cb092",
"body": "…r (ADR-104)\n\nClose the two gaps ADR-102 left open for queued agent runs: a dead worker\nstrands its run RUNNING forever, and a transient provider failure is terminal.\n\nHeartbeat: the runtime's step-boundary trace hook (shared with the ADR-103\ncancellation probe) renews a worker run's lease under an \n[…]\nentRunTerminationReason gained\nRETRIES_EXHAUSTED and NOT_RETRYABLE.\n\nClaude-Session: https://claude.ai/code/session_01S2HQiw1c1XCXGLeMJ917Mr\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(agent): worker heartbeat, stale-run reaper, retry and dead-lette…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T16:07:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "4436e7e8173d0eedfd8793fd5ef4a61062da9680",
"body": "Keep extra.typo3/cms.version and Documentation/guides.xml in step with\next_emconf.php per VersionConsistencyTest (the four-spot release rule).\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "chore: sync composer + guides version to 0.23.1",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T16:03:50Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "77e49c7200a0ca2475e4020e89c113fa55d7e9e2",
"body": "Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "chore: release v0.23.1",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T15:46:27Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "66455307b9763d83c3e3476f2bf594b55eb51f9c",
"body": "## Summary\n\nPart of the Netresearch demo icon/dark-mode overhaul.\n\n**Icons** — the whole set is unified to the TYPO3 v14 3-color grammar\n(`currentColor` primary, `opacity .4` secondary, `var(--nr-icon-accent,\n#2F99A4)` accent), with full-bleed teal `#2F99A4` legacy tiles for v13\nselected at runtime \n[…]\ne fixed`.\n- All icon SVGs validated (well-formed XML, viewBox 0 0 64 64, only the\nallowed palette); render-checked at 16/32/64px on light and dark\nbackgrounds.\n- Relies on PR CI for the JS/CSS matrix.",
"is_bot": false,
"headline": "feat(icons): v14 3-color icon family + backend dark-mode fixes (#488)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T15:25:39Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "fe23f56578a024988103608eea6952104c421147",
"body": "Add the [data-color-scheme=dark] handling to Playground.css (matching\nOverview.css), convert SetupWizard.css off hardcoded light colors to\nbackend custom-property tokens, and drive the Analytics budget bars and\nChart.js palette from CSS custom properties so they follow the active\nbackend color scheme.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(darkmode): make Playground, SetupWizard and Analytics scheme-aware",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T15:08:21Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "41775067fa470026bdf0bbc0402115087b6eab52",
"body": "…y variants\n\nRestyle all module, provider-type and record icons to the v14 3-color\ngrammar (currentColor primary, opacity .4 secondary, var(--nr-icon-accent,\n#2F99A4) accent) and add full-bleed teal legacy tiles selected at runtime\nby TYPO3 major version in Configuration/Icons.php. Extension.svg bec\n[…]\nn branded tile. All registered identifiers and TCA iconfile references\nare unchanged; provider-groq no longer collides with the Task glyph.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(icons): unify icon set to TYPO3 v14 3-color style with v13 legac…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T15:08:21Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5c17ba491f779dcede6721324c960610f403b4ea",
"body": "…dary (#487)\n\n## What\n\nP1 #8, slice 2 (\"Cancel Flag zwischen Modell- und Tool-Schritten\nprüfen\",\n[ADR-103](Documentation/Adr/Adr103CooperativeCancellation.rst)):\ncancellation stops being fence-only. Since ADR-092 the guarded terminal\ntransition won the row, but the in-flight loop ran to completion —\n[…]\nen\n**under PHPUnit 13.1** (stricter than the previous local resolve): cgl,\nphpstan (L10), unit, functional (sqlite, 1244 tests), rector, fuzzy.\n\nhttps://claude.ai/code/session_01S2HQiw1c1XCXGLeMJ917Mr",
"is_bot": false,
"headline": "feat(agent): stop a cancelled run cooperatively at the next step boun…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T13:03:02Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8b4248d365c9071e1b16cd02692d38059faf7ee1",
"body": "…dary\n\nCancellation was a persistence-level fence only (ADR-092): the guarded\nterminal transition won the row, but the in-flight loop ran to completion,\nspending provider calls and executing tools whose outcome was then discarded.\nWith queued worker runs (ADR-102) that gap grows — nrllm:agent:cancel\n[…]\ntream event — a decision, not an error. cancel()'s interface contract is\nupgraded from \"fence only\" to \"fence + cooperative stop\". ADR-103.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(agent): stop a cancelled run cooperatively at the next step boun…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T12:47:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8738dd0728e19b120fb4d7e4f6774304aa623b41",
"body": "A fresh dependency resolve now installs PHPUnit 13.1, which removed with()\nfrom the stubbing API (InvocationStubber): the three\n*LinksUsageRowToConfigurationUid tests configured a createMock() with\nmethod()->with()->willReturn() and no expects(), which fails PHPStan and the\nruntime under PHPUnit 13.\n[…]\nches the documented\n\"called twice now\" comment. composer.lock is deliberately not committed, so\nevery fresh CI resolve would have hit this.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(tests): restore PHPUnit 13 compatibility for three stub expectations",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T12:47:23Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "79fdfa4bb9591eb7973bd0f7bab65c9a3a8cfd08",
"body": "## What\n\nP1 #8, slice 1 of the queue epic\n([ADR-102](Documentation/Adr/Adr102QueuedAgentRuns.rst)): `QUEUED` stops\nbeing reserved vocabulary. `AgentRuntimeInterface` gains the queued half\nof its lifecycle:\n\n```php\npublic function enqueue(AgentRunRequest $request): string; // returns \n[…]\nd-letter (ADR-095 classifier gap for\n`FallbackChainExhaustedException` documented), cooperative cancel\nbetween loop steps, `WAITING_FOR_INPUT`.\n\nhttps://claude.ai/code/session_01S2HQiw1c1XCXGLeMJ917Mr",
"is_bot": false,
"headline": "feat(agent): queued agent runs over the TYPO3 message bus (#486)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T12:16:18Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ccf552b8c5651ccdfcc105b9c840f1857eb6f764",
"body": "…n rehydration\n\nReview finding: rehydrateRequest() threw a generic RuntimeException for a\nconfiguration deleted while the run was queued. Use\nRunConfigurationGoneException — the same typed absence approve() reports —\nso the FAILED run's persisted error class is meaningful. Still caught by\nrunQueued()'s rehydration guard; nothing escapes.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "refactor(agent): report a gone configuration as its typed exception i…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T12:04:42Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "314d3e065a04bdcedd2ee264dd8e903a267ed2c5",
"body": "QUEUED has been reserved vocabulary since ADR-081; ADR-101 deferred\nasynchronous execution to the point where the request payload is persisted so\na different process can run it. This is that step (ADR-102).\n\nThe run row is the state; the message is only a wake-up call:\n\n- enqueue() serialises the Ag\n[…]\ndwork for the stale-run reaper/heartbeat/retry slices of the queue epic.\nAgentRunRepositoryInterface gained enqueueRun() and claimQueued().\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(agent): queued agent runs over the TYPO3 message bus",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T11:53:31Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5d61de74b9e6d9a04392aaf5c2a7aeb829fd21b8",
"body": "## What\n\nP1 #7: the agent-run lifecycle — begin, trace, persist, suspend for\napproval, approve/deny, cancel, event polling, status — moves out of\n`ToolPlaygroundController` (which carried the `runLoop → catch(suspend)\n→ catch(guardrail) → catch(Throwable) → settle` ladder **three times**:\nbatch, res\n[…]\nch runs, review\nqueues, editor actions, status polling (`events()` pages by sequence) —\nnext P1 step: queue/cancel (#8) on top of this surface.\n\nhttps://claude.ai/code/session_01S2HQiw1c1XCXGLeMJ917Mr",
"is_bot": false,
"headline": "feat(agent): extract AgentRuntime as the public agent-run service (#485)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T10:02:54Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c5815dd04084da32db11f4cd43f0948feed7e6d7",
"body": "Two review findings:\n\n- events() loaded and hydrated the whole event stream on every poll and\n filtered in PHP. The sequence predicate now lives in the SQL query\n (findEvents gained an afterSequence parameter down through persister and\n repository), so a poller pages cheaply as the stream grows.\n\n[…]\nve executed the completed payload. COMPLETED breaks explicitly and\n a default arm turns an unhandled outcome into a logged error response.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(agent): page events in SQL and guard the outcome mapping",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T09:51:34Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "bbe0720f1628e23af5fdcf429bf8644b52d2d47d",
"body": "Persistence, approval and resume worked, but were assembled inside\nToolPlaygroundController: it began the run, built the trace, persisted events,\ncaught the suspension, settled the status and executed the resume — with the\nlifecycle ladder copied three times (batch, resume, stream). Any new consumer\n[…]\nspendRun() returns bool. Public-service count 34 -> 35 (ADR-101 is the new\ncount authority). CancelAgentRunCommand gained its missing test.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(agent): extract AgentRuntime as the public agent-run service",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T09:38:01Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "bdc292e96c6533a33492f957448f7f475b79bd69",
"body": "…484)\n\n## What\n\nEach specialized service recorded its own usage by calling\n`trackUsage()` directly after dispatch — a second write path beside\n`UsageMiddleware`, which records the token-shaped chat/embedding/vision\nresponses but skipped the specialized ones (images, characters, audio\nseconds). This \n[…]\nThis completes the specialized-service lifecycle arc (ADR-097 →\nADR-100): dispatch, input screening, fail-closed egress, and now unified\nusage.\n\nhttps://claude.ai/code/session_01S2HQiw1c1XCXGLeMJ917Mr",
"is_bot": false,
"headline": "feat(specialized): record usage through tagged pipeline extractors (#…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T07:04:48Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6b1d02ba1d96ab9feb8bb621b3bb8e749dc43636",
"body": "Each specialized service recorded its own usage by calling trackUsage()\ndirectly after dispatch — a second write path beside UsageMiddleware, which\nrecords the token-shaped chat/embedding/vision responses but skipped the\nspecialized ones (images, characters, audio seconds).\n\nA service no longer writ\n[…]\ngeMiddleware + the service's\nextractor and assert the same rows as before. trackImageUsage() /\ntrackTranscriptionUsage() are gone. ADR-100.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(specialized): record usage through tagged pipeline extractors",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T06:53:29Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "bf5b18d8e353d02ffdd3e23b80d9598d8057eca8",
"body": "…cycle (#483)\n\n## What\n\n[ADR-097](Documentation/Adr/Adr097SpecializedServicesOnPipeline.rst)\nrouted every specialized dispatch through the pipeline, but the wrapping\nwas a **convention**: a service method that built a request and sent it\nwithout `runLifecycle()` would compile, pass, and silently spe\n[…]\nd\n\nFolding the per-service usage recording into a tagged pipeline extractor\nread by the usage middleware (the last specialized-lifecycle step).\n\nhttps://claude.ai/code/session_01S2HQiw1c1XCXGLeMJ917Mr",
"is_bot": false,
"headline": "feat(specialized): fail closed on a provider dispatch outside runLife…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T23:47:52Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0fb0890098804d975f4675717760207427ab25b2",
"body": "…cycle\n\nADR-097 routed the specialized dispatches through the pipeline, but the wrapping\nwas a convention: a method that built a request and sent it without\nrunLifecycle() would compile and silently spend against the provider with no\ntelemetry, circuit breaker or usage. Nothing enforced it.\n\nAbstrac\n[…]\ntion, and getUsage()\n/ getGlossaries() as the new ProviderOperation::Metadata — observable and\nbreaker-guarded, labelled honestly. ADR-099.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(specialized): fail closed on a provider dispatch outside runLife…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T23:36:11Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "969f25dae09816cafe408c681a807675cf4d35d5",
"body": "## Problem\n\nEach LLM translation increments the backend **\"requests\"** KPI by **2**\n(or **3** when the source language is auto-detected) instead of 1.\n`LlmServiceManager->complete()` correctly increments by 1. Reported in\n#473.\n\n## Root cause\n\n`UsageAnalyticsService::getKpiTotals()` sums `request_co\n[…]\nTest`): the translator's underlying chat call is\nsuppressed.\n\nLocal: unit (5400), the changed functional file (27), rector, fuzzy, cgl\nall green; PHPStan adds no errors over the base tree.\n\nFixes #473",
"is_bot": false,
"headline": "fix: count one usage request per translation (#473) (#482)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T23:34:04Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "cc8f049a87a1ba5ead21d0dcac0a11e6ca3f611a",
"body": "Review on #482: detectLanguageAttributed() hardcoded suppressRequestCount,\nso a standalone LlmTranslator::detectLanguage() call recorded zero requests\n(the translator writes no separate 'translation' row for it). Thread a\ncountsAsRequest flag through the method — false for the translate() detection\nsub-step, true for the standalone public call — matching how TranslationService\nhandles its own detection.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix: count standalone LlmTranslator language detection as a request",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T23:24:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "854e3359c042eeec25331d168295c6034457b010",
"body": "A single LLM translation incremented the backend \"requests\" KPI by two\n(or three with auto-detection) instead of one. The analytics overview\n(UsageAnalyticsService::getKpiTotals) sums request_count across all rows,\nand a translation produced several:\n\n- TranslationService::translate()/translateForCo\n[…]\nflag, so the translation stays the single request of record. Standalone\ndetectLanguage() still counts as its own request.\n\nFixes #473\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix: count one usage request per translation (#473)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T23:09:00Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "063639d5f2aabc007655e9d2967891f651bfbe48",
"body": "…rails (#481)\n\n## What\n\nThe specialized services send a **prompt**, not a chat-message list, so\nthe input guardrails that screen a chat prompt on the send path\n([ADR-087](Documentation/Adr/Adr087InputGuardrails.rst)) never saw them\n— image / speech / translation prompts reached the providers unscree\n[…]\neam (`getSecureClient()` throwing when no\nlifecycle context is active).\n- Folding per-service usage recording into a tagged pipeline extractor.\n\nhttps://claude.ai/code/session_01S2HQiw1c1XCXGLeMJ917Mr",
"is_bot": false,
"headline": "feat(specialized): screen specialized prompts through the input guard…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T23:07:06Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a5f9184b8272ab863d38a0d75b1ae58c37d1f5ce",
"body": "… budget\n\nTwo review findings on the input-guardrail screening:\n\n- A REDACT verdict whose redactedContent is null previously fell back to the\n original, unredacted content — a fail-open. The GuardrailResult constructor is\n private and redact() mandates a non-null string, so it is unreachable via t\n[…]\nrompt aborts without the budget-aggregation queries.\n Aligns DALL-E edit, FAL and DeepL with the order generate/synthesize already\n used.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(guardrail): fail closed on a contentless REDACT and screen before…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T22:56:21Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9461fbeeb19532b78c782df46b84eceda0920539",
"body": "…rails\n\nThe specialized services send a prompt, not a chat-message list, so the input\nguardrails that screen a chat prompt on the send path (ADR-087) never saw them —\nimage / speech / translation prompts reached the providers unscreened. The\npipeline cannot screen their input either: a middleware co\n[…]\nng: subclasses / manual construction must pass the screener; an\nInputGuardrailScreener([]) with no guardrails is a valid test pass-through.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(specialized): screen specialized prompts through the input guard…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T22:40:48Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "55aa75cf8b50fce4157b57d0a8c4ba6a5a47a25d",
"body": "…line (#480)\n\n## What\n\nCompletes the specialized-service pipeline migration begun with DALL·E\n(ADR-097). The remaining four services now wrap their HTTP dispatch in\n`runLifecycle()` with a `forService()` context, so **every** specialized\nAI call writes a telemetry row with a correlation id and is gu\n[…]\natch seam (now switchable since all five route through\n`runLifecycle`).\n- Folding per-service usage recording into a tagged pipeline extractor.\n\nhttps://claude.ai/code/session_01S2HQiw1c1XCXGLeMJ917Mr",
"is_bot": false,
"headline": "feat(specialized): route FAL, Whisper, TTS and DeepL through the pipe…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T21:54:06Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "7fedacdd5e4ed8cf364fd874a1af95053d3ca583",
"body": "The three Whisper runLifecycle contexts hardcoded 'whisper-1', so a custom\n$options->model would still be recorded — and its circuit breaker keyed —\nunder 'whisper-1'. Use $options->model ?? self::DEFAULT_MODEL, matching the\nmodel the request and usage recording already resolve.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(specialized): label Whisper telemetry with the requested model",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T21:41:41Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "46600b4d5aa64aa19723b0d71649dbbf2d280388",
"body": "…cation\n\ngenerate() and generateMultiple() shared the identical endpoint-resolution,\npayload-build and runLifecycle dispatch block (16 duplicated lines flagged by\nSonarCloud). Fold it into a private dispatchGeneration() helper both call.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "refactor(specialized): extract FAL dispatchGeneration() to drop dupli…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T21:33:20Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a719645f00f2e248513e4599d775ffc53f4db82e",
"body": "…rait\n\nThe four new routing tests (FAL/Whisper/TTS/DeepL) each repeated the identical\nfive-line captured-context expectation, tripping SonarCloud's new-code\nduplication gate (6.5% > 3%). Move it into PipelineRoutingAssertionTrait so the\nexpectation lives once and each test calls\nassertRoutedThroughPipeline($capture, ProviderOperation::...).\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "test(specialized): extract shared pipeline-routing assertion into a t…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T21:23:36Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ed61768fab47e7299dbaf3f86129440238fc6bf6",
"body": "…line\n\nCompletes the specialized-service migration begun with DALL-E (ADR-097): the\nremaining four services wrap their HTTP dispatch in runLifecycle() with a\nforService() context, so every specialized call now writes a telemetry row with\na correlation id and is guarded by the provider circuit breake\n[…]\nt all five route through runLifecycle it can\nbe switched on), and folding the per-service usage recording into a tagged\npipeline extractor.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(specialized): route FAL, Whisper, TTS and DeepL through the pipe…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T21:08:11Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1d9d669629a69a8d016ce30d5d9ab4f588ff1591",
"body": "…ry + circuit breaker) (#479)\n\n## Description\n\nFirst specialized service to join the shared middleware lifecycle\n(ADR-097). The specialized services dispatch HTTP directly and bypass\nthe pipeline, so they get no telemetry, no correlation id, no circuit\nbreaker.\n[ADR-096](../blob/main/Documentation/A\n[…]\nipeline\nwith an ImageGeneration context (provider/model/correlation id); all 10\nspecialized test files updated for the constructor\n- [x] ADR-097 added\n\nNo version bump; changelog under `[Unreleased]`.",
"is_bot": false,
"headline": "feat(specialized)!: route DALL-E through the shared pipeline (telemet…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T20:42:07Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c3e0a1f47da706d6dea9a7232dc11e1064dd1a1a",
"body": "…ng test\n\nPHP does not allow by-reference constructor property promotion (`private mixed\n&$captured`) — it is a fatal compile error that broke the CI PHPStan matrix (a\nlocal parser tolerated it). The recording middleware now captures into a local\nvia a closure callback, and runLifecycle()'s terminal declares the\nProviderCallContext parameter explicitly for strict analysis.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(test): drop reference constructor promotion in the pipeline-routi…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T20:32:24Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6a617f3dfe03bf82349c1ff8af6f925af761824a",
"body": "The specialized services dispatch HTTP directly and bypass the middleware\npipeline, so they get none of what a chat call gets: no telemetry row, no\ncorrelation id, no circuit breaker. ADR-096 made that reachable by moving the\nconfiguration onto the call context; this is the first service to take it.\n[…]\nddlewarePipeline\nconstructor parameter after budgetService; an empty MiddlewarePipeline([]) is a\nvalid pass-through for tests. See ADR-097.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(specialized)!: route DALL-E dispatch through the shared pipeline",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T20:11:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b4dc0daf1b9e7a56c69f55b6acbdb7af3bb8cece",
"body": "…ntext (enables non-chat callers) (#478)\n\n## Description\n\nThe middleware pipeline (budget, telemetry, cache, idempotency,\nguardrail, fallback, usage, circuit breaker) took the `LlmConfiguration`\nas a **separate positional parameter** of `MiddlewarePipeline::run()`\nand every `ProviderMiddlewareInterf\n[…]\nThe ~26 middleware/pipeline test files migrated to the new API;\nnew `ProviderCallContext` factory + telemetry-fallback tests added\n- [x] ADR-096 added\n\nNo version bump; changelog under `[Unreleased]`.",
"is_bot": false,
"headline": "refactor(provider)!: move the pipeline configuration onto the call co…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T19:42:13Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0a7e05cce51a84414a151ac9447c84061290de9b",
"body": "… is empty\n\ntelemetryProvider/Model/ConfigurationIdentifier and UsageMiddleware read the\nconfiguration entity with '??', which only falls through when the whole entity\nis null. A transient/ad-hoc configuration can be present but carry an empty\nprovider/model/identifier, in which case the context str\n[…]\ny check (and ':?' in UsageMiddleware) so an empty entity value falls\nthrough to the context string as intended. A test locks the behaviour.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(provider): fall through to the context string when a config value…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T19:33:46Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "336297951dc1936c2be5f7a9c00bad291a61721c",
"body": "…ntext\n\nThe middleware pipeline took the LlmConfiguration as a separate positional\nparameter of MiddlewarePipeline::run() and of every ProviderMiddlewareInterface\n::handle(). Six of the eight middleware merely forwarded it; it existed as its\nown parameter only so FallbackMiddleware could substitute \n[…]\nndle() changed signature — a downstream custom middleware or direct run()\ncaller must move the configuration onto the context. See ADR-096.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "refactor(provider)!: move the pipeline configuration onto the call co…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T19:20:49Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e716a6166f8600edb95af8a9b73c5e1167ceb53d",
"body": "…ps the breaker; 429 typed on specialized path (#477)\n\n## Description\n\nThree places decided \"is this failure worth retrying against another\nprovider\" — `FallbackMiddleware`, `CircuitBreakerMiddleware` and\n`StreamingDispatcher` — each with its own `instanceof` ladder. They had\ndrifted, and **none ret\n[…]\nx] New tests: `FailureClassTest`, `FailureClassifierTest`, 5xx\nfallback + circuit-breaker behaviour, updated specialized 429 tests\n- [x] ADR-095 added\n\nNo version bump; changelog under `[Unreleased]`.",
"is_bot": false,
"headline": "feat(provider)!: shared failure taxonomy — 5xx now fails over and tri…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T17:54:17Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "766c041eeb468a1c0c6cd69be6afa4c0663baece",
"body": "…tions\n\ngetStatusCode() previously returned the upstream status only for the generic\nServiceUnavailableException (the default mapErrorStatus arm). The typed\nServiceConfigurationException (401/403) and ServiceQuotaExceededException (429)\ncarried none, so the seam a failure classifier reads on the specialized path was\ninconsistent. Both factories now record statusCode, and a test asserts it across\nevery branch.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(specialized): carry statusCode on the typed 401/403 and 429 excep…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T17:44:25Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d088e865597781451660d7cb2fb9f2f27c7d10e8",
"body": "…ecisions\n\nThree places decided \"is this failure worth retrying against another provider\" —\nFallbackMiddleware, CircuitBreakerMiddleware and StreamingDispatcher — and each\nkept its own instanceof ladder. They had drifted, and none retried a 5xx: a\nprovider returning 500 repeatedly neither failed ove\n[…]\nTTP 429 instead of ServiceUnavailableException\n(both extend SpecializedServiceException, so a base-class catch is unaffected).\nSee ADR-095.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(provider)!: one failure taxonomy for retry and circuit-breaker d…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T17:30:20Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "026e15a59de600e74d96e0f15475e840e125f446",
"body": "…n than CI pins (#476)\n\nA PHPStan newer than the CI matrix resolves flags 9 `?->` calls as\n`nullsafe.neverNull` (redundant nullsafe on a proven-non-null receiver).\nThe CI PHPStan cannot prove those and needs the `?->`, so a `?->`→`->`\nswap would break the pinned build. This ignores the rule with\n`re\n[…]\n — a no-op on the CI version, a safety net when\nCI bumps PHPStan. Local `runTests.sh -s phpstan` now reports no errors.\n\nFollows the existing strict-rules ignore idiom in\n`Build/phpstan/phpstan.neon`.",
"is_bot": false,
"headline": "ci(phpstan): ignore nullsafe.neverNull flagged only by a newer PHPSta…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T16:53:43Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "078f8a35aa9fc4ca8904cc12c9174fa35d2ff007",
"body": "Broaden the ignore regex to `(method call|property access)` so a redundant\n`?->property` on a proven-non-null receiver is covered too, not only method\ncalls. reportUnmatched:false keeps it a no-op when neither shape is emitted.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "ci(phpstan): also match nullsafe property access in the neverNull ignore",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T16:43:31Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0ea2afcdf6bb16a4114b91232f3774061788b35f",
"body": "… flags it)\n\nA PHPStan newer than the version the CI matrix resolves narrows some `?->`\nreceivers to never-null — an enum resolved from a hard-coded pattern set, or a\nproperty already proven non-null by an outer `if ($x !== null)` — and reports\nthe nullsafe operator as redundant. The CI PHPStan cann\n[…]\ne version that does. Follows the existing strict-rules ignore idiom in the\nsame file. Local `runTests.sh -s phpstan` now reports no errors.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "ci(phpstan): ignore nullsafe.neverNull (newer PHPStan than the CI pin…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T16:27:59Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a5e11ad7a1c680f8cb5e6966edbe09f8fe6a5c96",
"body": "… state machine, tool egress classification (#474)\n\n## Description\n\nImplements the P0 (security & correctness) items of the `nr_llm` roadmap\non top of 0.23.0. Each of the seven commits is atomic, signed, and\nindependently green against the local suite (`unit`, `functional -d\nsqlite`, `phpstan`, `cgl\n[…]\n094,\nretention admin guide)\n- [x] My changes generate no new warnings\n\nNo version bump or CHANGELOG release section — versioning belongs to the\nseparate release flow; entries are under `[Unreleased]`.",
"is_bot": false,
"headline": "feat: P0 security & correctness — retention, session ownership, agent…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T16:14:34Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9a367a915ef3ff57aaf09733faa9af903be2028b",
"body": "…e builders\n\nThe guarded finishRun() and the concurrent-safe touch() built their SET clauses\nas ->set($col, (string)$value, false, PARAM_INT). With createNamedParameter\nfalse the value is a raw SQL expression and the type argument is not consulted,\nso the values went in as string literals. They are \n[…]\nr($value,\n$type), false), which binds each value as a typed named parameter. No behaviour\nchange; the columns and predicates are identical.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "refactor(db): bind typed SET values via createNamedParameter in updat…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T16:05:43Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ebe9058884b591076637b7019ee91b77c2905c9b",
"body": "The rag-egress endpoint tests used http:// URL literals, which SonarCloud flags\nas php:S5332 and counts against the new-code security rating. The scheme is\nincidental to those assertions — they exercise host/port matching, userinfo\nrejection and scope gating — so they move to https with no loss of m\n[…]\nt genuinely exercises http's port-80 defaulting builds the\nscheme from a variable, keeping that coverage without a scanner-flagged literal.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "test(tool): use https in egress test fixtures (Sonar S5332)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T15:45:26Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5c55148e31c843f4b88ba1f062ade36788d22a42",
"body": "Two cross-cutting controls hung on optional constructor parameters that\nshort-circuited to a no-op when absent. A control that silently disappears\nbecause a caller forgot to wire it is fail-open — the opposite of what a budget\ngate or an input guardrail is for.\n\n- AbstractSpecializedService::$budget\n[…]\n arguments stops\nconstructing. That is the point — the omission was the fail-open. Groundwork for\nthe modality-neutral lifecycle (ADR-095).\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "refactor(specialized)!: make the budget gate and input screener required",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T15:08:36Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1f55e93513d2e8b07398aaa17b31fdb4b6c0f0ab",
"body": "DeepL was the last paid external call with no budget pre-flight at all.\nADR-078 excluded it because its options carried no budget fields — an\nexplanation of the mechanism, not a justification: a call that spends money and\nthat no cap can stop is not defensible whatever the shape of its options.\nTran\n[…]\nh their own interface and screener.\n\nThe ADR-078 exclusion paragraph is amended rather than rewritten, so the\nchange of mind stays visible.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(specialized): close the two remaining budget bypasses",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T14:35:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "2ed572d920c8a94d3eedf624a76adba369dffb5f",
"body": "Tool safety rested on denylists — tables no tool may read, field names that\nlook like credentials — and every audit found another name nobody had thought\nof; six patches on one day widened those lists before 0.23.0. The lists were\nnot wrong, the shape was: enumerating the bad is a race against every\n[…]\nnstall offering zero tools buys nothing the\nceiling does not already buy.\n\nRequires a database compare and the upgrade wizard. See ADR-094.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(tool): classify tool data and declare provider trust zones",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T14:13:01Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d2a95ff243d9b67ac522a728112aff985cde2fe3",
"body": "Three places enforced tool policy and three disagreed with the documentation.\n\nThe per-configuration gate — the skills' declared allow-list intersected with\nallowed_tool_groups — lived in ToolPlaygroundController. That was defensible\nwhile the playground was the only entry point; it stopped being de\n[…]\n tools outside its configuration's\nallowed_tool_groups now receives fewer. The playground's observable behaviour is\nunchanged. See ADR-093.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(tool)!: enforce the tool gate in the loop and close two policy gaps",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T13:48:21Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0429475fb631dc0971a83639bb403d7bba4be885",
"body": "An agent run stored what state it was in and nothing about how it got there.\nA run stopped by the budget guard and one that exhausted its iteration cap are\nboth COMPLETED with truncated=true — the loop swallows the budget denial so the\npartial trace survives — so the stored row could not tell a cost\n[…]\nnal for now: the exception carries no resumable\nstate, so making it suspendable is its own change (see ADR-092 consequences).\n\nSee ADR-092.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(agent): record why a run ended and guard terminal transitions",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T13:29:36Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0902e9dfc277ff45204aac9a63276c0af8aae313",
"body": "ADR-083 promised that a conversation session is owned by a backend user. The\ncode never checked it: send() loaded the session by uuid and used it, so any\ncaller holding a uuid could read and continue another user's conversation —\nthrough a service that is published as public API.\n\nTwo further defect\n[…]\nt already produced colliding message rows\nmust resolve those duplicates before the database analyzer can add the unique\nindex. See ADR-091.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(session)!: enforce session ownership and configuration binding",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T12:59:27Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "981523634a910e810a7c1e5b4937a79b8ebc2d3e",
"body": "Agent runs and conversation sessions were the two content-bearing subsystems\noutside the central privacy model (ADR-064): `nrllm:privacy:purge` covered\nonly evaluation results, the skill audit and telemetry, `nrllm:session:purge`\ncarried its own hardcoded 30-day default, and `AgentRunRepository::pur\n[…]\n fails unless it is either purged or declared exempt with a reason, so a new\n content table cannot be added outside the policy unnoticed.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(privacy): extend retention to agent runs and conversation sessions",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T12:32:49Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "20ec140631600c5c4c2faa8085f086bdfdf56b91",
"body": "Release **0.23.0** — bumps `ext_emconf.php` + `Documentation/guides.xml`\nto 0.23.0 and adds the CHANGELOG section.\n\n## Ordering (important)\n\n**Merge #471 (`ToolLoopServiceInterface`) first.** The 0.23.0 CHANGELOG\nlists it, and the release tag must contain it. Before merging this PR I\nwill rebase `re\n[…]\n\n- Bilingual GitHub Pages docs site\n- Broad tool-egress hardening (FAL boundaries, egress denylist,\nuntrusted response guards)\n\nAfter merge: tag `main` HEAD `v0.23.0` (signed) → release.yml publishes.",
"is_bot": false,
"headline": "chore(release): 0.23.0 (#472)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T11:09:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0f6301884a26056872994c31d25181ad05e11ead",
"body": "Bump ext_emconf and guides.xml to 0.23.0 and record the release in CHANGELOG:\nguardrail pipeline, human-in-the-loop tool approval, persistent sessions and\nagent runs, structured outputs, typed provider exceptions, ToolLoopServiceInterface,\nand tool-egress hardening.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "chore(release): 0.23.0",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T10:57:14Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e7f268b5a6bcbce613a043312f3683f8671c647c",
"body": "…tool loop (#471)\n\n## What\n\n`ToolLoopService` is `final readonly` with **no interface**, so\ndownstream extensions that run the tool loop must depend on the concrete\nclass and cannot test-double it — they end up writing bespoke local\nadapter interfaces just to unit-test their controllers (t3x-cowrite\n[…]\nn\ncondition, not introduced here. CI is the authoritative gate.\n\nNo ADR added: this mirrors the existing service-interface extractions,\nwhich are not individually ADR'd. Happy to add one if preferred.",
"is_bot": false,
"headline": "feat(tool): extract ToolLoopServiceInterface for injectable/testable …",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T10:31:35Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f94424ffed563468af2a29e408e7ad2f0e235862",
"body": "Address review:\n- Services.yaml: the interface insertion had detached `public: true` from\n UsageTrackerServiceInterface; restore it and mark ToolLoopServiceInterface\n public too (downstream extensions inject the interface across the container).\n- Declare resume() (ADR-084 human-in-the-loop) on the\n[…]\n\n- Public-service count 32 -> 33 (Category B alias): update\n PublicServicesPolicyTest and record it in ADR-084 as the new count authority.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(tool): keep UsageTracker public, add resume() to the interface",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T10:15:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "643a6fd006d3b16e35bf2f51a2b82c302486b97f",
"body": "…the loop\n\nToolLoopService is final, so downstream extensions that run the tool loop had\nto depend on the concrete class and could not test-double it (they resorted to\nbespoke local adapter interfaces). Extract ToolLoopServiceInterface with the\nrunLoop() signature and alias it to ToolLoopService, mirroring\nLlmServiceManagerInterface / UsageTrackerServiceInterface. ToolLoopService stays\nfinal; consumers now depend on the interface.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(tool): extract ToolLoopServiceInterface so consumers can inject …",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T09:36:54Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "69ff589de020caaa28ce3cd8849d0d98eacd2763",
"body": "…nt, dev-example fixes (#470)\n\nImproves the GitHub Pages microsite (`landingpage/`) for SEO,\nGEO/answer-engine readiness, coding-agent and LLM support, developer\nguidance, and human browsing — driven by a six-lens audit of the built\nsite and the codebase.\n\n## New: Governance & Security page\nBilingua\n[…]\nocks scroll (overflow-x); no horizontal page\noverflow on mobile.\n- ADR-089/090 render with summaries, correct groups, and single titles.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)",
"is_bot": false,
"headline": "feat(landingpage): governance & security page, GEO/LLM & SEO enrichme…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T21:07:12Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "60459f480beb5195942cb13daefdea180d5f6e76",
"body": "- Make the ADR count in the governance page dynamic: security.json/_de use an\n {ADR_COUNT} placeholder that build.py substitutes with len(adrs), instead of\n a hardcoded literal. The rendered count is 89 (ADR files number 1-90 with one\n gap), not 90.\n- German copy corrections in security_de.json: \n[…]\nchrittene Limit' (drop colloquialism),\n plural agreement in the data-residency clause, and 'um personenbezogene\n Felder bereinigt'.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(landingpage): address review feedback on the security page",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T20:35:55Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "225cabab829d95f22be42c59f4e89764bde9e40a",
"body": "…ev examples\n\nAdd a bilingual Governance & Security page (/en/security/, /de/security/)\ncovering key management, access control, guardrails, human-in-the-loop\napproval, tool safety, budgets, data residency, supply-chain integrity, and\nlicensing — sourced from ADRs 012/023/025/043/044/084-089. Wired \n[…]\nrnance).\n\nHuman browsing: add an on-page table of contents to the landing page, and a\nfooter link to the security page on every page.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "feat(landingpage): governance page, GEO/LLM enrichment, SEO schema, d…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T20:25:55Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "2ff229ccf6125ba6ae44ecd9800db6153ba44299",
"body": "adr.json metadata stopped at ADR-088, so the two newest ADRs rendered on\nthe site with empty summaries, fell into the fallback \"Other\" group, and\nshowed doubled titles (\"ADR 89: ADR-089: …\") because build.py prepends\n\"ADR {n}:\" to the RST doctitle. Add curated entries (with the leading\n\"ADR-089:\"/\"ADR-090:\" dropped) and assign 89 to Guardrails & Safety and\n90 to Core Services & Architecture.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(landingpage): add ADR-089/090 metadata and group assignment",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T20:25:43Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0bc35205a8b2cc38fe3213dee0ad61113e33169c",
"body": "…seams (#468)\n\nnr_llm bundles several independently useful subsystems — provider core,\nspecialized services (translation/image/speech), the tool & agent\nsystem, the guardrail safety layer, and the backend UI. Different\nconsumers want different subsets, which makes a future split into\nfocused extensi\n[…]\ndy; phpat architecture\ntests enforce the boundaries).\n- **README** \"Packaging: one extension for now\" section summarizing the\nsame, with the seam table, linking to the ADR.\n\nDocs-only; no code change.",
"is_bot": false,
"headline": "docs: record single-extension-until-1.0 decision (ADR-090) and split …",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T10:40:34Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "68a28a5ea13c5e19927ecc5b2554cca59df8cd4e",
"body": "…nts, var/log tokens, site-config DSN) (#469)\n\nThe three follow-ups deferred from the tool data-exfiltration audit\n(#467). Each\nverified against the code, with a regression test.\n\n- **probe_url — inactive baseVariants no longer trusted.**\n`EgressPolicyService`\n folded EVERY site `baseVariants[*].ba\n[…]\n (sqlite), phpstan (L10), cgl, rector,\nfuzzy,\narchitecture.\n\nResidual (documented): a pure ALLCAPS-concatenated key (`SECRETKEY`, no\nword\nboundary) relies on the value scrub rather than the key match.",
"is_bot": false,
"headline": "security: close remaining tool egress follow-ups (probe_url baseVaria…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T10:38:17Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0a6d257597649f2ea1e6cc27b2b202d005781978",
"body": "…seams\n\nnr_llm bundles several independently useful subsystems (provider core,\nspecialized services, tools/agent, guardrail, backend UI). Document why it stays\none extension during rapid pre-1.0 development and the anticipated seams for a\nsplit with/before 1.0: ADR-090 plus a \"Packaging\" section in the README.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "docs: record single-extension-until-1.0 decision (ADR-090) and split …",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T10:26:16Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0a4687d04cec8fb88f77f7b6d77f1abdbac022f3",
"body": "Bot review: a preg_replace failure returned null, which the (string) cast turned\ninto '' — bypassing the credential-key check. Fall back to the raw key (still\nchecked) on failure instead.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix: null-guard the site-config key normalization",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T10:26:13Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "42d1c7729e1c110d6075eb5106512af3b01c2015",
"body": "…nts, var/log tokens, site-config DSN)\n\nFollow-ups deferred from the tool data-exfiltration audit (#467):\n\n- probe_url egress allowlist folded in EVERY site baseVariant unconditionally,\n so an inactive local/staging variant on an internal host (http://web:80,\n staging.internal:8080) became a probe\n[…]\nnted): a pure ALLCAPS-concatenated key spelling (SECRETKEY) with\nno word boundary still relies on the value scrub, not the key match.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "security: close remaining tool egress follow-ups (probe_url baseVaria…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T10:25:20Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "11b776c2662e268f52841a3930f5c9035bd40a52",
"body": "….0 (#435)\n\nThis PR contains the following updates:\n\n| Package | Change |\n[Age](https://docs.renovatebot.com/merge-confidence/) |\n[Confidence](https://docs.renovatebot.com/merge-confidence/) |\n|---|---|---|---|\n|\n[netresearch/nr-vault](https://redirect.github.com/netresearch/t3x-nr-vault)\n| `^0.10.1\n[…]\ng](https://developer.mend.io/github/netresearch/t3x-nr-llm).\n\n<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNjUuMSIsInVwZGF0ZWRJblZlciI6IjQzLjI2NS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->",
"is_bot": false,
"headline": "fix(deps): update dependency netresearch/nr-vault to ^0.10.1 || ^0.11…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T10:12:27Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ba42448e849e2445479e1497fbdff453d20ff72c",
"body": "…nfig, legacy config file, encryptionKey, DSN) (#467)\n\nFrom an adversarial data-exfiltration audit of the read-only tools (SSRF\n/ path\ntraversal / DB-RBAC / secret-egress). The tool protections are\nname-based\ndenylists (the #463 era); this closes the next layer of gaps a read tool\ncould\nuse to exfil\n[…]\nthe line redaction is\nkeyword+assignment-shaped, so\n token-shaped secrets (Bearer/JSON headers) in logs egress (low).\n- `get_site_config` redaction is casing-dependent (camelCase-only\nnormalization).",
"is_bot": false,
"headline": "security: close tool secret-egress denylist gaps (vault table, FAL co…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T09:35:52Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5a639b91280529d4529beaf537dd31dedc0785a5",
"body": "…l keys\n\nBot review: align SourcePathGuard SECRET_LINE_PATTERN with the config readers\n(access/license key, authorization, dsn) so source-file reads redact the same\nshapes; add camelCase accesskey/licensekey/encryptionkey to the record field\ndenylist (the segment pattern's bare 'key' misses the un-delimited forms).\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "security: extend line + field secret patterns for camelCase credentia…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T09:25:18Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0ab2a11c6620b1b754dadd655a077b0e6ec51c8e",
"body": "…nfig, legacy config file, encryptionKey, DSN)\n\nFrom an adversarial data-exfiltration audit of the read-only tools; each gap was\nverified against the code. The protections are name-based denylists (#463 era);\nthese are the next layer of gaps a read tool could exfiltrate to an LLM provider:\n\n- The nr\n[…]\nount sub-path; probe_url trusts inactive\nsite baseVariants; var/log token-shaped secrets; get_site_config casing-dependent\nkey match.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "security: close tool secret-egress denylist gaps (vault table, FAL co…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T09:24:06Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "85a72e7fd1a7738669ea497896618964be71e6a3",
"body": null,
"is_bot": true,
"headline": "fix(deps): update dependency netresearch/nr-vault to ^0.10.1 || ^0.11.0",
"author_name": "renovate[bot]",
"author_login": "renovate[bot]",
"committed_at": "2026-07-19T09:20:19Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "25fc4c22e90e717ff87a91996f58b7f9f35ca607",
"body": "From the ultracode tool-execution security audit: `get_page_content` and\n`read_records` never call `$user->checkLanguageAccess()`, so a non-admin\nrestricted to certain backend languages could read content in languages\nthey are not permitted. Since tool results egress to the external LLM\nprovider, th\n[…]\ne gate fired rather than an empty result).\n\n## Gates\ncgl, PHPStan level 10, unit (5287), Rector `-n`, fuzzy — green; the two\ntool functional files green locally (11 tests). Full functional runs in\nCI.",
"is_bot": false,
"headline": "fix(tool): enforce backend language access in read tools (#466)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T08:34:07Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "fc570b1ab6948f66acd486f5f4a16107ad7a5a83",
"body": "From the ultracode tool-execution security audit: the FAL tools gate\naccess at **storage** granularity only, not **file-mount**.\n`FalStorageGate::effectiveStorages()` returns the whole storage uid for\na non-admin even when their file mount is just a subfolder, so\n`search_fal_files` / `find_missing_f\n[…]\nsts still pass (no\nregression).\n\n## Gates\ncgl, PHPStan level 10, unit (5279), Rector `-n`, fuzzy — green; FAL tool\nfunctional tests + the new file-mount test green locally. Full\nfunctional runs in CI.",
"is_bot": false,
"headline": "fix(tool): enforce file-mount boundaries in FAL tools (#464)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T08:29:04Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "77142e00a37d53d93d68747c02f3acf785c088d1",
"body": "The initial language guard only covered an explicit where_equals on\nsys_language_uid; a non-admin reading a language-aware table WITHOUT a language\nfilter still received rows in languages they may not access. For non-admins,\nfetch the table's language column (even when not displayed) and drop rows whose\nsys_language_uid the acting user cannot access, alongside the existing\npage-permission filter.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(tool): drop forbidden-language rows in read_records without a filter",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T08:23:24Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d8d1849f89b71e06c362d30dd3f31f9a524185b4",
"body": "Address review of the file-mount gate:\n- search_fal_files / find_missing_files applied the DB LIMIT before the PHP\n mount filter, so a non-admin whose result window was dominated by out-of-mount\n rows could get fewer than `limit` (or zero) in-mount hits. Non-admins now scan\n up to MOUNT_SCAN_CAP \n[…]\nrior\n evaluatePermissions after the check, so the mutation of the request-shared\n ResourceStorage does not leak to other consumers.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(tool): bound FAL mount-scan window and restore evaluatePermissions",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T08:18:36Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "93cbbb0c7c3719e97ced97e35d59d4834035ef4c",
"body": "…sliding window) (#465)\n\nCloses the last deferred finding from the guardrail audit: the >50 KB\nstreaming\nredaction overflow leak (ADR-088), designed via a 3-approach panel and\nverified\nby a randomised property test.\n\n## The bug\nLive stream redaction (ADR-088) re-redacted the whole raw buffer each\nch\n[…]\nwindow class.\n\n## Residual (documented)\nA credential whose anchor alone exceeds the 128-byte holdback and\nstraddles the\nfinal boundary can still partially leak (pre-existing ADR-088 limit,\nunchanged).",
"is_bot": false,
"headline": "security: close the streaming-redaction >50KB overflow leak (bounded …",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T08:08:31Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1fefceef42ed8b77ef2c2566401c9073ae25d9f9",
"body": "…il-closed verdicts, vision screening) (#462)\n\nFixes the guardrail-enforcement findings deferred from #459 (the\nredaction-pattern\nPR). Each was verified against the code; the middleware-ordering fix was\ndesigned\nvia a 3-approach panel and adversarially checked before implementing.\n\n## No unredacted \n[…]\nit is a separate,\ncareful\nfollow-up (with its own adversarial review) rather than a drive-by\nchange here.\n\nLocal gate green: unit, functional (sqlite), phpstan (L10), cgl, rector,\nfuzzy,\narchitecture.",
"is_bot": false,
"headline": "security: harden guardrail enforcement (no unredacted persistence, fa…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T08:08:29Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ac3c2ef226e11c31b818beb3bb5078583c09a6e0",
"body": "FalStorageGate gated FAL access at storage granularity only. A non-admin whose\nfile mount is a subfolder still has the whole storage in effectiveStorages(),\nso search_fal_files / find_missing_files enumerated file names and\nstorage-relative paths across the entire storage, and get_fal_references lis\n[…]\nadmin editor sees the in-mount file and missing\nentry, never the out-of-mount ones, and is denied references to an out-of-mount\nfile.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(tool): enforce file-mount boundaries in FAL tools",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T08:05:22Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d8b5736bb22c2869002a1486781a89e7b37954f6",
"body": "get_page_content and read_records never checked the acting user's backend\nlanguage access, so a non-admin restricted to certain languages could read\ncontent in languages they are not permitted — the language restriction was a\nno-op against these tools, whose results egress to the external LLM provid\n[…]\nbidden language outright; read_records denies an explicit where_equals\nfilter on a forbidden sys_language_uid. Admins are unaffected.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(tool): enforce backend language access in read tools",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T07:58:41Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d85cc4d9f4b100914d7266ef94fced25e9110b66",
"body": "…ens, full Bearer masking) (#459)\n\nFrom an adversarial audit of the guardrail / HITL / redaction suite\n(ADR-084…088).\nFive reviewers each attacked one property; every finding was verified\nagainst the\ncode before acting. This PR fixes the clear, testable redaction bugs;\nthe rest are\ndocumented below \n[…]\nforced\nby a conditional claim. RETRY is capped at one extra call;\nexception/telemetry\nsurfaces carry no content; the streaming holdback correctly masks\nboundary-split\nsecrets for the shipped patterns.",
"is_bot": false,
"headline": "security: harden secret redaction (connection strings, JWT/vendor tok…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T07:58:00Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ef0d57faeec938acfbcf2c187ea4c98ec39e48bb",
"body": "…rward\n\nAddress review feedback: the property-test redactor closure now null-guards each\npreg_replace (mirroring the production RedactsSecretsTrait) instead of a bare\n(string) cast; utf8SafeForward gains the >= length short-circuit for defensive\nsymmetry with utf8SafeBack (the while already bounds the access, so no behaviour\nchange).\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "test: null-guard the test redactor closure; boundary-guard utf8SafeFo…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T07:56:02Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "32091d04e2971337643af339dbe4c4a1b4c98e16",
"body": "A vision call cannot be re-requested through the pipeline, so screenVision()\ntreated a RETRY verdict as a silent pass — returning the response the guardrail\ndeemed deficient. Throw GuardrailViolationException instead (fail closed),\nconsistent with the completion path where an unsatisfiable RETRY also throws.\nNo shipped guardrail returns RETRY; this hardens the path for a future one.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "security: fail closed on a RETRY verdict for a vision response",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T07:55:55Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "5814ae568329ff95a09b847ef3769612ba634748",
"body": "…r auth.json) (#463)\n\nTwo secret-egress findings from the ultracode tool-execution security\naudit. Tool results egress verbatim to the external LLM provider, so a\nsecret that slips past the denylist leaks off-site.\n\n## Bug A — concatenated credential column names bypass the field\ndenylist\n`TableRead\n[…]\nAL tools gating by\nstorage rather than file-mount (3 tools) and a backend-language-access\ncheck — separate PRs, deferred until the workspace-restriction PR (#461)\nlands (they touch overlapping files).",
"is_bot": false,
"headline": "fix(tool): harden secret-egress denylist (credential columns, Compose…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T07:14:47Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "15b6e7fb3d543cb90013006da3830fbd76afabc2",
"body": "Live stream redaction re-redacted the whole raw buffer each chunk and, past a\n50 KB cap, flushed and passed EVERY later chunk through RAW — leaking a secret\npositioned past the cap in a long completion (a long document/code response, or\na prompt-injected model placing it there). The unbounded buffer\n[…]\nassthrough now asserts a past-cap secret is masked.\nADR-088 amended; the now-dead HOLDBACK_BYTES/utf8SafeLength move into the window.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "security: close the streaming-redaction overflow leak (ADR-088)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T07:13:32Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9c12a2a64b8c86abb7c3f1388852f2db7ab92594",
"body": "Follow-up to the credential-column hardening: instead of enumerating digit\nsuffixes, let the segment pattern tolerate a trailing digit run so token2 /\nsecret2 / key2 (confirm-style columns) are all caught, and add the one missing\nconcatenated noun (apitoken) to the substring list. Adversarial review flagged\nthese as bypassing both patterns.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(tool): close digit-suffix secret columns generically + apitoken",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T06:51:18Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6e804ee7ac275f07df2badfd5682ce1692def265",
"body": "SourcePathGuard did not deny auth.json, which holds Composer registry/OAuth\ndeploy tokens, so read_source could surface it to the LLM provider. Add it to\nthe denied-basename pattern (at any depth); a similarly named non-credential\nfile (author.json) stays readable.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(tool): deny Composer auth.json in read_source",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T06:45:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e6124a7825aaec080b578cfd593ff91f312bbeed",
"body": "TableReadAccessService::isSensitiveField() anchored keywords to underscore\nsegments, so concatenated/camelCase/digit-suffixed secret columns (apikey,\naccessToken, password2, clientSecret) bypassed the denylist and their values\ncould egress to the LLM provider. Add a boundary-free pattern for unambig\n[…]\ny (password, apikey, accesstoken, credential, ...), keeping\nbare secret/token out of it so secretary/tokenizer are not false-flagged.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(tool): catch concatenated credential column names in egress denylist",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T06:45:32Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "43c99d4e3cb91a60e8b6f6dba56b975e42e1d0a9",
"body": "An ultracode security audit of the LLM **tool-execution** system (5\nlenses → adversarial verify, 13 confirmed of 14) found that the\nrecord/content/tree read tools query the database with default\nrestrictions only, which do **not** exclude workspace draft/versioned\nrows. Because a tool result is sent\n[…]\nrther tool findings handled separately (FAL tools\ngating by storage rather than file-mount; secret-egress denylist gaps; a\nlanguage-access check) — each a distinct concern with its own review\nsurface.",
"is_bot": false,
"headline": "fix(tool): exclude workspace drafts from LLM read tools (#461)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T06:39:14Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1a98d7a8031529f5de86a00f3a04c14fe888aa5d",
"body": "GetFalReferencesTool reads sys_file_reference (a workspace-aware table) with\nremoveAll() restrictions, so unpublished draft references leaked to the LLM\nprovider — the same class as the read-tool fix, in a tool the first commit\nmissed. Re-add a live-workspace WorkspaceRestriction after removeAll() (deleted\nstays filtered by the explicit where; hidden is still surfaced intentionally).\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(tool): exclude workspace draft references from get_fal_references",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T06:26:26Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0245e3cddd84da400beb4f96b9e1e1a98f71b0d2",
"body": "GuardrailMiddleware only screened CompletionResponse, so a VisionResponse — whose\ndescription is model-generated, untrusted text — passed through unscreened. A\nvision model transcribing an image that contains a secret (a screenshot of an\n.env file, an API key) returned it verbatim.\n\nScreen the descr\n[…]\n the pipeline here). A clean vision response is returned\nunchanged; embeddings and raw-array operations still pass through untouched.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "security: screen vision responses through the output guardrails",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T06:19:14Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "61ab69eb34ccb13da6975b8d97d054341903a780",
"body": "The record/content/tree read tools (search_records, read_records,\nget_page_content, get_pagetree) queried with default restrictions only, which\ndo not exclude workspace draft/versioned rows. A tool result is sent verbatim\nto an external LLM provider, so unpublished workspace content leaked off-site.\n[…]\nver executes, so it now registers a stub TcaSchemaFactory (pulled by the\nWorkspaceRestriction constructor) and purges it on teardown.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(tool): exclude workspace drafts from LLM read tools",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T06:18:38Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c16782390bc60513785d76ab65f1868866538a0d",
"body": "…dacted persistence)\n\nTwo enforcement gaps from the guardrail/HITL adversarial audit:\n\nFail-open verdict handling: the output (GuardrailMiddleware) and input\n(InputGuardrailScreener) verdict switches had no default arm, so a future\nGuardrailVerdict added without updating them would silently pass the\n[…]\nhe new resolved order; TelemetryMiddleware unit tests cover the\npolicy-exception-as-success classification. ADR-085 amended in place.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "security: harden guardrail enforcement (fail-closed verdicts, no unre…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T06:16:56Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f5b16bdfb066811ee4639d7e6e93d47785d71c6f",
"body": "… token store) (#460)\n\nAn ultracode audit of un-audited subsystems (find → adversarial verify,\n5 lenses) surfaced four confirmed defects at untrusted boundaries. Three\nfalse-positives were refuted (two SSRF-shaped ones turned out already\nguarded). Each fix is independently committed.\n\n## Bug A — Dee\n[…]\n state (`ModelRepository::countByProvider()`\nexplicitly counts it). Adding a runtime `hidden` guard would revert that\nintentional behavior, so it is **not** changed here. Left out of this PR\nentirely.",
"is_bot": false,
"headline": "fix: harden untrusted-boundary handling (translation, image, retries,…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T05:53:12Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1b3417a36a2ec967ba98d2c0eb5e929c253b2d15",
"body": "…ller\n\nsetTokenAction called vault->store() and the following persistence without a\ntry/catch, so a vault or encryption failure produced a raw HTML 500 the\nbackend module cannot render. Wrap the store + persist in a try/catch that\nlogs and returns a JSON 500, mirroring SetupWizardController. Logger is\ninjected as an optional trailing constructor argument.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(backend): return JSON on token-store failure in SkillSourceContro…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T05:40:01Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "36f977780f9abfde5b9e5e4c1d59818a408a92f7",
"body": "configure() only lower-clamped maxRetries, so a large value from the free-text\noptions-JSON override produced an effectively unbounded retry loop against a\ndown upstream, pinning the request worker. Clamp to [0, MAX_RETRIES_LIMIT]\n(10, matching the max_retries TCA range) in configure(), and re-clamp in the\nsend loop as a defense against other set paths.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(provider): clamp maxRetries to the TCA upper bound",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T05:40:01Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1617cb06d5a47587f9e153b562f17b14cfeebd1f",
"body": "A `data[]` element that is a scalar (or carries a non-string url/b64_json)\nmade the generate/generateMultiple/createVariations/edit paths read an array\noffset on a non-array and crash with a TypeError. Read each field through\nimageString(), degrading a malformed element to an empty result instead of a\n500.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(image): type-check untrusted DALL-E response elements",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T05:40:01Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ddd33c9444a5e21a52e7f503943b947cb15a569f",
"body": "A malformed 2xx body — a non-array `translations` container, or an entry\nthat is not an object or lacks a string `text` — made DeepLTranslator read\n`$translation['text']` and crash with an uncaught TypeError (a raw 500 the\ncaller cannot handle). Validate the container and each entry via\nextractTrans\n[…]\nt()/extractDetectedSourceLanguage() and surface a typed\nServiceUnavailableException instead. Covers translate() and translateBatch().\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "fix(translation): guard untrusted DeepL response shape",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T05:40:01Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "38a85f9e60d2f074bb74cad317c9bcbf36dc2302",
"body": "The secret guardrails (ADR-085/087) and ContentRedactor share\nErrorMessageSanitizerTrait/RedactsSecretsTrait for best-effort masking. Three\ngaps let real secrets through:\n\n- Connection-string passwords (scheme://user:password@host) were never masked —\n only URL query parameters were. SecretRedactio\n[…]\n\nEach preg_replace stays individually null-guarded so a backtrack-limit failure\nkeeps the last good string instead of wiping content.\n\nSigned-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>",
"is_bot": false,
"headline": "security: harden secret redaction against more secret shapes",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-19T05:33:27Z",
"body_truncated": true,
"is_coding_agent": false
}
],
"releases_count": 43,
"commits_last_year": 1750,
"latest_release_at": "2026-07-20T11:22:07Z",
"latest_release_tag": "v0.23.0",
"releases_from_tags": false,
"days_since_last_push": 0,
"active_weeks_last_year": 29,
"days_since_latest_release": 1,
"mean_days_between_releases": 1.2
},
"community": {
"has_readme": true,
"has_license": true,
"has_description": true,
"has_contributing": true,
"health_percentage": 100,
"has_issue_template": false,
"has_code_of_conduct": true,
"has_pull_request_template": true
},
"ecosystem": {
"packages": [
{
"name": "netresearch/nr-llm",
"exists": true,
"license": "GPL-2.0-or-later",
"keywords": [
"translation",
"extension",
"streaming",
"typo3",
"ai",
"chatbot",
"Gemini",
"openai",
"llm",
"embeddings",
"claude",
"ollama",
"provider-abstraction"
],
"ecosystem": "packagist",
"matches_repo": true,
"registry_url": "https://packagist.org/packages/netresearch/nr-llm",
"is_deprecated": false,
"latest_version": "v0.23.0",
"repository_url": "https://github.com/netresearch/t3x-nr-llm",
"versions_count": 43,
"total_downloads": 7919,
"dependents_count": 1,
"deprecation_note": null,
"maintainers_count": null,
"monthly_downloads": 2421,
"first_published_at": null,
"latest_published_at": "2026-07-20T11:09:25Z",
"latest_version_yanked": null,
"days_since_latest_publish": 1
}
]
},
"popularity": {
"forks": 1,
"stars": 4,
"watchers": 1,
"fork_history": {
"days": [
{
"date": "2026-02-23",
"count": 1
}
],
"complete": true,
"collected": 1,
"total_forks": 1
},
"star_history": {
"days": [
{
"date": "2026-01-05",
"count": 1
},
{
"date": "2026-03-19",
"count": 1
},
{
"date": "2026-04-23",
"count": 1
},
{
"date": "2026-07-10",
"count": 1
}
],
"complete": true,
"collected": 4,
"total_stars": 4
},
"open_issues_and_prs": 3
},
"ai_readiness": {
"has_nix": false,
"example_dirs": [],
"has_llms_txt": false,
"has_dockerfile": true,
"has_mcp_signal": false,
"bootstrap_files": [
"Makefile"
],
"api_schema_files": [],
"has_devcontainer": false,
"typecheck_configs": [],
"toolchain_manifests": [],
"largest_source_bytes": 208619,
"source_files_sampled": 1032,
"oversized_source_files": 20,
"agent_instruction_files": [
".ddev/AGENTS.md",
".ddev/CLAUDE.md",
".ddev/GEMINI.md",
".github/workflows/AGENTS.md",
".github/workflows/CLAUDE.md",
".github/workflows/GEMINI.md",
"AGENTS.md",
"CLAUDE.md",
"Classes/AGENTS.md",
"Classes/CLAUDE.md",
"Classes/GEMINI.md",
"Configuration/AGENTS.md",
"Configuration/CLAUDE.md",
"Configuration/GEMINI.md",
"Documentation/AGENTS.md",
"GEMINI.md",
"Resources/AGENTS.md",
"Resources/CLAUDE.md",
"Resources/GEMINI.md",
"Tests/AGENTS.md",
"Tests/CLAUDE.md",
"Tests/GEMINI.md"
],
"agent_instruction_max_bytes": 13459
},
"dependencies": {
"manifests": [
"composer.json",
"package.json"
],
"advisories": {
"error": null,
"scope": "repository_graph",
"source": "osv",
"findings": [],
"collected": true,
"malicious": [],
"truncated": false,
"by_severity": {},
"advisory_count": 0,
"affected_count": 0,
"assessed_count": 10,
"malicious_count": 0,
"assessed_package": null,
"unassessed_count": 15,
"direct_affected_count": 0
},
"ecosystems": [
"npm",
"packagist"
],
"dependencies": [
{
"name": "netresearch/nr-vault",
"manifest": "composer.json",
"ecosystem": "packagist",
"version_constraint": "^0.10.1 || ^0.11.0"
},
{
"name": "psr/http-client",
"manifest": "composer.json",
"ecosystem": "packagist",
"version_constraint": "^1.0"
},
{
"name": "psr/http-factory",
"manifest": "composer.json",
"ecosystem": "packagist",
"version_constraint": "^1.0"
},
{
"name": "psr/log",
"manifest": "composer.json",
"ecosystem": "packagist",
"version_constraint": "^2.0 || ^3.0"
},
{
"name": "symfony/yaml",
"manifest": "composer.json",
"ecosystem": "packagist",
"version_constraint": "^6.4 || ^7.0 || ^8.0"
},
{
"name": "typo3/cms-core",
"manifest": "composer.json",
"ecosystem": "packagist",
"version_constraint": "^13.4 || ^14.3"
}
],
"all_dependencies": {
"error": null,
"source": "github-sbom",
"packages": [
{
"name": "netresearch/nr-vault",
"direct": true,
"version": null,
"ecosystem": "packagist"
},
{
"name": "psr/http-client",
"direct": true,
"version": null,
"ecosystem": "packagist"
},
{
"name": "psr/http-factory",
"direct": true,
"version": null,
"ecosystem": "packagist"
},
{
"name": "psr/log",
"direct": true,
"version": null,
"ecosystem": "packagist"
},
{
"name": "symfony/yaml",
"direct": true,
"version": null,
"ecosystem": "packagist"
},
{
"name": "typo3/cms-core",
"direct": true,
"version": null,
"ecosystem": "packagist"
},
{
"name": "@axe-core/playwright",
"direct": false,
"version": "4.12.1",
"ecosystem": "npm"
},
{
"name": "@playwright/test",
"direct": false,
"version": "1.61.1",
"ecosystem": "npm"
},
{
"name": "@types/node",
"direct": false,
"version": "24.13.3",
"ecosystem": "npm"
},
{
"name": "axe-core",
"direct": false,
"version": "4.12.1",
"ecosystem": "npm"
},
{
"name": "fsevents",
"direct": false,
"version": "2.3.2",
"ecosystem": "npm"
},
{
"name": "playwright",
"direct": false,
"version": "1.61.1",
"ecosystem": "npm"
},
{
"name": "playwright-core",
"direct": false,
"version": "1.61.1",
"ecosystem": "npm"
},
{
"name": "undici-types",
"direct": false,
"version": "7.18.2",
"ecosystem": "npm"
},
{
"name": "ergebnis/composer-normalize",
"direct": false,
"version": null,
"ecosystem": "packagist"
},
{
"name": "fakerphp/faker",
"direct": false,
"version": null,
"ecosystem": "packagist"
},
{
"name": "giorgiosironi/eris",
"direct": false,
"version": null,
"ecosystem": "packagist"
},
{
"name": "netresearch/typo3-ci-workflows",
"direct": false,
"version": null,
"ecosystem": "packagist"
},
{
"name": "php",
"direct": false,
"version": null,
"ecosystem": "packagist"
},
{
"name": "tpwd/ke_search",
"direct": false,
"version": null,
"ecosystem": "packagist"
},
{
"name": "typo3/cms-dashboard",
"direct": false,
"version": null,
"ecosystem": "packagist"
},
{
"name": "typo3/cms-indexed-search",
"direct": false,
"version": null,
"ecosystem": "packagist"
},
{
"name": "typo3/cms-install",
"direct": false,
"version": null,
"ecosystem": "packagist"
},
{
"name": "sentence-transformers",
"direct": false,
"version": "5.6.0",
"ecosystem": "pypi"
},
{
"name": "torch",
"direct": false,
"version": "2.13.0",
"ecosystem": "pypi"
}
],
"collected": true,
"truncated": false,
"total_count": 25,
"direct_count": 6,
"indirect_count": 19
}
},
"maintainership": {
"issues": {
"open_prs": 2,
"merged_prs": 443,
"open_issues": 1,
"closed_ratio": 0.964,
"closed_issues": 27,
"closed_unmerged_prs": 16
},
"bus_factor": 1,
"bot_contributors": 3,
"top_contributors": [
{
"type": "User",
"login": "CybotTM",
"commits": 1646,
"avatar_url": "https://avatars.githubusercontent.com/u/326348?v=4"
},
{
"type": "User",
"login": "just-tobi",
"commits": 8,
"avatar_url": "https://avatars.githubusercontent.com/u/5242689?v=4"
}
],
"contributors_sampled": 2,
"top_contributor_share": 0.995
},
"quality_signals": {
"has_ci": true,
"has_tests": true,
"ci_workflows": [
"auto-merge-deps.yml",
"check-template-drift.yml",
"checks.yml",
"ci.yml",
"community.yml",
"docs.yml",
"e2e.yml",
"pages.yml",
"release.yml",
"republish.yml"
],
"has_docs_dir": true,
"linter_configs": [
".php-cs-fixer.dist.php",
"phpstan.neon"
],
"has_editorconfig": true,
"has_linter_config": true,
"has_precommit_config": false
},
"security_signals": {
"lockfiles": [
"package-lock.json"
],
"scorecard": {
"checks": [
{
"name": "Binary-Artifacts",
"score": 10,
"reason": "no binaries found in the repo",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
},
{
"name": "Branch-Protection",
"score": 3,
"reason": "branch protection is not maximal on development and all release branches",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
},
{
"name": "CI-Tests",
"score": 10,
"reason": "11 out of 11 merged PRs checked by a CI test -- score normalized to 10",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
},
{
"name": "CII-Best-Practices",
"score": 7,
"reason": "badge detected: Silver",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
},
{
"name": "Code-Review",
"score": 10,
"reason": "all changesets reviewed",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
},
{
"name": "Contributors",
"score": 10,
"reason": "project has 3 contributing companies or organizations -- score normalized to 10",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
},
{
"name": "Dangerous-Workflow",
"score": null,
"reason": "no workflows found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
},
{
"name": "Dependency-Update-Tool",
"score": 10,
"reason": "update tool detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
},
{
"name": "Fuzzing",
"score": 0,
"reason": "project is not fuzzed",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
},
{
"name": "License",
"score": 10,
"reason": "license file detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
},
{
"name": "Maintained",
"score": 10,
"reason": "30 commit(s) and 22 issue activity found in the last 90 days -- score normalized to 10",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
},
{
"name": "Packaging",
"score": null,
"reason": "packaging workflow not detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
},
{
"name": "Pinned-Dependencies",
"score": null,
"reason": "no dependencies found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
},
{
"name": "SAST",
"score": 10,
"reason": "SAST tool is run on all commits",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
},
{
"name": "Security-Policy",
"score": 10,
"reason": "security policy file detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
},
{
"name": "Signed-Releases",
"score": 8,
"reason": "5 out of the last 5 releases have a total of 5 signed artifacts.",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
},
{
"name": "Token-Permissions",
"score": null,
"reason": "No tokens found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
},
{
"name": "Vulnerabilities",
"score": 10,
"reason": "0 existing vulnerabilities detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
}
],
"commit": "28ec3e2c5d66a08a075fc92d851837b2b0958898",
"ran_at": "2026-07-22T06:09:21Z",
"aggregate_score": 8.4,
"scorecard_version": "v5.5.0"
},
"has_codeql_workflow": false,
"has_security_policy": true,
"has_dependabot_config": true
},
"contribution_flow": {
"collected": true,
"ci_last_run_at": "2026-07-22T06:07:45Z",
"oldest_open_prs": [
{
"number": 475,
"created_at": "2026-07-20T16:25:41Z",
"last_comment_at": "2026-07-20T16:28:30Z",
"last_comment_author": "codecov"
},
{
"number": 490,
"created_at": "2026-07-21T16:04:20Z",
"last_comment_at": "2026-07-21T16:07:16Z",
"last_comment_author": "codecov"
}
],
"last_merged_pr_at": "2026-07-22T06:07:43Z",
"ci_last_conclusion": null,
"oldest_open_issues": [
{
"number": 80,
"created_at": "2026-03-01T17:14:24Z",
"last_comment_at": "2026-03-01T17:18:18Z",
"last_comment_author": "github-actions"
}
]
}
},
"config": {
"disabled_metrics": [],
"disabled_categories": [],
"disabled_components": {}
},
"source": {
"url": "https://github.com/netresearch/t3x-nr-llm",
"host": "github.com",
"name": "t3x-nr-llm",
"owner": "netresearch"
},
"metrics": {
"overall": {
"key": "overall",
"band": "good",
"name": "Overall health",
"note": null,
"notes": [],
"value": 76,
"inputs": {
"security": 87,
"vitality": 90,
"community": 47,
"governance": 68,
"engineering": 90
},
"components": []
},
"categories": [
{
"key": "vitality",
"band": "excellent",
"name": "Vitality",
"value": 90,
"weight": 0.22,
"metrics": [
{
"key": "development_activity",
"band": "good",
"name": "Development activity",
"note": null,
"notes": [],
"value": 84,
"inputs": {
"commits_last_year": 1750,
"human_commit_share": 0.99,
"days_since_last_push": 0,
"active_weeks_last_year": 29
},
"components": [
{
"key": "push_recency",
"name": "Push recency",
"detail": "last push 0 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "push_recency",
"params": {
"days": 0
}
}
],
"max_points": 36
},
{
"key": "commit_cadence",
"name": "Commit cadence",
"detail": "29/52 weeks with commits",
"points": 20.1,
"status": "partial",
"details": [
{
"code": "commit_cadence_weeks",
"params": {
"weeks": 29
}
}
],
"max_points": 36
},
{
"key": "commit_volume",
"name": "Commit volume",
"detail": "1750 commits in the last year",
"points": 18,
"status": "met",
"details": [
{
"code": "commits_last_year",
"params": {
"count": 1750
}
}
],
"max_points": 18
},
{
"key": "openssf_scorecard_maintained",
"name": "OpenSSF Scorecard: Maintained",
"detail": "30 commit(s) and 22 issue activity found in the last 90 days -- score normalized to 10",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
},
{
"key": "release_discipline",
"band": "excellent",
"name": "Release discipline",
"note": null,
"notes": [],
"value": 98,
"inputs": {
"releases_count": 43,
"latest_release_tag": "v0.23.0",
"releases_from_tags": false,
"days_since_latest_release": 1,
"mean_days_between_releases": 1.2
},
"components": [
{
"key": "ships_releases",
"name": "Ships releases",
"detail": "43 releases published",
"points": 27,
"status": "met",
"details": [
{
"code": "releases_published",
"params": {
"count": 43
}
}
],
"max_points": 27
},
{
"key": "release_recency",
"name": "Release recency",
"detail": "latest release 1 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "release_recency",
"params": {
"days": 1
}
}
],
"max_points": 36
},
{
"key": "release_cadence",
"name": "Release cadence",
"detail": "a release every ~1.2 days",
"points": 27,
"status": "met",
"details": [
{
"code": "release_cadence",
"params": {
"gap": 1.2
}
}
],
"max_points": 27
},
{
"key": "openssf_scorecard_signed_releases",
"name": "OpenSSF Scorecard: Signed-Releases",
"detail": "5 out of the last 5 releases have a total of 5 signed artifacts.",
"points": 8,
"status": "partial",
"details": [],
"max_points": 10
}
]
},
{
"key": "abandonment",
"band": "excellent",
"name": "Abandonment",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"cap": null,
"state": "maintained",
"guards": [],
"signals": [],
"red_flag": false,
"multiplier_pct": 100,
"declared_reason": null,
"unverified_reason": null,
"unanswered_open_prs": null,
"unanswered_open_issues": null,
"days_since_last_merged_pr": null,
"days_since_last_human_commit": 0,
"days_since_last_human_commit_is_floor": false
},
"components": [
{
"key": "project_is_still_maintained",
"name": "Project is still maintained",
"detail": "last human commit 0 days ago",
"points": 100,
"status": "met",
"details": [
{
"code": "abandonment_maintained",
"params": {
"days": 0
}
}
],
"max_points": 100
}
]
}
],
"description": "Is the project alive — is code being written and are releases shipping?"
},
{
"key": "community",
"band": "at_risk",
"name": "Community & Adoption",
"value": 47,
"weight": 0.18,
"metrics": [
{
"key": "popularity",
"band": "critical",
"name": "Popularity & adoption",
"note": null,
"notes": [],
"value": 8,
"inputs": {
"forks": 1,
"stars": 4,
"watchers": 1,
"growth_state": "unverified",
"growth_factor_pct": 100,
"growth_unverified_reason": "below_threshold"
},
"components": [
{
"key": "stars",
"name": "Stars",
"detail": "4 stars",
"points": 7.7,
"status": "partial",
"details": [
{
"code": "stars",
"params": {
"count": 4
}
}
],
"max_points": 60
},
{
"key": "forks",
"name": "Forks",
"detail": "1 forks",
"points": 0,
"status": "missed",
"details": [
{
"code": "forks",
"params": {
"count": 1
}
}
],
"max_points": 25
},
{
"key": "watchers",
"name": "Watchers",
"detail": "1 watchers",
"points": 0,
"status": "missed",
"details": [
{
"code": "watchers",
"params": {
"count": 1
}
}
],
"max_points": 15
}
]
},
{
"key": "community_health",
"band": "excellent",
"name": "Community health",
"note": null,
"notes": [],
"value": 92,
"inputs": {
"has_readme": true,
"has_license": true,
"has_contributing": true,
"has_issue_template": false,
"has_code_of_conduct": true,
"has_pull_request_template": true
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 22.5,
"status": "met",
"details": [],
"max_points": 22.5
},
{
"key": "license",
"name": "License",
"detail": "recognized license (GPL-2.0)",
"points": 22.5,
"status": "met",
"details": [
{
"code": "license_standard",
"params": {}
},
{
"code": "license_spdx",
"params": {
"spdx": "GPL-2.0"
}
}
],
"max_points": 22.5
},
{
"key": "contributing_guide",
"name": "CONTRIBUTING guide",
"detail": null,
"points": 18,
"status": "met",
"details": [],
"max_points": 18
},
{
"key": "code_of_conduct",
"name": "Code of conduct",
"detail": null,
"points": 13.5,
"status": "met",
"details": [],
"max_points": 13.5
},
{
"key": "issue_template",
"name": "Issue template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.2
},
{
"key": "pr_template",
"name": "PR template",
"detail": null,
"points": 6.3,
"status": "met",
"details": [],
"max_points": 6.3
}
]
},
{
"key": "ecosystem_adoption",
"band": "at_risk",
"name": "Ecosystem adoption (downloads)",
"note": null,
"notes": [],
"value": 47,
"inputs": {
"packages": [
"netresearch/nr-llm"
],
"dependents": 1,
"ecosystems": "packagist",
"total_downloads": 7919,
"monthly_downloads": 2421
},
"components": [
{
"key": "monthly_downloads",
"name": "Monthly downloads",
"detail": "2,421 downloads/month across packagist",
"points": 45.1,
"status": "partial",
"details": [
{
"code": "downloads_monthly",
"params": {
"count": 2421,
"ecosystems": "packagist"
}
}
],
"max_points": 80
},
{
"key": "registry_dependents",
"name": "Registry dependents",
"detail": "1 packages depend on it",
"points": 2,
"status": "partial",
"details": [
{
"code": "registry_dependents",
"params": {
"count": 1
}
}
],
"max_points": 20
}
]
}
],
"description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
},
{
"key": "governance",
"band": "moderate",
"name": "Sustainability & Governance",
"value": 68,
"weight": 0.24,
"metrics": [
{
"key": "maintainer_resilience",
"band": "critical",
"name": "Maintainer resilience (bus factor)",
"note": null,
"notes": [],
"value": 22,
"inputs": {
"bus_factor": 1,
"contributors_sampled": 2,
"top_contributor_share": 0.995
},
"components": [
{
"key": "bus_factor",
"name": "Bus factor",
"detail": "1 contributor(s) cover half of all commits",
"points": 9,
"status": "partial",
"details": [
{
"code": "bus_factor",
"params": {
"count": 1
}
}
],
"max_points": 54
},
{
"key": "commit_distribution",
"name": "Commit distribution",
"detail": "top contributor authored 100% of commits",
"points": 0.1,
"status": "partial",
"details": [
{
"code": "top_contributor_share",
"params": {
"share": 100
}
}
],
"max_points": 22.5
},
{
"key": "contributor_breadth",
"name": "Contributor breadth",
"detail": "2 contributors",
"points": 2.7,
"status": "partial",
"details": [
{
"code": "contributors_sampled",
"params": {
"count": 2
}
}
],
"max_points": 13.5
},
{
"key": "openssf_scorecard_contributors",
"name": "OpenSSF Scorecard: Contributors",
"detail": "project has 3 contributing companies or organizations -- score normalized to 10",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
},
{
"key": "responsiveness",
"band": "excellent",
"name": "Issue & PR responsiveness",
"note": null,
"notes": [],
"value": 97,
"inputs": {
"merged_prs": 443,
"open_issues": 1,
"closed_issues": 27,
"issue_closed_ratio": 0.964,
"closed_unmerged_prs": 16
},
"components": [
{
"key": "issue_resolution",
"name": "Issue resolution",
"detail": "96% of issues closed",
"points": 45.1,
"status": "partial",
"details": [
{
"code": "issues_closed_share",
"params": {
"share": 96
}
}
],
"max_points": 46.75
},
{
"key": "pr_acceptance",
"name": "PR acceptance",
"detail": "443/459 decided PRs merged",
"points": 36.9,
"status": "partial",
"details": [
{
"code": "decided_prs_merged",
"params": {
"merged": 443,
"decided": 459
}
}
],
"max_points": 38.25
},
{
"key": "openssf_scorecard_code_review",
"name": "OpenSSF Scorecard: Code-Review",
"detail": "all changesets reviewed",
"points": 15,
"status": "met",
"details": [],
"max_points": 15
}
]
},
{
"key": "stewardship",
"band": "moderate",
"name": "Ownership & stewardship",
"note": null,
"notes": [],
"value": 67,
"inputs": {
"followers": 40,
"owner_type": "Organization",
"is_verified": null,
"owner_login": "netresearch",
"public_repos": 278,
"account_age_days": 6097
},
"components": [
{
"key": "ownership_backing",
"name": "Ownership backing",
"detail": "organization-owned",
"points": 30,
"status": "met",
"details": [
{
"code": "owner_organization",
"params": {}
}
],
"max_points": 30
},
{
"key": "verified_domain",
"name": "Verified domain",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 20
},
{
"key": "owner_reach",
"name": "Owner reach",
"detail": "40 followers of netresearch",
"points": 11.6,
"status": "partial",
"details": [
{
"code": "owner_followers",
"params": {
"count": 40,
"login": "netresearch"
}
}
],
"max_points": 25
},
{
"key": "track_record",
"name": "Track record",
"detail": "278 public repos, account ~16 yr old",
"points": 25,
"status": "met",
"details": [
{
"code": "public_repos",
"params": {
"count": 278
}
},
{
"code": "account_age_years",
"params": {
"years": 16
}
}
],
"max_points": 25
}
]
},
{
"key": "package_maintenance",
"band": "excellent",
"name": "Package maintenance",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"packages": [
"netresearch/nr-llm"
],
"ecosystems": "packagist",
"any_deprecated": false,
"min_days_since_publish": 1
},
"components": [
{
"key": "published_resolvable",
"name": "Published & resolvable",
"detail": "1 package(s) on packagist",
"points": 25,
"status": "met",
"details": [
{
"code": "packages_published",
"params": {
"count": 1,
"ecosystems": "packagist"
}
}
],
"max_points": 25
},
{
"key": "publish_recency",
"name": "Publish recency",
"detail": "latest publish 1 days ago",
"points": 35,
"status": "met",
"details": [
{
"code": "publish_recency",
"params": {
"days": 1
}
}
],
"max_points": 35
},
{
"key": "version_history",
"name": "Version history",
"detail": "43 published versions",
"points": 20,
"status": "met",
"details": [
{
"code": "published_versions",
"params": {
"count": 43
}
}
],
"max_points": 20
},
{
"key": "not_deprecated",
"name": "Not deprecated",
"detail": "active, not deprecated or yanked",
"points": 20,
"status": "met",
"details": [
{
"code": "package_not_deprecated",
"params": {}
}
],
"max_points": 20
}
]
}
],
"description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
},
{
"key": "engineering",
"band": "excellent",
"name": "Engineering Quality",
"value": 90,
"weight": 0.2,
"metrics": [
{
"key": "engineering_practices",
"band": "excellent",
"name": "Engineering practices",
"note": null,
"notes": [],
"value": 90,
"inputs": {
"has_ci": true,
"has_tests": true,
"has_editorconfig": true,
"has_linter_config": true,
"has_precommit_config": false
},
"components": [
{
"key": "ci_workflows",
"name": "CI workflows",
"detail": "10 workflow(s)",
"points": 24,
"status": "met",
"details": [
{
"code": "ci_workflows",
"params": {
"count": 10
}
}
],
"max_points": 24
},
{
"key": "tests_present",
"name": "Tests present",
"detail": null,
"points": 24,
"status": "met",
"details": [],
"max_points": 24
},
{
"key": "linter_config",
"name": "Linter config",
"detail": ".php-cs-fixer.dist.php, phpstan.neon",
"points": 16,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": ".php-cs-fixer.dist.php, phpstan.neon"
}
}
],
"max_points": 16
},
{
"key": "pre_commit_hooks",
"name": "Pre-commit hooks",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 9.6
},
{
"key": "editorconfig",
"name": ".editorconfig",
"detail": null,
"points": 6.4,
"status": "met",
"details": [],
"max_points": 6.4
},
{
"key": "openssf_scorecard_ci_tests",
"name": "OpenSSF Scorecard: CI-Tests",
"detail": "11 out of 11 merged PRs checked by a CI test -- score normalized to 10",
"points": 20,
"status": "met",
"details": [],
"max_points": 20
}
]
},
{
"key": "documentation",
"band": "excellent",
"name": "Documentation",
"note": null,
"notes": [],
"value": 90,
"inputs": {
"topics": [
"ai",
"claude",
"llm",
"openai",
"php",
"typo3",
"typo3-extension",
"anthropic",
"gemini",
"gpt",
"embeddings",
"ollama",
"provider-abstraction",
"streaming"
],
"has_wiki": false,
"homepage": "https://netresearch.github.io/t3x-nr-llm/",
"has_readme": true,
"has_docs_dir": true,
"has_description": true
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 30,
"status": "met",
"details": [],
"max_points": 30
},
{
"key": "documentation_directory",
"name": "Documentation directory",
"detail": null,
"points": 25,
"status": "met",
"details": [],
"max_points": 25
},
{
"key": "documentation_homepage_site",
"name": "Documentation / homepage site",
"detail": "https://netresearch.github.io/t3x-nr-llm/",
"points": 15,
"status": "met",
"details": [],
"max_points": 15
},
{
"key": "repository_description",
"name": "Repository description",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "topics",
"name": "Topics",
"detail": "14 topics",
"points": 10,
"status": "met",
"details": [
{
"code": "topics_count",
"params": {
"count": 14
}
}
],
"max_points": 10
},
{
"key": "wiki",
"name": "Wiki",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
}
],
"description": "Are baseline engineering and documentation practices in place?"
},
{
"key": "security",
"band": "excellent",
"name": "Security",
"value": 87,
"weight": 0.16,
"metrics": [
{
"key": "security_posture",
"band": "good",
"name": "Security posture",
"note": "Excluded from scoring (no data or not applicable): Dangerous-Workflow, Packaging, Pinned-Dependencies, Token-Permissions. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"dangerous_workflow",
"packaging",
"pinned_dependencies",
"token_permissions"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 84,
"inputs": {
"source": "openssf_scorecard",
"checks_evaluated": 14,
"scorecard_version": "v5.5.0",
"checks_inconclusive": 4,
"scorecard_aggregate": 8.4
},
"components": [
{
"key": "binary_artifacts",
"name": "Binary-Artifacts",
"detail": "no binaries found in the repo",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "branch_protection",
"name": "Branch-Protection",
"detail": "branch protection is not maximal on development and all release branches",
"points": 2.2,
"status": "partial",
"details": [],
"max_points": 7.5
},
{
"key": "ci_tests",
"name": "CI-Tests",
"detail": "11 out of 11 merged PRs checked by a CI test -- score normalized to 10",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "cii_best_practices",
"name": "CII-Best-Practices",
"detail": "badge detected: Silver",
"points": 1.8,
"status": "partial",
"details": [],
"max_points": 2.5
},
{
"key": "code_review",
"name": "Code-Review",
"detail": "all changesets reviewed",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "contributors",
"name": "Contributors",
"detail": "project has 3 contributing companies or organizations -- score normalized to 10",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "dangerous_workflow",
"name": "Dangerous-Workflow",
"detail": "no workflows found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 10
},
{
"key": "dependency_update_tool",
"name": "Dependency-Update-Tool",
"detail": "update tool detected",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "fuzzing",
"name": "Fuzzing",
"detail": "project is not fuzzed",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "license",
"name": "License",
"detail": "license file detected",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "maintained",
"name": "Maintained",
"detail": "30 commit(s) and 22 issue activity found in the last 90 days -- score normalized to 10",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "packaging",
"name": "Packaging",
"detail": "packaging workflow not detected",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 5
},
{
"key": "pinned_dependencies",
"name": "Pinned-Dependencies",
"detail": "no dependencies found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 5
},
{
"key": "sast",
"name": "SAST",
"detail": "SAST tool is run on all commits",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "security_policy",
"name": "Security-Policy",
"detail": "security policy file detected",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "signed_releases",
"name": "Signed-Releases",
"detail": "5 out of the last 5 releases have a total of 5 signed artifacts.",
"points": 6,
"status": "partial",
"details": [],
"max_points": 7.5
},
{
"key": "token_permissions",
"name": "Token-Permissions",
"detail": "No tokens found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 7.5
},
{
"key": "vulnerabilities",
"name": "Vulnerabilities",
"detail": "0 existing vulnerabilities detected",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
}
]
},
{
"key": "dependency_advisories",
"band": "excellent",
"name": "Dependency advisories",
"note": "Excluded from scoring (no data or not applicable): Indirect dependencies free of known advisories, No advisories left outstanding. Remaining weights renormalized. Matched 10 resolved dependencies against OSV; 15 could not be assessed (no resolved version, an unsupported ecosystem, or beyond the reported package list). This repository publishes no package the index resolves, so the repository dependency graph was assessed instead. That graph mixes development and test pins with shipped dependencies, so only the declared runtime dependencies are scored; transitive findings are reported as context and excluded from the score. Reachability is not analyzed.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"indirect_dependencies_free_of_known_advisories",
"no_advisories_left_outstanding"
]
}
},
{
"code": "weights_renormalized",
"params": {}
},
{
"code": "advisories_scope_repository",
"params": {
"assessed": 10
}
},
{
"code": "advisories_unassessed",
"params": {
"count": 15
}
},
{
"code": "advisories_repo_graph_caveat",
"params": {}
},
{
"code": "advisories_reachability",
"params": {}
}
],
"value": 100,
"inputs": {
"source": "osv",
"advisories": 0,
"affected_packages": 0,
"assessed_packages": 10,
"unassessed_packages": 15,
"affected_by_severity": "none",
"direct_affected_packages": 0
},
"components": [
{
"key": "direct_dependencies_free_of_known_advisories",
"name": "Direct dependencies free of known advisories",
"detail": "no direct dependency carries a known advisory",
"points": 35,
"status": "met",
"details": [
{
"code": "no_direct_advisories",
"params": {}
}
],
"max_points": 35
},
{
"key": "indirect_dependencies_free_of_known_advisories",
"name": "Indirect dependencies free of known advisories",
"detail": "transitive set not separable from development and test dependencies in this scope",
"points": 0,
"status": "excluded",
"details": [
{
"code": "advisories_scope_not_separable",
"params": {}
}
],
"max_points": 25
},
{
"key": "no_advisories_left_outstanding",
"name": "No advisories left outstanding",
"detail": "no advisory carries a publication date",
"points": 0,
"status": "excluded",
"details": [
{
"code": "advisories_no_publication_date",
"params": {}
}
],
"max_points": 40
}
]
},
{
"key": "malicious_dependencies",
"band": "excellent",
"name": "Malicious dependencies",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"source": "osv",
"meaning": "reported as a malicious package by the OpenSSF corpus; the remedy is removal or moving off the compromised name, never an upgrade of the same artifact. Versions the registry has since pulled are listed but not scored",
"packages": [],
"red_flag": false,
"assessed_packages": 10,
"malicious_packages": 0,
"direct_malicious_packages": 0,
"withdrawn_malicious_packages": 0,
"installable_malicious_packages": 0
},
"components": [
{
"key": "no_dependency_reported_as_a_malicious_package",
"name": "No dependency reported as a malicious package",
"detail": "no dependency is reported as a malicious package",
"points": 100,
"status": "met",
"details": [
{
"code": "no_malicious_dependencies",
"params": {}
}
],
"max_points": 100
}
]
},
{
"key": "high_risk_jurisdiction_exposure",
"band": "excellent",
"name": "High-Risk Jurisdiction Exposure",
"note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
"notes": [
{
"code": "jurisdiction_evidence_limits",
"params": {}
}
],
"value": 100,
"inputs": {
"meaning": "self-published location evidence; not nationality or citizenship",
"red_flag": false,
"exposures": [],
"policy_countries": [
"Russia",
"Iran",
"North Korea"
],
"review_only_matches": 0,
"assessed_self_published_locations": 4
},
"components": [
{
"key": "policy_exposure_multiplier",
"name": "Policy exposure multiplier",
"detail": "no confirmed policy-scope location match",
"points": 100,
"status": "met",
"details": [
{
"code": "jurisdiction_no_match",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
},
{
"key": "ai_readiness",
"band": "good",
"name": "AI Readiness",
"value": 77,
"weight": 0,
"metrics": [
{
"key": "ai_agent_context",
"band": "excellent",
"name": "Agent context & guidance",
"note": null,
"notes": [],
"value": 85,
"inputs": {
"has_llms_txt": false,
"legible_history_share": 1,
"agent_instruction_files": [
".ddev/AGENTS.md",
".ddev/CLAUDE.md",
".ddev/GEMINI.md",
".github/workflows/AGENTS.md",
".github/workflows/CLAUDE.md",
".github/workflows/GEMINI.md",
"AGENTS.md",
"CLAUDE.md",
"Classes/AGENTS.md",
"Classes/CLAUDE.md",
"Classes/GEMINI.md",
"Configuration/AGENTS.md",
"Configuration/CLAUDE.md",
"Configuration/GEMINI.md",
"Documentation/AGENTS.md",
"GEMINI.md",
"Resources/AGENTS.md",
"Resources/CLAUDE.md",
"Resources/GEMINI.md",
"Tests/AGENTS.md",
"Tests/CLAUDE.md",
"Tests/GEMINI.md"
],
"agent_instruction_max_bytes": 13459
},
"components": [
{
"key": "agent_instructions",
"name": "Agent instructions",
"detail": ".ddev/AGENTS.md, .ddev/CLAUDE.md, .ddev/GEMINI.md, .github/workflows/AGENTS.md, .github/workflows/CLAUDE.md, .github/workflows/GEMINI.md, AGENTS.md, CLAUDE.md, Classes/AGENTS.md, Classes/CLAUDE.md, Classes/GEMINI.md, Configuration/AGENTS.md, Configuration/CLAUDE.md, Configuration/GEMINI.md, Documentation/AGENTS.md, GEMINI.md, Resources/AGENTS.md, Resources/CLAUDE.md, Resources/GEMINI.md, Tests/AGENTS.md, Tests/CLAUDE.md, Tests/GEMINI.md",
"points": 45,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": ".ddev/AGENTS.md, .ddev/CLAUDE.md, .ddev/GEMINI.md, .github/workflows/AGENTS.md, .github/workflows/CLAUDE.md, .github/workflows/GEMINI.md, AGENTS.md, CLAUDE.md, Classes/AGENTS.md, Classes/CLAUDE.md, Classes/GEMINI.md, Configuration/AGENTS.md, Configuration/CLAUDE.md, Configuration/GEMINI.md, Documentation/AGENTS.md, GEMINI.md, Resources/AGENTS.md, Resources/CLAUDE.md, Resources/GEMINI.md, Tests/AGENTS.md, Tests/CLAUDE.md, Tests/GEMINI.md"
}
}
],
"max_points": 45
},
{
"key": "machine_readable_docs_llms_txt",
"name": "Machine-readable docs (llms.txt)",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
},
{
"key": "legible_commit_history",
"name": "Legible commit history",
"detail": "99 of 99 human commits state their intent (structured subject or explanatory body)",
"points": 40,
"status": "met",
"details": [
{
"code": "legible_history",
"params": {
"legible": 99,
"sampled": 99
}
}
],
"max_points": 40
}
]
},
{
"key": "ai_verify_loop",
"band": "good",
"name": "Verify loop (build / test / typecheck)",
"note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Pinned-Dependencies. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"openssf_scorecard_pinned_dependencies"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 79,
"inputs": {
"has_nix": false,
"has_tests": true,
"lockfiles": [
"package-lock.json"
],
"has_dockerfile": true,
"typed_language": false,
"bootstrap_files": [
"Makefile"
],
"has_devcontainer": false,
"has_linter_config": true,
"typecheck_configs": [],
"agent_commit_share": 0.01,
"toolchain_manifests": [],
"dependency_bot_commit_share": 0.01
},
"components": [
{
"key": "one_command_bootstrap",
"name": "One-command bootstrap",
"detail": "Makefile",
"points": 18,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "Makefile"
}
}
],
"max_points": 18
},
{
"key": "automated_tests",
"name": "Automated tests",
"detail": null,
"points": 22,
"status": "met",
"details": [],
"max_points": 22
},
{
"key": "lint_format_config",
"name": "Lint / format config",
"detail": ".php-cs-fixer.dist.php, phpstan.neon",
"points": 11,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": ".php-cs-fixer.dist.php, phpstan.neon"
}
}
],
"max_points": 11
},
{
"key": "static_type_checking",
"name": "Static type checking",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 11
},
{
"key": "reproducible_environment",
"name": "Reproducible environment",
"detail": "Dockerfile, lockfile",
"points": 10,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "Dockerfile, lockfile"
}
}
],
"max_points": 10
},
{
"key": "demonstrated_agent_practice",
"name": "Demonstrated agent practice",
"detail": "1 of the last 100 commits agent-authored or agent-credited",
"points": 2,
"status": "partial",
"details": [
{
"code": "agent_authored_commits",
"params": {
"count": 1,
"sampled": 100
}
}
],
"max_points": 10
},
{
"key": "automated_maintenance",
"name": "Automated maintenance",
"detail": "1 of the last 100 commits are automated dependency updates",
"points": 8,
"status": "met",
"details": [
{
"code": "dependency_bot_commits",
"params": {
"count": 1,
"sampled": 100
}
}
],
"max_points": 8
},
{
"key": "openssf_scorecard_pinned_dependencies",
"name": "OpenSSF Scorecard: Pinned-Dependencies",
"detail": "no dependencies found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 10
}
]
},
{
"key": "ai_code_legibility",
"band": "moderate",
"name": "Code legibility for models",
"note": null,
"notes": [],
"value": 54,
"inputs": {
"primary_language": "PHP",
"largest_source_bytes": 208619,
"source_files_sampled": 1032,
"oversized_source_files": 20
},
"components": [
{
"key": "type_checkable_code",
"name": "Type-checkable code",
"detail": "PHP without a type-check config",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_typecheck_config_language",
"params": {
"language": "PHP"
}
}
],
"max_points": 45
},
{
"key": "manageable_file_sizes",
"name": "Manageable file sizes",
"detail": "20/1032 source files over 60KB",
"points": 53.9,
"status": "partial",
"details": [
{
"code": "oversized_source_files",
"params": {
"kb": 60,
"sampled": 1032,
"oversized": 20
}
}
],
"max_points": 55
}
]
}
],
"description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
}
],
"metrics_version": "1.13.0"
},
"warnings": [],
"report_type": "repository",
"generated_at": "2026-07-22T06:09:38.371501Z",
"schema_version": "0.26.0",
"badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/n/netresearch/t3x-nr-llm.svg",
"full_name": "netresearch/t3x-nr-llm",
"license_state": "standard",
"license_spdx": "GPL-2.0"
}