Repository navigation
DO NOT MERGE — merge train: 3635,3304,3346 - #3639
Closed
trinity-ability wants to merge 41 commits into
Closed
trinity-ability wants to merge 41 commits into
trinity-ability wants to merge 41 commits into
Conversation
…low a link; runtime config files join the write lists Requirements (content-files §13.1, GUARD-002 §28) and the file-routes bullet in the architecture area file, ahead of the code. Refs Abilityai/trinity-enterprise#819 Refs Abilityai/trinity-enterprise#823
…review One credential constant feeds the write list and a new owner-tier read set: the credential paths plus .git/config, the /proc and /dev spellings, the Trinity-managed copies (.trinity/git-credential, .trinity/backup/*) and the secret file classes (.kube/config, .config/gcloud/*, *.key, *.pem, *.p12, *.pfx). Download and preview refuse an owner-tier path unless the caller is a person who passes the owner tier (owner or admin); the PERSON gate runs first. A NUL byte is refused with 400 before the agent is called, and the agent is sent the normalised path that was checked. The 403 body says what a teammate can still do and whom to ask. Refs Abilityai/trinity-enterprise#819
Every refused owner-tier read writes one file_read_refused row, and every allowed one by anyone other than the owner's own signed-in session writes file_read_allowed (AUTHORIZATION, routed endpoint, normalised path, rule). A person is filed as actor_user; an agent key as its agent; other keys by scope and key, never as the owner. The audit is best-effort: a failure never changes the answer. Refs Abilityai/trinity-enterprise#819
~/.claude.json, ~/.claude/.credentials.json, ~/.gemini/settings.json and ~/.tmp/codex/* (the Codex runtime's login and MCP config) are refused on write, mkdir and delete at the backend and are owner-tier to read. Deleting .tmp or .tmp/codex through the Files routes is refused, since they hold the Codex login. Refs Abilityai/trinity-enterprise#823
path_deny gains ~/.claude.json, ~/.claude/.credentials.json, ~/.gemini/settings.json and ~/.tmp/codex/*, and the image-build smoke gains a ~/.claude.json row, so the build proves the baseline. Refs Abilityai/trinity-enterprise#823
… its file routes PROTECTED_PATHS and EDIT_PROTECTED_PATHS gain .claude.json and .credentials.json. PUT, mkdir and DELETE also refuse any resolved target inside the runtime config paths (Claude Code's two login files, Gemini's settings.json, Codex's .tmp/codex), so a link elsewhere in the home that points at one is refused too; DELETE refuses a directory above one. The home is a module constant so the handlers run over a temporary home in tests. Snapshot refreshed. Refs Abilityai/trinity-enterprise#823
The agent server opens a download or preview path one component at a time with O_NOFOLLOW and serves the descriptor it opened, so the checked file is the file read. A path that is a link, or passes through one, is refused with 403 resolved_path_mismatch for every caller, the owner and the platform's own reads included. The backend turns that refusal into a file_read_refused audit row and a structured 403; a platform read that meets one logs a warning naming the file. The fence is resolved-path containment, not a string prefix. A source scan pins that Trinity itself creates no links in an agent home. Refs Abilityai/trinity-enterprise#819
…reads The backend probes each agent container once, per container and image, for the agent server's no-link read (ent#708-style: grep its source for the read function). Until an agent is stopped and started on the current image, download and preview by anyone below the owner tier are refused with 403 agent_restart_required; the owner and admins are unaffected and never trigger the probe. A missing target fails safe and is remembered for that image; an exec that raises or times out fails safe and is probed again on the next read. Each refusal is logged with a running count. Refs Abilityai/trinity-enterprise#819
The download and preview store calls parse a text or blob error body back
into {detail, code, path}, so a structured refusal reads as its message
instead of "Request failed with status code 403". The Credentials panel
reads first and opens the editor only on content or a 404; any other
failure keeps it closed and shows the message in an InlineError beside
the file list. The Files panel's Download shows the message too.
Refs Abilityai/trinity-enterprise#819
…ads, no-link reads and runtime config files file-browser (owner-tier set, link refusal, older-image refusal, audit, error bodies and what the UI shows), agent-guardrails (path_deny + 4), agent-sharing and credential-injection, the index; user docs say what a teammate on a shared agent can and cannot do and whom to ask, that the Files tab does not open links, and that the runtime login files and .tmp are not editable there. Refs Abilityai/trinity-enterprise#819 Refs Abilityai/trinity-enterprise#823
The Work card reads pipeline files over its own httpx client, so the platform-read warning in agent_client.read_file never reached it. A 403 body is now read under the same byte budget and passed to that warning. Refs Abilityai/trinity-enterprise#819
The 403 agent_restart_required message now reads: "This agent needs a restart to apply an update. Ask the agent's owner or an admin to stop and start it in Trinity." It is shown on agents not yet on the current image. Refs Abilityai/trinity-enterprise#819
The older-image check on download and preview now exempts the same caller the credential tier serves: a person who passes the owner gate (the agent's owner, or an admin). Agent keys, the owner's own included, are refused with 403 agent_restart_required like any caller below the owner tier until the agent restarts on the current image; on a verified image they read as before. Refs Abilityai/trinity-enterprise#819
The read-policy probe caches a verdict only when grep answered: exit 0 (verified), or exit 1 or 2 with no output (token or file absent; grep -qs prints nothing). The exec helper reports a Docker fault as exit 1 with text in its output, so that, a timeout or any other code now reads as "not verified" for this read, is logged, and is probed again next time. Refs Abilityai/trinity-enterprise#819
A guard reads the probe's path and token from the backend module, maps the path through the base image's COPY of ./agent_server to the repo file, and asserts that file defines the token as a module-level function (matched on the def with comments stripped). Renaming or moving the read function now fails here instead of on every agent after the next image build. Refs Abilityai/trinity-enterprise#819
The objective join reads template.yaml and objectives over the agent door directly, so the platform-read warning in agent_client.read_file never reached it. A 403 from that read now goes through the same warning, which names the file when the agent server refused it as a link. The read still reports the file as unreadable. Refs Abilityai/trinity-enterprise#819
Download reads at most the file's size when it was opened, and the preview stream stops at the Content-Length it declares. A file that grows after the open (an agent log, say) is served at its opened size, so the body never runs past the declared length. Refs Abilityai/trinity-enterprise#819
…s nothing Quick Inject reads the current .env before merging. A coded 403 on that read now has a mounted case: the server's message is shown and nothing is injected; an uncoded failure keeps the generic message. Refs Abilityai/trinity-enterprise#819
A Python file that fails to tokenize, or is not UTF-8, was skipped by the scan for link creation in an agent's home. It is now scanned as raw text, comments included, so it can only add a finding, never hide one. Refs Abilityai/trinity-enterprise#819
… tier The requirements, architecture and feature-flow lines on the file routes now state the rule: reading credential paths and the secret file classes is owner-tier, the credential, alias and Trinity-copy patterns are on the write deny list, and the per-verb tier follows separately. Refs Abilityai/trinity-enterprise#819
…onfig test files Registry entries for the four new test files and the two extended ones. Refs Abilityai/trinity-enterprise#819 Refs Abilityai/trinity-enterprise#823
…nk read The file-browser flow describes the PERSON gate before the owner tier, the older-image check (only a person who is the owner or an admin skips it; only grep's quiet answer is cached), the descriptor-based agent-server reads that stop at the opened size, the three platform readers that name a linked file, and the current line references. The sharing and credential flows match the panel and the restart message. Refs Abilityai/trinity-enterprise#819
… teammate gets a restart The architecture bullet and requirements now say a conclusive older-image probe is cached per container and image, and that only a person who is the owner or an admin skips the check (agent keys included in the refusal). Requirements state reads stop at the size the file had when opened. The Files user doc quotes the restart message and lists every owner-tier path; the sharing doc says whom to ask for a restart and that credential management is for the owner or an admin. Refs Abilityai/trinity-enterprise#819 Refs Abilityai/trinity-enterprise#823
…it failure and probe agent keys Route-level tests over routers.agent_files.router for download and preview: the agent is sent the normalized path, a failing audit store changes no answer (403 for a shared user, 200 for an admin), and the owner's agent-scoped key gets agent_restart_required on an unverified image. Refs Abilityai/trinity-enterprise#819
… it spawned — on the raw backend routes too (trinity-enterprise#629) An agent-scoped MCP key resolves to its OWNER carrying the owner's role (Invariant #8), so every access/owner predicate said yes for any sibling the owner holds — on an admin-owned install, every agent. The agent_permissions edge the MCP layer checks (P-02) did not exist one hop down: with its injected key and TRINITY_BACKEND_URL, agent A could chat with, task, loop, read the history and logs of, or replace the git token of any sibling B. Ruling (operator, 2026-10-06): fix the hole. Narrowed in the shared gate, not per endpoint (the #1890 lesson): - dependencies.agent_may_reach(current_user, target, manage=): * use tier — get_authorized_agent[_by_name], assert_agent_access, A2A inbound: itself, an edge target, an agent it spawned with its current key; * manage tier — get_owned_agent[_by_name], assert_agent_owner: itself or an agent it spawned. An edge means "may call", never "may manage". Outside its reach reads as the uniform 404 (#186). Every other principal is unchanged. The edge read fails closed, like the MCP layer's. - get_skill_managed_agent_by_name keeps the plain owner fence: skills.manage (ent#596) IS the grant to manage the owner's other agents. - Every inline can_user_access_agent / can_user_share_agent call (16 files) ANDs the reach in; nevermined's admin shortcut no longer skips it. - The operator queue had its own predicate (admin role → no filter): an agent key now always gets a set narrowed to its reach. - MCP: every `baselined: ENT629` row in TOOL_ACCESS_POLICY is `enforce` on its agent parameter; the "tool-surface gate only" notes now name the backend fence. Collection reads (the agent list, event-subscription list, MCP key list) are not narrowed: they disclose names, never act on an agent. Tests: tests/unit/test_ent629_agent_reach.py — the reach truth table on a real schema, every helper, A2A inbound, the operator queue, the skills capability keeping its reach, the dispatch routes resolving through the narrowed gate, and an AST guard that fails the build on an inline access check without the reach (exemptions carry their reason). Mutation-checked: reach always-true, edge-as-manage, the access dependency dropping it, and an inline site dropping it each turn tests red. Docs: Invariant #8, security.md §5, P-02, the J10 docstring, mcp-server.md and the affected feature flows and requirements. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…b; regression fixes (trinity-enterprise#629) From the regression run over every unit file touching a changed module (14 new failures vs dev, all traced): - The skill fence's owner check is its own seam again, `get_capability_owned_agent_by_name` (not narrowed: the capability is the grant), so the ent#596 recorder patches a named function. - files.py: a skills.manage holder writing a sibling's skills path reaches it without an edge (`_may_reach_for_path`); every other path is narrowed. The capability gate still decides the write. - share_agent_file checks "an agent key shares only its own volume" before the (now narrowed) owner gate, so a sibling gets the specific refusal. - The depth suites (#2806, #2973) seed the A→B edge their sibling calls need. - The edge read fails closed (a fixture schema without agent_permissions refused instead of raising — the enterprise assignment suites). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts with #3236 (ent#164) and #3208/#753 (skill gates): imports and the MCP access table keep both sides; files.py keeps both helpers. Semantic fix: ent#629's narrowed owner gate refused what ent#164 and ent#753 grant — an agents.manage holder reconfiguring its owner's other agents, and a skills.manage holder setting a sibling's skill gate (7 tests red after the textual merge). A capability whose grant IS the reach keeps it, bounded to agents the owner OWNS: - agent_may_reach(manage=True) admits an agents.manage holder on an owner-owned target (_manages_owners_agent, fail-closed); reads are not widened. - The skill-gate writes resolve the agent with get_capability_owned_agent, the path-dependency form of the capability owner fence, after require_person_or_capability has refused a non-holder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pawned, read directly (trinity-enterprise#629) dev's ent#611/#715/#815 queue suites (29 tests) went red after the merge: an admin-owned agent key's scope was built from the owner's accessible-agent list and then filtered agent by agent, so a self or permitted agent without an ownership row (every one in those fixtures) dropped out. The scope now starts from {self} ∪ the agent's edges — the MCP's own rule — bounded by a non-admin owner's set, plus the owner's agents it spawned. The edge read fails closed. The router also gets the logger it was missing. ent#715's heads-ups fixture seeds the agent key's edge to the agent it reads, so the test still checks that person rows are withheld, not that the whole queue is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts (docs and test registry only, both sides kept): - docs/memory/feature-flows.md: dev's new index rows above this branch's ent#819/ent#823 row - docs/user-docs/agents/agent-runtimes.md: the ~/.tmp/codex note, then dev's Gemini and failure-reporting sections - tests/registry.json: this branch's four ent#819/ent#823 entries and every dev entry
# Conflicts: # docs/memory/architecture.md # src/backend/routers/agents.py # src/mcp-server/src/access.ts
…ity-enterprise#629)
Merge-train review (2026-10-08, 2026-10-10): the narrowed gate revoked the
cross-agent reach that ent#164 capabilities exist to grant. A
`schedules.manage` holder got 404/403 on a sibling's schedule routes, an
`instructions.manage` holder on its instruction files and git reset, and an
`agents.manage` holder on `PUT /{name}/model`, because `agent_may_reach`
never saw which capability the route had checked.
- The two capability fences (`capability_fence`,
`require_person_or_capability`) record the target they admitted on the
request's principal (`User._capability_reach`, a private attribute), after
the grant check and the owner bound. `agent_may_reach` honours that record
on both tiers. This replaces `_manages_owners_agent`, which widened every
owner check for an `agents.manage` holder on every route.
- `files._may_reach_for_path`: the path exception covers `instructions.manage`
as well as `skills.manage`, needs the grant for each file class the path
touches, and is bounded to agents the owner OWNS (`owner_owns_agent`). It
used to reach any agent the owner could access.
- `loops._check_loop_access`: reach is checked before the admin and
initiator shortcuts, so an agent key cannot read or stop a sibling's loop
by id.
- Operator queue: the spawned set is one query
(`list_agents_spawned_by_key`), not two lookups per held agent.
Also merges dev: the agents.py activity routes stay deleted (#3483), and
access.ts takes dev's owner constants alongside this PR's enforce rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sal (trinity-enterprise#629)
J10 `test_without_permission_an_agent_cannot_read_or_stop_a_loop_on_another`
failed on journey-smoke. Since the backend checks an agent key's reach before
the admin shortcut in `_check_loop_access`, `GET /api/loops/{id}` answers 403
for a loop on an agent outside the caller's reach. `get_loop_status` and
`stop_loop` resolve the loop through that read before their own gate, so the
caller got a raw `API error (403)` instead of "Loop '<id>' not found or not
accessible" and the hint.
A 403 on the read now returns the same uniform body as the MCP gate, with the
same log line and audit row. The stop is still never sent. A 404 (no such
loop) keeps the backend's own shape, as the existing test pins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… owner fence (trinity-enterprise#629) The backend regression diff caught 4 new failures in `test_3435_credential_tool_gates::test_agent_key_is_refused_human_only_even_for_its_owner`. With the narrowed owner fence, `get_owned_agent_by_name` answers an agent key on a sibling with the uniform 404, so the handler-body `reject_agent_principal` never ran. The agent was told "Agent not found" instead of the rule it hit. `dependencies.human_only` is the route-level form of `reject_agent_principal`. On export and import it now runs before the ownership dependency. The refusal depends on the principal alone, before any lookup, so it discloses nothing about the target (#186). The inline call stays as belt-and-braces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the #3346 review (trinity-enterprise#629) Addresses webmixgamer's review of f9cd838. 1. `skills.manage` reached another user's agent through the skills routes on an admin-owned install. `get_skill_managed_agent_by_name` went through the unnarrowed owner fence, which `can_user_share_agent` answers True for any agent when the owner is admin; the files tab refused the same target. The skills fence now applies the bound every capability fence applies (grant, then `owner_owns_agent`, then record the reach) and returns the narrowed `get_owned_agent_by_name`. `can_manage_agent_skills` takes the same bound. The skill-gate writes use `get_owned_agent_by_name` (`_gate_writer` records first), so `get_capability_owned_agent[_by_name]` and `_owned_agent_or_404(narrow=False)` are gone: one mechanism. 2. The MCP `enforce` gate read the lenient permissions list, which is built from the Docker-filtered fleet. A ghost with no container, a child being recreated, a spawned child whose edge was removed, and every call during a Docker fault all read as "not permitted". New `GET /api/agents/{caller}/reach/{target}` answers from the edge or the spawn record in the database; `TrinityClient.isAgentPermitted` reads it and fails closed. 3. security.md no longer says collection reads "disclose names, never act": fleet-level routes still answer an agent key with its owner's reach, some of them write, and some return task text and emails. A private follow-up tracks them. 4. Tests for the review's surviving mutations: - credential export's `human_only`; - the queue's non-admin bound; - `deleted_at` in the spawned set; - a schedules holder through the real route via TestClient (same principal object for the fence and the path dependency); - another user's agent on the skills fence; - the reach endpoint. Each mutation now fails a test. 5. An agent key never gets the admin "every user" chat history or sessions view: "may call" is not "may read everyone's conversations with it". Also: learnings fragment (capability bound in one helper). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…inity-enterprise#629) The backend regression diff caught `test_probe_is_a_persons_even_for_a_key_that_may_manage_skills[agent_key_with_skills_manage]`. Since the #3346 review, an agent key's `skills.manage` reaches only agents its owner OWNS (`owner_owns_agent`), not every agent `can_user_share_agent` admits. The fixture stubbed "may share" but never said who owns `gated-agent`, so the bound read "unknown owner" and refused. The test's premise is unchanged: the key's owner owns the agent, so the manage check admits it and the probe is still refused for a key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s nothing to name (#3533) An opener with no topic ("yo", "Run sleep 40, then say done.") made the title model describe its own inability, and that sentence was stored as the chat title ("Unable to generate title", "Unclear Initial Contact"). Both title prompts now define the reply for "nothing to title": exactly NONE. `_is_no_title` treats the sentinel and a closed set of failure phrasings as no title; `_generate_thread_title` then returns None, so nothing is stored and the derived first-line title stands. The outcome is recorded as ok, because the generator answered and three greetings in a row must leave the operator health record quiet. `title_source` stays unset, so the existing one retry on the next exchange still runs. Fixes #3533 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…action with #3346) #3346 (trinity-enterprise#629) bounds an agent key to its own agent, its permission edges and the agents it spawned. #3304's read-tier tests use a 'sibling' agent key against 'shared-agent' with only the access layer stubbed, so seven of them hit the new reach refusal before the owner-tier gate they test. Stub reach alongside access; reach refusal on the file routes stays pinned by test_ent629_agent_reach.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 13, 2026
fix(workspace): a chat keeps its fallback title when the generator has nothing to name (#3533)
#3635
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integration surface for #3635, #3304, #3346. Never merged; members merge individually once green.
One interaction found in assembly: #3346's agent-key reach refusal trips 7 of #3304's owner-tier tests, whose sibling agent key has no permission edge. The train carries a 5-line fixture stub. It will be pushed to #3304's branch after #3346 lands, together with the one-line import union in services/agent_service/files.py.
🤖 Generated with Claude Code