Skip to content

Release: v0.8.0#1529

Merged
vybe merged 582 commits into
mainfrom
dev
Jul 8, 2026
Merged

Release: v0.8.0#1529
vybe merged 582 commits into
mainfrom
dev

Conversation

@vybe

@vybe vybe commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Release: v0.8.0

Cumulative platform release since v0.7.0 — 110 issues across the public (abilityai/trinity) and private (abilityai/trinity-enterprise) trackers. Full notes: docs/releases/0.8.0.md.

Highlights

Release mechanics


Closes #73
Closes #186
Closes #187
Closes #267
Closes #292
Closes #388
Closes #506
Closes #654
Closes #679
Closes #792
Closes #846
Closes #894
Closes #903
Closes #905
Closes #918
Closes #919
Closes #945
Closes #946
Closes #954
Closes #959
Closes #1018
Closes #1084
Closes #1085
Closes #1103
Closes #1112
Closes #1131
Closes #1134
Closes #1155
Closes #1156
Closes #1157
Closes #1205
Closes #1266
Closes #1280
Closes #1281
Closes #1332
Closes #1342
Closes #1353
Closes #1360
Closes #1369
Closes #1376
Closes #1378
Closes #1400
Closes #1401
Closes #1406
Closes #1409
Closes #1410
Closes #1416
Closes #1418
Closes #1420
Closes #1422
Closes #1423
Closes #1424
Closes #1425
Closes #1426
Closes #1427
Closes #1432
Closes #1439
Closes #1441
Closes #1443
Closes #1444
Closes #1445
Closes #1446
Closes #1447
Closes #1450
Closes #1457
Closes #1461
Closes #1463
Closes #1464
Closes #1472
Closes #1478
Closes #1489
Closes #1491
Closes #1500
Closes #1501
Closes #1502
Closes #1513
Closes #1521

dolho and others added 30 commits June 19, 2026 15:56
#1231) (#1233)

Agent containers mounted /tmp as a hardcoded 100 MB noexec,nosuid RAM-backed
tmpfs. It fills easily — e.g. `gh` CLI install artifacts (~38 MB) that hardcode
/tmp and bypass the #1098 TMPDIR redirect — after which every /tmp write fails
with "No space left on device", including git's commit scratch, so autonomous
scheduled runs "complete" but silently fail to persist. The size being a
literal meant operators couldn't tune it without a code change + base-image
rebuild.

- capabilities.py: AGENT_TMPFS_MOUNT size now read from AGENT_TMP_SIZE (env on
  the backend service, which builds the agent mount spec), default 512m,
  validated `^\d+[mg]$` with empty/invalid → default. noexec,nosuid stay
  hardcoded — only size is configurable, and it stays bounded (counts against
  the agent memory cgroup). Single source of truth, so create (crud.py) and
  recreate (lifecycle.py) can't drift.
- Wire AGENT_TMP_SIZE=${AGENT_TMP_SIZE:-512m} on the backend service in both
  docker-compose.yml and docker-compose.prod.yml; document in .env.example.
- architecture.md Container Security: note the now-configurable size.
- tests/unit/test_1231_agent_tmp_size.py: default, valid m/g, case-fold,
  invalid→default, and the security flags are never dropped.

Mount specs are creation-time: existing agents pick up a new size on recreate,
not restart. Builds on #1098 (TMPDIR redirect) — closes the gap for tools that
hardcode /tmp. The agent-side git-sync silent-persist-failure is a separate
issue in the abilities repo, per the ticket.

Related to #1231

Co-authored-by: Eugene Vyborov <1073874+vybe@users.noreply.github.com>
…#1149)

Surface per-schedule performance on the Overview tab and the Schedules tab,
both from a SINGLE compact aggregate (no N per-schedule round-trips) — extends
#1107 (Overview) and generalises #868 (per-schedule deep analytics).

Backend:
- db `get_agent_schedules_summary(agent, hours)` — one rollup row per
  non-deleted schedule (zero-run schedules included): terminal success_rate
  (success / (success + failed[incl. error]); None when zero terminal),
  NULL-skipping avg_duration_ms, cost_total, context_avg, tool_call_total
  (parsed over newest 5,000 rows agent-wide, tool_calls_sampled flag), and
  last-run outcome. Cheap grouped SQL; iso_cutoff window (Invariant #16).
- GET /api/agents/{name}/schedules/analytics-summary?window=7d|14d|30d
  (AuthorizedAgent). Declared BEFORE /{schedule_id} in routers/schedules.py
  so the literal segment isn't captured as a schedule_id (Invariant #4) —
  putting it in analytics.py would be shadowed (schedules_router mounts first).
- models: ScheduleSummaryRow + AgentSchedulesSummaryResponse (Invariant #14).

Frontend (single fetch, two consumers — Invariant #7):
- executions.js fetchSchedulesSummary, cached per ${name}:${window} like
  fetchAgentAnalytics.
- OverviewPanel: "Schedules performance" section, honors the existing 7/14/30d
  window selector, each row deep-links to the Schedules tab; hidden at zero.
- SchedulesPanel: inline mini-stats per row (success rate, avg duration, runs,
  last-run dot) — badge style, no new chart/modal — from the same call.

Tests: tests/unit/test_1115_schedules_summary.py (6) — terminal success rate,
NULL-skip avg, tool-call total, zero-run-still-appears, soft-delete excluded,
out-of-window excluded. Full analytics suites green (30 passed). Frontend
prod build clean; endpoint verified live across 7/14/30d windows.

Related to #1115

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Eugene Vyborov <1073874+vybe@users.noreply.github.com>
…igration (#1290)

Adds a top-of-README callout recommending PostgreSQL for production (SQLite remains the zero-config dev default, opt-in via DATABASE_URL, #300), links the public Trinity Ops Agent (trinity-ops-public) for instance operations, and documents migrating existing SQLite instances via its /migrate-to-postgres skill. Also adds a Database section, a DATABASE_URL env row, and an ops-agent entry in the docs index.

Co-authored-by: Eugene Vyborov <eugene@beingluminous.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r into one shared module (#1088) (#1297)

The `is_auth_failure` + `AUTH_INDICATORS` + `NON_AUTH_KILL_MARKERS` (#904)
logic was duplicated inline in `subscription_auto_switch.py` and
`scheduler/service.py`, kept in sync by a hand-written "keep these lists in
sync" comment — exactly how the #904 kill-marker bug class re-appears.

Consolidate into one canonical module:

- New `src/backend/services/failure_classifier.py` — canonical, pure-stdlib
  classifier (55 lines). `subscription_auto_switch.py` now re-exports
  `is_auth_failure` unchanged, so existing importers
  (`routers/chat.py`, `services/task_execution_service.py`) and their test
  patch targets keep working.
- New `src/scheduler/failure_classifier.py` — byte-identical vendored mirror.
  The scheduler runs in a separate container and cannot import
  `backend.services`; it uses the classifier for log-labelling only (picks the
  `logger.error` wording, never gates a switch). The agent-runtime classifier
  in `error_classifier` is intentionally NOT merged — it diverges semantically
  and stays kill-safe by `_classify_signal_exit` precedence (D4).
- Byte-identity is enforced by
  `tests/unit/test_904_sigkill_no_false_auth.py::TestBackendSchedulerParity`;
  the re-export is pinned by `TestBackendReExportGuard`. No hand-sync.

Pure structural refactor, no behavioral change (verified by SHA-256 equality
of the two copies and line-for-line comparison vs the deleted code). The test
rewrite also drops the prior `exec(compile(...))` source-slicing in favour of
`importlib` path-loading, removing the only injection primitive in scope.

Tests: 19/19 pass in `test_904_sigkill_no_false_auth.py`.
CSO --diff: CLEAR (docs/security-reports/cso-diff-2026-06-21.md).

Refs #1088

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nd default-OFF flag (#946) (#1293)

* feat(orchestration): pull-pilot routing for agent→agent MCP chat behind default-OFF flag (#946)

Phase 2 PoC for pull/work-stealing (Epic #1045, umbrella #1081). When
MCP_AGENT_CHAT_PULL_ENABLED is ON, a sequential agent→agent (scope='agent',
non-self) chat_with_agent is routed by the MCP server through the durable async
/task path instead of the synchronous held /chat; the caller gets an immediate
{accepted|queued, execution_id} receipt and polls get_execution_result.
scope='user', self-tasks, and parallel=true are unchanged. Default OFF — flag
flip + MCP routing revert is the whole rollback.

- config.py: canonical MCP_AGENT_CHAT_PULL_ENABLED registry entry (both services
  read the SAME env key, so a single-.env deploy can't drift).
- settings.py: surface mcp_agent_chat_pull_enabled in /api/settings/feature-flags
  (auth-gated, observability-only — not a UI surface).
- chat.py: release the idempotency claim on the two /task dispatch-breaker-open
  (CircuitOpen) deny paths, mirroring /chat and CapacityFull (T5 fix) — without
  it a breaker-open reject silently blocks same-key retries for 24h.
- mcp-server: scope-based pull routing + D8 dispatch-mode idempotency token so a
  flag flip can't replay a wrong-shape snapshot across endpoints; startup log of
  the routing mode for the soak's control/treatment window.
- tests: chat.test.ts (routing fork + key behavior), test_946_task_idempotency_on_deny.py
  (deny-path claim release), feature-flag exposure tests.
- docs: ACTOR_MODEL_POSTCARD (#945 resolved), PULL_PILOT_946_SOAK harness +
  go/no-go record, CSO diff audit (CLEAR), TARGET_ARCHITECTURE/architecture updates.

Refs #946

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(feature-flows): document pull-pilot routing (#946) + AGENT_TMP_SIZE tmpfs (#1231)

Sync feature-flow docs with recent changes:
- agent-to-agent-collaboration.md: new Pull-Pilot Routing (#946) section —
  flag-gated MCP routing fork to the durable async /task path, poll-for-result
  receipt contract, D8 idempotency route token, feature-flag exposure, and the
  T5 /task dispatch-breaker-open deny-path claim release.
- container-capabilities.md: refresh stale tmpfs facts — agent /tmp size is now
  operator-configurable via AGENT_TMP_SIZE (default 512m, noexec,nosuid fixed),
  plus the TMPDIR=/home/developer/.tmp heavy-scratch redirect (#1098).
- feature-flows.md: add Recent Updates index rows for #946 and #1231.

Refs #946

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Eugene Vyborov <eugene@beingluminous.com>
…1294)

* feat(agent): runtime data_paths with portable export/import (#1169)

Declare an agent's runtime data (SQLite DBs, datasets) under data/ on the
already-durable home volume — no separate volume, no platform schema change.

- template.yaml `data_paths:` surfaced by template_service (github + local)
  and materialized at creation by crud.py -> git_service.materialize_data_paths:
  writes ~/.trinity/data-paths.yaml and appends data/ + entries to the agent's
  own .gitignore (idempotent). Opt-in; empty list is a no-op.
- S4 persistent-state and data_paths now share one extracted heredoc/list
  primitive (materialize_trinity_yaml_list / _read_trinity_yaml_list).
- New routers/agent_data.py: POST /data/export (stream | base64, 413 over cap,
  manifest-only tar when data/ missing) and POST /data/import (proxies to the
  agent-server restore primitive; data/** allowlist + traversal guard;
  Idempotency-Key). Both serialized per agent by a cross-worker Redis op lock.
- MCP tools export_agent_data / import_agent_data (Invariant #13).
- Validation checks DP-001..DP-005 in agent-validation-spec.
- Docs: architecture, requirements, feature-flows index + agent-data-volumes
  flow, agent guide; CSO diff audit report (0 critical/high).

Tests: ~30 unit + TestClient tests (export/import endpoints, allowlist,
gitignore, template surface). PR2 (scheduled snapshots + pre-snapshot
quiesce hook + retention/cascade) deferred.

Closes #1169

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(agent-data): satisfy sys.modules pollution lint in #1169 tests

The two new data_paths test files copied the baselined `patch.dict` +
bare `del sys.modules[...]` loader from test_persistent_state_allowlist.py,
which the sys.modules pollution lint flags as NEW (non-baselined) violations.

Adopt the blessed snapshot/restore exception (precedent:
test_telegram_webhook_backfill.py): declare a top-level
`_STUBBED_MODULE_NAMES` list + an autouse `_restore_sys_modules` fixture, and
install the stubs / evict the cached module directly (the fixture owns
restoration). Removes the bare `del sys.modules[...]` entirely rather than
hiding it. Drop the now-unused `patch` import from the gitignore test.

Lint passes (no new violations); both files' 16 tests still green; no
cross-file leakage.

Refs #1169

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(feature-flows): sync recent changes — #1231 tmpfs, #1115/#1231 index rows

Fix container-capabilities.md for the now-configurable agent /tmp tmpfs
(#1231): default 100m → 512m via AGENT_TMP_SIZE, and correct the stale
full_capabilities ternary excerpts to the shared AGENT_TMPFS_MOUNT constant
(noexec,nosuid always applied, both modes). Add Recent Updates index rows for
the per-schedule performance scorecards (#1115) and the tmpfs-size fix (#1231);
the #1169 and #1116 rows already shipped in-commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Eugene Vyborov <eugene@beingluminous.com>
…ed agent network (#1159) (#1292)

* fix(security): authenticate the in-container agent server on the shared agent network (#1159)

The in-container agent server (:8000) had zero inbound auth on
trinity-agent-network: any agent could read a sibling's .env secrets or
run arbitrary Claude on it. Every backend->agent call now carries a
per-agent X-Trinity-Agent-Token = HMAC-SHA256(AGENT_AUTH_SECRET,
"trinity-agent-auth:v1:"+name), verified by a pure-ASGI middleware on all
HTTP and WebSocket routes (constant-time compare; only /health exempt).

- Derive-don't-store: the master AGENT_AUTH_SECRET lives only in the
  backend env (auto-generated by start.sh like SECRET_KEY); each container
  receives only its own token, so a compromised agent cannot compute a
  sibling's. Fail-closed -- derive raises on an empty secret.
- Callers route through services/agent_auth.py (agent_httpx_client /
  build_agent_auth_headers / merge_auth_headers); a static guard test
  fails any new raw agent-{name}:8000 caller that bypasses them.
- Removed the dead, unauthenticated /ws/chat route (ran arbitrary Claude)
  and the agent server's wildcard CORS (internal-only).
- Grace path for old images: empty TRINITY_AGENT_AUTH_TOKEN -> allow;
  check_agent_auth_token_env_matches forces a one-pass recreate to inject.
- Retired the unused src/scheduler/agent_client.py.

Tests: unit (matcher, middleware, header guard, derivation) + security
isolation test. CSO diff audit in docs/security-reports/cso-diff-2026-06-20.md.

Closes #1159

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(feature-flows): document agent-server authentication (#1159) + sync recent flows

Add a feature-flow doc for the in-container agent-server inbound auth shipped
in this PR, and sync the index with two recent merged changes.

- New feature-flows/agent-server-authentication.md: end-to-end trace of the
  derived X-Trinity-Agent-Token (HMAC over AGENT_AUTH_SECRET), the pure-ASGI
  middleware enforcing it on every HTTP/WS route, fail-closed vs grace path,
  recreate reconciliation, and the migrated callers + static guard. Added to
  the Authentication & Security catalog in the index.
- container-capabilities.md: refresh the stale /tmp tmpfs size (was a hardcoded
  100m) to the configurable AGENT_TMP_SIZE (default 512m, #1231).
- Recent Updates rows for #1159, #1231, and the previously-missing #1115.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Eugene Vyborov <eugene@beingluminous.com>
#1039 (configurable data-retention) moved to the private enterprise
tracker; replace the now-private issue number in voip-telephony.md with a
generic description so the public doc doesn't deep-link a private issue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…runner-1160

docs(feature-flows): add database-migration-runner flow (#1160)
Run ~100 best-practice checks (11 categories) against a running agent's
workspace, surfaced non-blocking in the Overview tab with one-click
auto-fix for the 10 gitignore checks, plus an MCP tool.

- services/compatibility/ package (spec/collector/static_checks/ai_checks/fixes):
  ONE docker exec -> in-container python -> JSON snapshot (secret files
  existence-only, size/binary caps); pure STATIC checks (HARD-only) +
  category-batched AI checks (Haiku, iterate-expected, fail-open, capped at
  SOFT, secret-redacted); runtime-aware (claude-only checks skipped for
  Codex/Gemini).
- GET/POST endpoints (read AuthorizedAgentByName; fix OwnedAgentByName, gitignore
  only, per-agent Redis lock, atomic write, uncommitted until next sync;
  include_ai path rate-limited). agent_compatibility_results table (dual-track
  SQLite + Alembic) persists the latest snapshot; cascade/rename via AGENT_REFS.
- CompatibilityPanel.vue (two-phase fetch, grouped checklist, per-check fix,
  re-run) in OverviewPanel; get_agent_compatibility_report MCP tool.
- 35 fixture-driven unit tests; spec sync-tested against docs/agent-validation-spec.md.

Persistence departs from the issue's "no DB table" note so AI verdicts show
without re-spend + enable fleet aggregation (see requirements section 41).

Fixes #668

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ity bumps, CI shims, license, CSP)

# Conflicts:
#	.github/workflows/container-security.yml
#	.github/workflows/schema-parity.yml
#	src/frontend/security-headers.conf
#	src/frontend/vite.config.js
#	tests/git-sync/package-lock.json
#	tests/git-sync/package.json
chore: reconcile origin/main into dev (14 out-of-band commits)
CodeQL py/polynomial-redos (high): `_ASSIGN_RE` captured the value as
`[ \t]*(.+?)[ \t]*$`. The lazy `.+?` and the surrounding `[ \t]*` can both
match a tab, giving polynomial backtracking when `_redact()` runs the pattern
over up to 48 KB of agent-supplied file text.

Capture the value greedily to end-of-line (`(.*)$`) and let the callers
strip — both `_looks_placeholder()` callers already `.strip()`, so secret
detection and redaction are behaviourally identical (verified: `=`, `:`,
`export`, and indented forms still match). 35 unit tests pass.
feat: agent deployment compatibility validation — server-side checks with auto-fix (#668)
Companion to trinity-enterprise#36. OSS carries only the entitlement-gated
surface; all SSO logic lives in the private submodule.

- Login.vue: "Sign in with <IdP>" buttons (shown only when the `sso` feature is
  entitled and a provider is enabled), plus OIDC callback-fragment handling
  (`/login#sso=ok|mfa|error`) — reuses the existing 2FA challenge UI when the
  IdP login still requires a local second factor.
- stores/auth.js: completeSsoLogin() (reuses _finalizeLogin / _setMfaChallenge)
  + fetchSsoProviders() (empty in OSS-only builds — endpoint 404s).
- Settings.vue: admin-gated "SSO" tab → SsoPanel.vue (provider CRUD + test +
  policy). Gated by enterpriseStore.isEntitled('sso'), same as the 2FA tab.
- Bump enterprise submodule to the SSO module commit.
- docs: architecture enterprise-modules row + requirements §40 (SSO/OIDC).

No new backend dependency (python-jose + httpx already in the image) and no
OSS Python changes — the mint/whitelist/mfa seams already exist.

Stacked on feat/5-2fa-totp (reuses the OSS mfa_gate + 2FA challenge surface).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pulls in the email_verified / issuer-pinning / login-CSRF fixes
(trinity-enterprise 87c8f97). OSS gated surface unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…flow

Add an "Incubating Directions (Not Yet Decided)" section to
TARGET_ARCHITECTURE.md capturing the goal-directed control-surface idea
(Objective + policies + roster + externally-measured evals), explicitly
bounded by CLAUDE.md §8 and sequenced after the pull migration + #300.
Incubating in trinity-enterprise#27.

Reword the voip-telephony flow note to drop a stale #1039 reference in
favor of describing the LOG_* data-retention no-op class directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Routine /cso full-audit posture report (Phases 0–14, daily 8/10 gate).
Follows the docs/security-reports/ convention; no real secrets reproduced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ync dev features

- Add videos.md (35 published videos, newest-first by topic) and a README Watch section
- Add 'Watch' callouts to 32 feature pages linking the most relevant, newest videos
- Add user-facing whats-new/v0.6.1.md (translated from release notes; no issue numbers)
- Document dev-only features: agent runtimes (Claude Code/Codex/Gemini CLI, #1187),
  agent data paths + export/import (#1169), compatibility validation (#668),
  in-app bug reporting (#1116), subscription hot-reload (#1089), pull-pilot routing (#946),
  configurable AGENT_TMP_SIZE (#1231), Postgres migration-runner groundwork (#1160)
- Index agent-runtimes, agent-data, and the previously-orphaned agent-session pages

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/trinity-enterprise#38, #82)

Capture an optional operator email/company at first-run setup with an explicit,
unchecked-by-default opt-in to "occasionally receive important security & product
updates", submitted once to a new /v1/operator-intake endpoint on #1116's
Cloudflare intake app. The same email binds as the admin's sign-in identity so
the operator can log in with email + password — no verification email is sent (a
fresh install has no Resend key; the email is bound, not code-verified). The
code-based email second factor stays Phase 2 on the existing mfa_gate seam.

- backend: operator_intake_service (fire-and-forget, at-most-once via a
  system_settings marker, DO_NOT_TRACK aware, owns installation_id); setup
  endpoint captures profile + binds admin email; authenticate_user resolves the
  admin by username OR registered email (password guard blocks code-only users);
  PUT /api/users/me/email for the existing-admin transition
- frontend: SetupPassword email/company + consent checkbox; Login "username or
  email" field; Settings -> General "Admin sign-in email" card
- config: OPERATOR_INTAKE_ENABLED / OPERATOR_INTAKE_URL (+ .env.example)
- docs: requirements section 43, architecture catalog, first-time-setup feature flow
- tests: 16 unit tests (intake idempotency/guards, email-login resolution, setup)

Fixes abilityai/trinity-enterprise#38
Fixes #82

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…in (trinity-enterprise#38, #82)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#82)

The _EMAIL_RE pattern duplicated into setup.py and users.py had two
[^@\s]+ atoms around the literal \. that both also match '.', giving the
engine many ways to place the dot and backtracking polynomially on
user-controlled email input (CodeQL alerts #211, #212).

Constrain only the final segment to [^@\s.]+ (no dot) so the trailing \.
can align with exactly one position -> linear matching. Behaviour is
unchanged: multi-subdomain addresses still validate; an 80k-char
pathological input now resolves in ~2ms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…erts, SSH, binary) (#1305)

* feat(credentials): curated credential file-type injection — SA keys, certs, SSH, binary (enterprise#11)

Widens CRED-002 injection from the fixed 3-path exact allowlist
(.env/.credentials.enc/.mcp.json) to a curated set of credential file *types*,
without reopening the arbitrary-path RCE surface (#183/#590/#598).

- New services/credential_paths.py — single-source policy: ALLOW (.config/gcloud/**,
  .kube/config, *.pem/*.key/*.crt/*.cert/*.p12/*.pfx, .ssh/id_*, + existing exact set)
  with deny-precedence over anything executed/sourced at startup (shell rc,
  CLAUDE.md/AGENTS.md/.claude/**, .mcp.json.template, .ssh/authorized_keys/config,
  .git*, bin/**) and `..`/absolute traversal. Vendored byte-identically into the
  agent image (Invariant #5) with a parity test.
- Agent-server hardening: the inject + update file loops now enforce the policy AND
  a resolve-under-home traversal guard the original write path lacked; parent-dir
  creation + chmod 0o600 preserved. New GET /api/credentials/list for export discovery.
- Binary-safe: inject carries files_b64 (base64); agent writes via write_bytes.
  .credentials.enc gains a v2 {files, files_b64} envelope (legacy flat archives still
  decrypt); encrypt/decrypt stay flat for the single-secret callers (SIEM/2FA/SSO).
- Export now captures the FULL injected set (via /list) + binary, not just the 2 defaults.
- Three surfaces in sync (Invariant #13): MCP inject_credentials gains files_b64;
  frontend CredentialsPanel gains a file-upload affordance (text vs base64 auto-detected).
- Tests: allowlist test now exercises the REAL policy (newly-allowed + still-blocked),
  + credential_paths parity test + binary archive round-trip test. 61 pass.
- docs/memory/architecture.md: credential-path policy documented.

Related to Abilityai/trinity-enterprise#11. Loosens a deliberately-tight boundary —
run /cso on the diff before merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(credentials): close /cso findings on the injection widening (#11 review)

Security review of the widening surfaced one HIGH regression + hardening items;
all fixed here.

#1 (HIGH, RCE): the #598 .mcp.json content-validation guard was bypassable via
the new files_b64 (binary) channel — validate_mcp_config only checked `files`,
so `files_b64={".mcp.json": base64(<stdio-command MCP server>)}` skipped it and
configured an RCE MCP server on the target agent. Fix: .mcp.json may only arrive
as TEXT (files), where it is validated; rejected in files_b64 at the backend
inject router AND the agent-server write helper.

#2 (defense-in-depth): import/auto-import wrote decrypted archives via the
agent-server /inject layer only. Added validate_credential_set() (curated path
policy + .mcp.json content + no-binary-.mcp.json) on the backend import boundary
so enforcement is dual-layer as the issue mandates. (Archives are AES-GCM with
the server key, so a forged archive wasn't practical — but the layer belongs.)

#3: .ssh/ is now locked to id_* only — a stray *.key/*.pem under .ssh is no
longer accepted (policy was previously broader than the "SSH keys = id_*" intent).

#4 (noted): .config/gcloud/** can hold a google-auth executable credential_source;
only honored under non-default GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES=1.
Documented in credential_paths.py.

+6 regression tests (169 pass). CSO report: docs/security-reports/cso-2026-06-22-11-diff.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(credentials): exclude vendor dirs from cert globs + accurate export count (#11 live test)

Found while testing PR #1305 against a real local instance:

1. Over-capture: the broad *.pem/*.key/*.crt globs matched bundled CA files
   (e.g. .local/.../site-packages/certifi/cacert.pem), so export's /list walk
   swept vendored cert material into .credentials.enc. Added node_modules,
   site-packages, .local, .venv/venv, .cache, go/pkg to the deny-list (both
   root and nested forms) so cert globs only catch real credential files.

2. export's files_exported count re-read just the 2 default files (reported 1
   while the archive actually held 5). export_to_agent now returns the true
   captured count; dropped the redundant stale read.

Verified end-to-end on a live agent: allowed types inject (text+binary, 0600,
parent dirs), blocked paths 400 (incl. .ssh non-id_*, .mcp.json-via-files_b64,
weaponized .mcp.json text), and binary round-trips through export→import with
matching sha256. +5 regression tests (72 pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dmin-email

feat(setup): first-run operator intake + admin email login (trinity-enterprise#38, #82)
…ev-sync

docs(user-docs): video library + per-page links, v0.6.1 What's New, sync dev features
…-ref

docs(voip): genericize moved-issue reference in feature-flow
…stale voip hunk

Two review-driven fixes ahead of the v0.7.0 cut:

- Annotate the CSO posture report (.md + .json) with post-audit remediation
  status. The report audited `main` pre-cut and listed F1/F2/F3 as open
  VERIFIED findings; they are already remediated on `dev` and ship in v0.7.0:
    - F1 (unauth agent-server) -> #1159 X-Trinity-Agent-Token middleware
    - F2 (fastmcp -> hono/undici) -> #1255, #1289; fastmcp ^4.3.0
    - F3 (form-data CRLF via axios) -> #1254
    - F4/F5/F8 exploit path closed by #1159 (auth gate)
  Adds a top-of-report banner, per-row status tags, per-finding notes, and a
  machine-readable `remediation_status` block in the JSON. Avoids publishing a
  stale "open CRITICAL + exploit" to a PUBLIC repo without its fix context.

- Drop the voip-telephony.md reword: it is superseded by already-merged #1301,
  which made the identical `#1039` -> `LOG_*` change on `dev`. Restoring to
  merge-base removes the redundant/conflicting hunk from this PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs: incubating direction + voip flow reword + CSO 2026-06-21 report
dolho and others added 23 commits July 8, 2026 11:32
test: fix dev test-suite drift + harness bugs (no product changes)
…catalog

fix(templates): curate the agent template catalog — hide fixtures, order starters first (#1513)
feat: bundle a Brain-Orb-enabled Cornelius in the default install (Abilityai/trinity-enterprise#107)
…l-height

# Conflicts:
#	docs/memory/feature-flows.md
#	docs/memory/learnings.md
…trinity-enterprise#78)

Builds on the portal chat drawer:
- Chat sessions: per-agent history switcher + "New chat"; resumes the most-recent
  thread on open; search results open on their thread.
- Cross-chat search: a search box on the portal home searches the client's
  conversations (title + message content) across all rostered agents; results
  replace the roster and open the matching thread.
- Voice mode: 🔊 speaks replies (ElevenLabs TTS) and a 🎤 mic dictates input —
  browser Web Speech API where available (Chrome/Edge/Safari), MediaRecorder →
  server Scribe STT fallback for Firefox. Gated on the agent's `voice_available`.
- Polish: larger, clickable agent cards on the roster; a "How do files work?"
  note in the Files drawer.

Backed by the enterprise endpoints in trinity-enterprise#106 (sessions/search/
tts/stt). Frontend-only; no OSS submodule bump here (lands after #106 merges).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps the patch-and-minor group in /src/mcp-server with 3 updates: [fastmcp](https://github.com/punkpeye/fastmcp), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [tsx](https://github.com/privatenumber/tsx).


Updates `fastmcp` from 4.3.2 to 4.4.0
- [Release notes](https://github.com/punkpeye/fastmcp/releases)
- [Commits](punkpeye/fastmcp@v4.3.2...v4.4.0)

Updates `@types/node` from 26.0.1 to 26.1.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `tsx` from 4.22.4 to 4.23.0
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](privatenumber/tsx@v4.22.4...v4.23.0)

---
updated-dependencies:
- dependency-name: fastmcp
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: patch-and-minor
- dependency-name: "@types/node"
  dependency-version: 26.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: patch-and-minor
- dependency-name: tsx
  dependency-version: 4.23.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: patch-and-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the patch-and-minor group in /tests/git-sync with 1 update: [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest).


Updates `vitest` from 4.1.9 to 4.1.10
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.10/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patch-and-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the patch-and-minor group in /src/frontend with 3 updates: [js-yaml](https://github.com/nodeca/js-yaml), [vue-chartjs](https://github.com/apertureless/vue-chartjs) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `js-yaml` from 5.2.0 to 5.2.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](nodeca/js-yaml@5.2.0...5.2.1)

Updates `vue-chartjs` from 5.3.3 to 5.3.4
- [Release notes](https://github.com/apertureless/vue-chartjs/releases)
- [Changelog](https://github.com/apertureless/vue-chartjs/blob/main/CHANGELOG.md)
- [Commits](apertureless/vue-chartjs@v5.3.3...v5.3.4)

Updates `vite` from 8.1.2 to 8.1.3
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.1.3/packages/vite)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 5.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-and-minor
- dependency-name: vue-chartjs
  dependency-version: 5.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-and-minor
- dependency-name: vite
  dependency-version: 8.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patch-and-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…'t flake (#1500)

The frontend-e2e run surfaced a second pre-existing infra flake (alongside
the #954 spec repair): dashboard-grid-view timed out clicking the dashboard
mode toggles because the first-run OnboardingWizard (trinity-enterprise#52)
auto-opens on the CI stack's fresh, zero-user-agent Dashboard and its
`fixed inset-0` backdrop intercepts pointer events. It's a race against
fleet-load, so it was flaky, not deterministic, and local dev never
reproduces it (a real stack has agents, so the wizard never auto-opens).

Seed the product's own remembered-dismissal key
(`trinity_onboarding_dismissed_v1`) in the shared auth.setup.js storageState
— same origin as the JWT `token`, so it's captured by the snapshot every
spec inherits. Fixes it fleet-wide with one line; specs that want the wizard
opt back in via `?onboarding=1` (which bypasses the key by design).

No app code changes. Adds a learnings entry for the fresh-install-only-modal
class of e2e flake.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…wnership (#1478) (#1509)

LOG_RETENTION_DAYS archived old log files but never freed disk: the backend
mounted the logs volume read-only, so log_archive_service.unlink() failed per file
(`Errno 30: Read-only file system`) and the volume grew unbounded (~900MB/day; eu2
hit 32G).

Two layers were blocking the delete:

1. **Read-only mount** — docker-compose.prod.yml AND docker-compose.yml both mounted
   `trinity-logs:/data/logs:ro` on the backend. Changed to read-write. Vector stays
   the sole writer of new log lines; the backend only reads (to gzip) + deletes the
   archived originals.

2. **Directory ownership** — Vector runs as root and creates `/data/logs` root:root
   0755, so even rw the backend (UID 1000) can't unlink (deleting a file needs write
   on the *directory*). Vector can't fix it itself (cap_drop: ALL → no CAP_CHOWN).
   Added a one-shot `logs-init` container (root, alpine) that `chown 1000:1000` +
   `chmod 775` the dir before the backend starts (depends_on: completed). Chowns the
   dir only (fast, not -R); Vector's future root-owned files stay deletable via the
   now-writable dir.

Also: log_archive_service.start() logs a WARNING at startup if LOG_DIR isn't
writable, so the failure is visible instead of silent per-file errors at cleanup_hour.

Verified live (dev): reproduced the `:ro` failure; after the fix the dir is
1000:1000 775, backend writes, and a root-owned file in /data/logs is deletable by
UID 1000 (Errno 30 gone).

Closes #1478.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…plit

docs(claude-md): split core-team context into .claude submodule via @import
…7-08

docs(user-docs): sync dashboard Grid view + fork-to-own; fix broken links
fix(ui): Tasks tab fills viewport height like the Chat tab (#1500)
…d (trinity-enterprise#78)

Point src/backend/enterprise at feature/78-portal-history-hardening (630cca9),
which carries the client-portal sessions/search/voice endpoints this frontend
(#1498) calls. Forward move from f1bafee. Re-bump to the merged commit once
trinity-enterprise#106 lands on enterprise main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(portal): Client Portal frontend — chat, sessions, search, voice, files (trinity-enterprise#78)
Replace stale (old-nav) screenshots with the current v0.7.0 UI and refresh the
docs/README to reflect features shipped since 0.7.

Screenshots
- Add current-UI shots (Grid dashboard, Operations->Executions, Agent Overview,
  Brain Orb) to the README showcase and the previously image-less user-docs
  sections (dashboard.md Grid view, executions.md Operations tab,
  managing-agents.md Overview tab, dynamic-dashboards.md Brain Orb).
- Remove 4 orphaned old-nav README assets; add a
  !docs/assets/screenshots/*.png gitignore negation so README assets track
  without force-add.

User docs
- Document the fresh-install default Cornelius agent (setup.md).
- Document the Starter Templates catalog curation + hidden fixtures
  (creating-agents.md).

README feature refresh (post-0.7)
- Runtimes: add OpenAI Codex; Fleet Observability: add Grid View.
- Add Sequential Agent Loops, Webhook Triggers, Voice, Operator Queue,
  Agent Overview, and Compatibility Report.
- MCP: tool count 74 -> 90+; add missing categories (git, operator-queue,
  loops, reports, memory, pipelines, voip).
- Fix stale "Events page" -> Operations page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-readme-refresh

docs: refresh screenshots + user docs + README for post-0.7 features
…s created_at (#1426) (#1435)

* fix(operator-queue): tolerate agent items missing created_at/title/question

Agents author ~/.trinity/operator-queue.json free-form. An item that omitted
created_at made OperatorQueueOperations.create_item raise KeyError; because the
item stayed `pending` in the agent file, the 5s operator-queue sync retried and
error-logged it EVERY cycle, indefinitely ("Failed to create queue item <id>:
'created_at'" on eu2/v0.7.0) — the request never reached the Operating Room and
the operator never saw it. title/question had the same latent KeyError.

Make the sync ingest boundary defensive: default the three hard-indexed fields
in create_item (mirroring the existing type/status/priority .get() defaults).
created_at defaults to utc_now_iso() (ingest time, ISO-Z per Invariant #16);
title/question get sensible fallbacks. The item is now created once, so the
next cycle sees it via exists() and the loop stops — the request surfaces
instead of vanishing.

Verified:
- tests/unit/test_1426_operator_queue_created_at.py (3, real code + SQLite):
  missing created_at defaulted (not raised); missing title/question defaulted;
  present fields preserved.
- Live against the rebuilt backend image + real DB: a created_at-less item
  creates without KeyError (created_at defaulted to now), and the 5s sync loop
  logged zero "Failed to create queue item" errors.

Related to #1426

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(test): #1426 test used an uninitialized temp DB → regression-diff CI fail

The test overrode TRINITY_DB_PATH to a fresh file, but init_database() runs only
once (at first `database` import, under the conftest-pinned path), so
get_engine() then hit an uninitialized DB — every case failed with
'no such table: operator_queue', which the regression-diff job flagged as a
head-only regression. Use the conftest's already-initialized per-process DB
(drop the override). The operator_queue.py fix itself is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Eugene Vyborov <1073874+vybe@users.noreply.github.com>
…nc model catalog (#1521) (#1524)

Replace ~15 scattered hardcoded 200000 context-window denominators with one
model→context-window catalog so the "% context used" gauge is correct for
1M-context models (Opus 4.8/4.7, Sonnet 5, Fable 5, Gemini = 1M; Codex = 272K).
The runtime-reported modelUsage.contextWindow stays PRIMARY; the catalog is the
FALLBACK only — bare Claude → 200K safe floor, [1m] → 1M, Gemini → 1M,
Codex → 272K, unknown → 200K + logged warning. The bare-Claude floor is
deliberate: the effective window is auth/plan-dependent, so a fixed 1M fallback
would hide an imminent 200K compaction wall.

- New pure-stdlib services/model_context.py, vendored byte-identically into the
  agent server (Invariant #5, parity-tested).
- Unify 5 disagreeing agent-server resolvers (state.py, claude_code, gemini,
  codex, the ABC) via the catalog. Seed metadata.context_window at construction
  in the Claude + headless paths — the real Claude fallback, since
  get_context_window is dead code for Claude and the flat Pydantic default was
  the actual fallback. Fix state.py init-ordering so a [1m] agent reports 1M
  before its first turn. The non-abstract ABC default means a new runtime can't
  ship a silently-wrong 200K.
- Backend downstream fallbacks use the shared DEFAULT_CONTEXT_WINDOW constant.
- Re-sync ModelSelector presets (add Fable 5, Sonnet 5) + agent-server alias note.
- No schema change (columns/DDL unchanged; only written values become correct).

Co-authored-by: Eugene Vyborov <eugene@beingluminous.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Oleksii Dolhov <oleksii.dolhov@gmail.com>
…lease

main diverged after v0.7.0 (Step-10 resync was skipped): the v0.7.0 squash plus
3 dependabot bumps (esbuild/form-data/vite — all superseded in dev) and the #1334
CI fix. dev is authoritative; this -s ours merge records main as a parent so the
dev->main release PR is clean. The #1334 CI fix (missing from dev) is re-applied
in the following commit.
- VERSION 0.7.0 -> 0.8.0 (read by /api/version + baked into the backend image)
- Re-apply #1334: drop `-e ./src/cli` from tests/requirements-test.txt (it reds
  GitHub's Dependency-Graph check on every release) and install trinity_cli on
  demand from tests/setup-env.sh. The fix landed on main but was missing from dev.
- Add docs/releases/0.8.0.md (110 issues across both trackers).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Release-prep: v0.8.0 (reconcile main + #1334 CI fix + version bump + notes)
@vybe vybe requested a review from AndriiPasternak31 as a code owner July 8, 2026 19:16
destination = config.fork_to_own.destination_repo
# Source-mode rows bypass the UNIQUE(repo, branch) index, so
# this is the guard against two agents auto-pushing the same
# destination main. (Re-checked after reservation below — this
# this is the guard against two agents auto-pushing the same
# destination main. (Re-checked after reservation below — this
# pre-check is check-then-act with the whole copy in between.)
bound_agents = db.get_git_config_agent_names_for_repo(destination)
)
if rc != 0:
logger.error("fork-to-own: template clone failed for %s: %s",
template_repo, scrub_secret(out, read_pat))
# the copy lands there and origin points at it). Also drives the
# featured card treatment in CreateAgentModal, with `tagline` as the
# card subtitle.
"fork_to_own": metadata.get("fork_to_own"),
# single-id resolve — `get_local_template()` / creation-by-id — still
# works for the test harness; `get_local_templates()` does the actual
# exclusion. (#1513)
"hidden": bool(data.get("hidden", False)),
ogg = await tts_service.synthesize_voice_note(spoken, cfg["voice_id"])
if not ogg:
return False

# #1131: a denied docker.sock (e.g. wrong group_add GID on macOS Docker
# Desktop) raises here; swallowing it silently showed "No agents" in the
# UI with nothing in the logs. WARN so the failure is diagnosable — but
# throttled (see _SOCKET_WARN_THROTTLE_S) so a persistent denial on this
…unit files (#1530)

The base/head regression matrix (which only runs on PRs, not dev pushes) surfaced
16 order-dependent failures in test_fork_to_own / test_73_stats_cache /
test_186_enumeration_uniformity — all new-on-dev files that pass in isolation but
fail under full-suite ordering. Three distinct sys.modules-pollution vectors, all
test-harness bugs (product code is intact); each fix only restores real modules:

1. test_voice_auth.py stubbed services.{docker_service,template_service,
   platform_audit_service} at module level and never restored them. The stubs are
   incomplete (no is_trinity_compatible / execute_command_in_container), so later
   files importing those symbols hit "ImportError: ... (unknown location)". Now
   snapshots + restores them after voice.py imports (mirrors test_voice_tools.py).

2. test_fork_to_own.py's crud_env teardown ran a manual
   "del sys.modules['services.agent_service.*']" AFTER patch.dict.stop() — which
   had already restored the pre-test snapshot. The delete left
   services.agent_service.stats absent, so test_73's module-level "stats_mod"
   reference went stale and its monkeypatch missed. Removed the redundant/harmful
   delete; patch.dict owns restore.

3. test_self_hosted_git_url.py's _reload_github_service() deletes+reimports
   services.github_service without restoring, leaking a fresh OwnerType enum;
   test_fork_to_own's owner-guard then compares OwnerType by identity across the two
   modules and silently skips ("DID NOT RAISE HTTPException"). Added an autouse
   fixture that snapshots/restores services.github_service* per test.

Verified: full unit suite green under all three CI seeds (12345/67890/99999),
3646 passed / 15 skipped each (locally on py3.14; CI runs py3.11).

Co-authored-by: Eugene Vyborov <eugene@beingluminous.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vybe vybe merged commit b3b7078 into main Jul 8, 2026
28 of 29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment