Skip to content

feat(auth): an agent changes its own shape only with a grant (ent#164) - #3236

Merged
vybe merged 4 commits into
devfrom
feature/ent164-self-change-grants
Oct 7, 2026
Merged

vybe merged 4 commits into
devfrom
feature/ent164-self-change-grants

Conversation

@dolho

@dolho dolho commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Implements the re-scoped abilityai/trinity-enterprise#164 (2026-10-02): grants, not per-action approval. An agent key resolves to its owner (Invariant #8), so until now any agent could reshape any agent of the same owner. ent#596 closed this for skills. This PR extends the same capability-grant seam to the rest of an agent's shape.

Capability Covers Exempt
schedules.manage create / update / delete / enable / disable a schedule, plus its webhook and webhook secret the agent's own schedules (#2996 kept); trigger
instructions.manage CLAUDE.md, AGENTS.md and .claude/** except skills, through the file routes; git/reset-to-main-preserve-state —
agents.manage durable create, delete, deploy-local, systems/deploy, PUT /model, and read-only / resources / timeout / public-channel-model / guardrails spawning or discarding an ephemeral agent (ent#69)

How it behaves:

  • Without the grant: the agent gets a named 403 (*_management_not_permitted).
  • What the 403 says: raise an ask with ask_class: permission-request. Approving that ask does not grant anything; an admin grants the permission in Settings.
  • Audit: every refusal is audited as capability_refused.
  • Not affected: humans and trinity-system are never fenced.
  • Still person-only: autonomy, api-key-setting, capabilities, capacity and rename.
  • Calibrating companions: cannot be granted instructions.manage (422 calibrating_agent, ent#663).

New routes (the backend for ent#756's Settings toggles):

  • GET /api/agents/{name}/capability-grants: owner-level. Returns all four capabilities, held or not, with who granted each and when.
  • PUT /api/agents/{name}/capability-grants/{capability} {granted}: admin and interactive, idempotent, audited.

⚠️ Behaviour change on upgrade

After deploy, no agent holds the three new capabilities. An orchestrating agent that does any of the following gets a 403 until an admin grants the permission:

  • creates durable agents;
  • writes another agent's schedules or instructions;
  • deletes or deploys agents;
  • reconfigures an agent.

Self-scheduling and ghost spawning keep working.

Decisions taken (2026-10-05)

Tests

  • tests/unit/test_ent164_self_change_grants.py (40 tests):
    • fences read off FastAPI's dependant graph;
    • behaviour per capability class;
    • ghost and own-schedule exemptions;
    • instruction-path classification;
    • the create endpoint refusing before anything is created;
    • the calibrating refusal.
  • test_2996_owner_config_person_only.py: real keys through the real get_current_user and grant table.
    • An ungranted agent gets the named refusal.
    • A granted agent changes the stored value.
    • The grant does not open the PERSON-only writes.
    • Grant-route tests: only an interactive admin can grant; a repeat grant is idempotent; both grant and revoke are audited.
  • test_2996_human_only_routes.py: new census class person_or_grant; the GET route is listed agent_callable with a reason.
  • Mutation battery: I broke the schedules fence, the instructions gate, the calibrating check, the own-agent exemption, the ghost-create exemption and the create-gate call site in turn. Each mutation turned the suite red.
  • Broad related run (169 unit files): green except test_ent679_*. Those fail identically with this change stashed (ModuleNotFoundError: payments_py.a2a.inband, local venv only).

Not in this PR

  • ent#756: the Settings toggles UI.
  • ent#753: the approval map.
  • Known limit: the fence covers platform routes. An agent can still edit its own container's files with its own tools.

Fixes abilityai/trinity-enterprise#164

🤖 Generated with Claude Code

Re-scoped 2026-10-02: grants, not per-action approval. Three capabilities
join skills.manage (ent#596) in the closed set:

- schedules.manage: schedule create/update/delete/enable/disable and the
  webhook routes on ANOTHER agent. An agent's own schedules stay free
  (#2996); trigger is not fenced.
- instructions.manage: CLAUDE.md, AGENTS.md and .claude/** except skills
  through the file routes, plus git reset-to-main-preserve-state. Not
  grantable to a calibrating companion (ent#663).
- agents.manage: durable create, delete, deploy-local, systems/deploy,
  the chat model, and the read-only/resources/timeout/
  public-channel-model/guardrails writes (now person-or-grant). Ghost
  spawn and discard are exempt (ent#69).

Without the grant, an agent gets a named 403
(*_management_not_permitted). The 403 tells the agent to raise a
permission-request ask and says an admin grants the permission in
Settings. Every refusal is audited. Autonomy, api-key-setting,
capabilities, capacity and rename stay person-only.

New: GET/PUT /api/agents/{name}/capability-grants[/{capability}], with
PUT limited to an interactive admin and audited; this is the backend for
ent#756. The route census gains a person_or_grant class.

Related to Abilityai/trinity-enterprise#164

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dolho
dolho requested a review from vybe October 5, 2026 14:30
@vybe

vybe commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

merge-train (2026-10-05, evening run): not on this train. It rides the next one once fixed. CI is green at 02a21145.

Blocking

  1. The instructions.manage fence on the file routes can be bypassed. The cause is in _normalize_user_path (src/backend/services/agent_service/files.py:52-64), which _touches_instructions (:127) relies on, and it predates this PR. Details, proof and the one-line fix are in abilityai/trinity-enterprise#792, kept off this thread because the repo is public. This PR should land after that fix or carry it, with the path-classification parametrize extended to match.

Needs a decision before it lands

  • A second door for "reset the git workspace". POST /api/agents/{agent_name}/git/pull with strategy=force_reset (routers/git.py:339, MCP git_pull) discards local changes and is unfenced; git/sync with pull_first is the same class. The refusal text says "resetting its git workspace", so either fence them or narrow the claim.
  • Instruction writes outside the path list: CLAUDE.local.md, home-level .claude.json, a symlink inside the target workspace, and the DB-stored prompts (PUT /{agent_name}/public-prompt, PUT /{name}/voice/prompt).
  • "Covers every reconfigure route" holds for the five in agent_config.py plus /model. Owner-tier writes elsewhere stay agent-reachable without a grant, for example circuit-breaker, mcp-exposed, folders, file-sharing, git/auto-sync, github-pat, access-policy, and git/freeze-schedules-if-failing on the schedules side. A holder can also lift its own read-only and guardrails. Fence them or list them as out of scope in the flow doc.
  • The calibrating check is grant-time and holder-only (capability_grant_service.py:83): a ready holder can rewrite a calibrating sibling's CLAUDE.md, and a grant made before the stamp persists.
  • Open-core placement. ent#164 has no recorded OSS-core ruling; the 09-11 comment there calls it a recommendation pending confirmation. That needs to be on the issue before this merges.

Mechanical, for the same push

  • No closing keyword: the body says "Implements" / "Related to". It completes ent#164 in substance, so Fixes abilityai/trinity-enterprise#164.
  • test_ent164 asserts fixed route lists; test_ent596 derives the writer set from the router and pins it. A new schedule write route without the fence stays green today.
  • Coverage is wiring-only (dependant-graph name match) for eight of the nine schedule routes, PUT /model, deploy-local, systems/deploy and git/reset-to-main-preserve-state. A validator probe with a real agent key got the named 403 on all of them, so the behaviour is right; it is just not pinned.
  • The grant-route test's agent key belongs to a role=user owner, so the role check alone refuses it. Drive an admin-owned agent key and an admin user-scoped key.
  • docs/memory/architecture/security.md §5a and §6 are untouched and no longer match; the flow doc shows the 403 key as error: where the payload key is message.
  • MCP tool descriptions for the schedule, agent, system and git tools do not mention the grant (ent#596 did this for skills.ts).

What held up: the grant route is human-only against every key type tried, the own-agent and ghost exemptions are not spoofable, the table already exists on both migration tracks, and trinity-system is unaffected.

@vybe vybe added the status-needs-fix PR has an unaddressed review/validation finding; cleared by the author's next push (#2815) label Oct 5, 2026

@AndriiPasternak31 AndriiPasternak31 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review from my side (lane C). Re-checked vybe's train notes against 02a21145 and they all still hold; adding three things that aren't in that comment.

Ran locally: 385 passed across the PR's tests plus the adjacent auth guards (test_293 / test_186 / test_2094 / test_1310 / the #2996 census + runtime). Mutating require_person_or_capability to skip the capability call turns 6 tests red, so that path is pinned.

Blocking

  1. abilityai/trinity-enterprise#792 is still open, and it defeats instructions.manage on the file routes (and skills.manage). I reproduced it against this branch's _touches_instructions. This needs to land after #792 or carry the fix, with the classification parametrize in test_ent164 extended to cover it.

  2. agents.manage on the five reconfigure routes reaches further than the PR says. Those handlers authorise through assert_agent_owner → can_user_share_agent, which returns True for any role == "admin" (db/agent_settings/sharing.py:286). An agent key carries its owner's role. So on a default admin-owned install, one grant lets that agent change read-only / resources / timeout / public-channel-model / guardrails on every agent on the instance, other users' included. security.md §5a currently says "a holder gains no reach beyond its owner's agents". Separately, nothing stops a holder targeting itself: it can switch off its own read-only mode and clear the owner's extra_bash_deny / extra_path_deny / disallowed_tools. These five were PERSON-only under #2996, so this is the one place the PR loosens rather than narrows. Suggest: (a) bound the holder to agents its owner actually owns (owner_username equality, not the admin short-circuit), and (b) keep read-only-off and guardrail-relaxing PERSON-only, at least when target == caller. If the team wants the fleet-wide reach, then security.md and the ent#756 toggle copy need to say so.

  3. Open-core ruling. The only statement on ent#164 is the 09-11 "recommendation … pending operator confirmation". It needs an explicit OSS-core line from the operator before merge (CLAUDE.md: enterprise-tracker features are gated by default).

Same push, non-blocking

  • The 403's _ASK_FOR_IT (dependencies.py:1795) tells the agent to raise an ask "with ask_class 'permission-request'". The ask surface has no such field. ask_service._normalize reads type ∈ {approval, question, alert} and friends, and MCP ask_operator's zod schema has no ask_class, so it is silently dropped. ask_class only exists on seat decisions. Suggest type: "question" with the permission named in the title. The same wording is in requirements/auth.md:110 and the flow doc.
  • The 403 also says "an admin grants it in the agent's Settings". That's ent#756, which isn't built. With the upgrade-time 403s, the only remedy after deploy is an admin JWT PUT by hand. Either say how to grant via the API until #756 ships, or merge them together.
  • There are now two doors that grant skills.manage with different audit verbs: PUT /api/skills/agents/{name}/skill-manager → skill_manager_grant, and the new generic route → capability_grant. Either refuse skills.manage on the generic route or unify the verb.
  • The capability table says agents.manage covers "delete". In practice enforce_agent_spawn_scope (agents.py:739) still limits an agent to agents it spawned, which is good. Worth stating so nobody reads the grant as "delete any agent".
  • MCP descriptions: #3238 just trimmed deploy_local_agent to Claude Code's 2,048-char cap, so the grant sentence vybe asked for has to fit inside that.
  • Lane C: frontend-e2e skipped on the PR (no ui label). Branch is 4 behind dev.

What looks solid: the grant route is admin + interactive (no require_role("admin")), the fence refuses on the principal before any target lookup (no #186 differential), and the own-agent and ghost exemptions can't be spoofed (the ghost path is entitlement-gated and ghosts can't hold grants). The grants table is already CASCADE in agent_cleanup.

dolho and others added 2 commits October 7, 2026 11:41
…al copy that works (ent#164 review)

- require_person_or_capability: after the capability, the target must be
  owned by the holder's owner (owner_username equality, 404 otherwise). The
  reconfigure handlers authorise through can_user_share_agent, which admits
  any admin, so one grant on an admin-owned install reached every agent.
- read-only and guardrails stay person-only when the holder targets itself.
- The 403 tells the agent to raise an ask of type 'question' naming the
  permission (ask_class does not exist on the ask surface) and names the
  grant route, since the Settings toggles (ent#756) are not built.
- skills.manage granted through the generic route audits with the
  skill-manager verb, so both doors share one.
- instructions.manage copy names reset-to-main-preserve-state instead of
  claiming every git reset; the flow doc lists force_reset pull, sync
  pull_first, the instruction writes outside the path list, the other
  owner-tier writes and the grant-time calibration check as out of scope.
- Leading-slash paths (ent#792, fixed on dev by #3276) added to the
  instruction-path classification parametrize.
- Grant route tested against an admin-owned agent key and an admin
  user-scoped key; reach and self bounds tested through real keys.
- MCP descriptions for the schedule, agent, system and reset tools name the
  grant (budget test passes).
- security.md §5a/§6, auth.md and the flow doc updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot removed the status-needs-fix PR has an unaddressed review/validation finding; cleared by the author's next push (#2815) label Oct 7, 2026
@dolho

dolho commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the 10-05 train notes (vybe) and @AndriiPasternak31's review in e64aaa56e. I also merged dev, which brings in the ent#792 fix (#3276).

Blocking

  1. ent#792: fixed on dev by fix(files): leading slashes no longer skip the deny list and fences; DELETE carries the deny list (abilityai/trinity-enterprise#792) #3276 and now merged here. The instruction-path parametrize gains six leading-slash cases (//home/developer/CLAUDE.md, ///…, //…/.claude/settings.json, the skills subtree, and the ancestor deletes). All of them pass.
  2. agents.manage reach:
    • (a) require_person_or_capability now limits a holder to agents its owner actually owns. It compares owner_username and returns a 404 otherwise, so the admin short-circuit in can_user_share_agent no longer applies. Test: an admin-owned holder gets a 404 on another user's agent, on all five routes, through real keys.
    • (b) read-only and guardrails stay person-only when the holder targets itself (self_person_only=True). Tested on both routes. A holder can still change its own resources, timeout and public-channel model, and can change a sibling's read-only and guardrails.
    • Updated security.md §5a and the flow doc to say this route set is the one place the PR loosens bug(auth): agent-scoped keys can toggle their own agent's autonomy — PUT /api/agents/{name}/autonomy lacks reject_agent_principal #2996, and how it's bounded.
  3. Open-core ruling: still open. This one needs the operator, so I haven't touched it.

Same push

  • The 403 now says: raise an ask of type 'question' whose title names the permission. It names PUT /api/agents/{agent}/capability-grants/{capability} as the way to grant until ent#756 ships. Same wording in requirements/auth.md and the flow doc.
  • Two doors: when skills.manage is granted through the generic route, it now audits as skill_manager_grant/skill_manager_revoke, the same verb as the skill-manager route.
  • The flow doc says delete is still limited to agents the holder spawned (enforce_agent_spawn_scope).
  • MCP descriptions for create/update/delete/toggle_agent_schedule, create_agent, delete_agent, deploy_local_agent, deploy_system and reset_to_main_preserve_state now name the grant. The 2,048-character budget test passes.
  • Git reset claim: I narrowed it. The refusal names reset-to-main-preserve-state, and git/pull force_reset and git/sync pull_first are listed as out of scope. I didn't fence them.
  • Instruction writes outside the path list, other owner-tier writes, and the grant-time-only calibrating check: listed under "Known limits — out of scope" in the flow doc.
  • Body now says Fixes abilityai/trinity-enterprise#164. The flow doc shows the 403 key as message.
  • Grant-route test adds an admin-owned agent key and an admin user-scoped key. Both are refused.

Not done: pinning the schedule route lists against the router (test_ent596 style), and behaviour tests for the eight schedule routes, PUT /model, deploy-local, systems/deploy and reset. Those are still wiring-only.

🤖 Generated with Claude Code

…ry fenced route (ent#164 validation)

The owner bound landed on the five reconfigure routes only. Every
`capability_fence` route (schedule writes, PUT /model, delete, git
reset) still authorised through `can_user_access_agent`, which admits
any admin, so an admin-owned holder of `schedules.manage` wrote
schedules on every agent on the instance and the agents.manage
refusal text overclaimed. One helper, `_refuse_unless_owners_agent`,
now runs inside the fence after the capability check, so a non-holder's
403 still comes first (#186). Pinned at the unit level across four
fences and through real keys and the real grant table; the schedule
write set is now derived from the router, not only a literal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vybe

vybe commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

/validate-pr (lane C, 2026-10-07) at e64aaa56 → fix pushed as a42dcf22.

Held up: 373 passed locally across the PR's tests + test_ent596 / test_293 / test_186 / test_1310; four mutations (drop the owner bound, fence never enforces, ignore self_person_only, _touches_instructions always False) each turned the suite red. §4 security greps clean, enterprise-disclosure grep clean, no schema change (the grants table is on both tracks since ent#596), docs updated in all three tiers, Tier 1 green, dev-ci green.

Fixed in a42dcf22: the owner bound covered the five reconfigure routes only. Every capability_fence route (schedule writes, PUT /model, delete, git reset) authorises through can_user_access_agent, which admits any admin — so an admin-owned schedules.manage holder wrote schedules on every agent on the instance, and the agents.manage refusal's "reaches only agents its owner owns" overclaimed. The bound is now one helper (_refuse_unless_owners_agent) that runs inside the fence after the capability check (non-holder 403 first, #186). Pinned at the unit level across four fences and through real keys; the schedule write set is now derived from the router. Flow doc + security.md §5a updated.

Open-core ruling: posted on ent#164 (OSS-core), which closes @AndriiPasternak31's item 3; items 1–2 were addressed in e64aaa56. ui label added so frontend-e2e runs on the PR (lane C).

Still wiring-only (author's "not done"): behaviour tests for the eight remaining schedule routes, deploy-local, systems/deploy. Not blocking.

🤖 Generated with Claude Code

@vybe
vybe dismissed AndriiPasternak31’s stale review October 7, 2026 14:30

Items 1-2 addressed in e64aaa5 (ent#792 merged; owner bound + self bound with real-key tests); item 3 resolved by the OSS-core ruling on ent#164 (2026-10-07). Reach bound extended to every fence in a42dcf2.

@vybe vybe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated lane C (report in the comment above). Reach bound extended to every fenced route in a42dcf2; OSS-core ruling recorded on ent#164.

@webmixgamer

Copy link
Copy Markdown
Contributor

Heads-up from #3347 (the skill gate map, abilityai/trinity-enterprise#753): it carries a verbatim copy of this PR's require_person_or_capability and _path_agent, with the same name, signature and placement (right after can_manage_agent_skills). The skill-gate writes use it with self_person_only=True.

  • Whichever merges second keeps this PR's version and drops the duplicate in dependencies.py. That is the only conflict between the two branches.
  • If the review here changes the helper's behaviour, the gate routes should take the change too. tests/unit/test_ent753_gate_routes.py pins their principal matrix.
  • Census: the gate routes use the helper through a module-level variable (Depends(_gate_writer)) and are listed in AGENT_CALLABLE. If this PR's walker classifies them as person_or_grant, those three entries move.

@vybe
vybe merged commit a687ecc into dev Oct 7, 2026
28 checks passed
webmixgamer added a commit that referenced this pull request Oct 7, 2026
#3236 landed the shared `require_person_or_capability` / `_path_agent` this
branch carried a verbatim copy of. Resolved `src/backend/dependencies.py` by
keeping dev's version (with `_refuse_unless_owners_agent` and
`capability_fence`) and dropping the duplicate; `get_skill_gate_readable_agent_by_name`
stays. The gate routes' census entries still classify (test_2996 green); the
flow doc no longer calls the helper a copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vybe pushed a commit that referenced this pull request Oct 7, 2026
…terprise#753) (#3347)

* feat(skill-gates): the per-agent skill gate map (trinity-enterprise#753)

Stores, per agent, which skills need approval before they run and which kind
of person approves. The dispatch check (ent#751) and the in-container hook
(ent#752) read it through skill_gate_service.list_skill_gates, which stops
returning {} and reads the table — the gate is live from here.

- Storage: agent_skill_gates (agent x skill -> approver primary|approver,
  deadline 1-168h, origin set|library_default|cleared), both tracks (SQLite
  entry + Alembic 0093 on 0092), CASCADE AgentRef. A failed read raises, so
  the check refuses (gate_unavailable) instead of reading "nothing is gated".
- REST GET/PUT/DELETE /api/agents/{agent_name}/skill-gates[/{skill_name}] and
  MCP list_skill_gates / set_skill_gate / clear_skill_gate. Writes: a person
  who owns the agent or is an admin, or a skills.manage holder on an agent its
  owner owns, never on itself (require_person_or_capability, copied verbatim
  from #3236). An agent key reads its own gates; set_by is withheld from
  machine keys. Named 422/409 refusals; OSS offers only `primary`.
- Library `approval: recommended` defaults follow assignment state: applied
  before a package is delivered (existing holders backfilled at start/sync),
  removed once the skill is unassigned and its package has gone; a cleared
  default leaves a tombstone. The library can tighten, never loosen.
- Removal: an explicit gate goes only when a person unassigns the skill and
  its package removal completed. An agent's unassign, the system key, a
  deferred removal, Sync, start and the sweep keep it (gates_kept), so
  unassign-then-reassign cannot launder a gate away. Own-skill gates are
  sticky until cleared. This departs from the issue's "removing a skill
  removes its gate" for own skills and non-person removals, by the recorded
  scope decisions.
- Marker ordering (#752): the fail-closed marker is written before an
  agent's first gate (decided under the per-agent lock) and removed after
  its last; stopped agents are never exec'd.
- Audit on every change (who; via ui/api/orchestrator/system; trigger).

Tests: tests/unit/test_ent753_*.py, src/mcp-server/src/tools/skill-gates.test.ts;
every write path, auth bound and ordering line mutation-checked (each red).
Security: docs/security-reports/cso-diff-2026-10-07-ent753-skill-gate-map.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(skill-gates): partial writes merge from a mapping, not a sentinel (trinity-enterprise#753)

verify-local's unit stage (random order) found two things the per-file runs
could not:

- `write_skill_gate` marked an unsent field with a module-level `KEEP =
  object()` compared by identity. After another test re-imported `db.*`, the
  caller's KEEP and the DB module's were different objects, so the sentinel
  itself reached SQL ("type 'object' is not supported"). The write now takes
  `changes` — only the fields to set — so there is nothing to compare by
  identity and a reload cannot turn "keep" into a value.
- The service and route fixtures patched `import services.x as X`, which reads
  the package attribute; after another test's re-import that can be a stale
  copy while the code under test imports from sys.modules (learning
  2026-10-05). The fake Docker exec then missed, the real exec ran, and the
  marker tests saw no marker events. The fixtures now patch
  `importlib.import_module(...)`; reproduced with a stale package binding
  (red), then green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skill-gates): architecture catalog rows for the gated-skill check (#3344 item 2)

`skill_gate_requests` in database.md, `skill_gate_service.py` and
`skill_gate_map_service.py` in backend.md's service catalog, the MCP gate
result handling in mcp-server.md, and the dispatch gate's person-only endings
in security.md — each two lines or fewer, beside the gate map rows this
branch already added. Item 2 of #3344; items 1 and 3 stay open there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(skill-gates): the PUT response says whether the approver is reachable, as GET does (trinity-enterprise#753)

Found smoke-testing the routes on the verify-local sibling stack: GET reported
approver_reachable=false for an owner with no email while the PUT response's
gate carried null (only its warnings said approver_unassigned).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: trinity-ability <309458136+trinity-ability@users.noreply.github.com>
dolho added a commit that referenced this pull request Oct 8, 2026
….manage + create_project / import_project (trinity-enterprise#588)

Re-scope of ent#588 (ruling 2026-09-29, ent#661): on Trinity, /project-init
creates the project through the platform. ent#661 planned the agent right as
"a new capability beside skills.manage; changing members or visibility stays
person-only" — this is that capability, on the #3236 seam:

- db/capability_grants: `projects.manage` joins the closed set — start a
  Workspace project ON BEHALF OF the agent's owner (owner = creator and
  first member, agent = steward, members-only) and import one of the
  agent's OWN folder projects. People and visibility are never grantable.
- dependencies: its named refusal (`project_management_not_permitted`)
  with the ask instruction.
- Settings → Permissions to change itself: a fifth toggle, "Start projects".
- MCP: `create_project` and `import_project` (license-blind, like the rest
  of projects.ts), each saying the grant is needed and what the owner keeps.

The enforcement and the create/import themselves are in the enterprise
projects module (abilityai/trinity-enterprise, same issue), which checks
projects.manage before any read. Until this lands on dev the enterprise
routes refuse every agent — the capability is not in the closed set — so
the merge order is this PR, then the enterprise PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dolho added a commit that referenced this pull request Oct 8, 2026
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>
vybe pushed a commit that referenced this pull request Oct 9, 2026
….manage + create_project / import_project (trinity-enterprise#588)

Re-scope of ent#588 (ruling 2026-09-29, ent#661): on Trinity, /project-init
creates the project through the platform. ent#661 planned the agent right as
"a new capability beside skills.manage; changing members or visibility stays
person-only" — this is that capability, on the #3236 seam:

- db/capability_grants: `projects.manage` joins the closed set — start a
  Workspace project ON BEHALF OF the agent's owner (owner = creator and
  first member, agent = steward, members-only) and import one of the
  agent's OWN folder projects. People and visibility are never grantable.
- dependencies: its named refusal (`project_management_not_permitted`)
  with the ask instruction.
- Settings → Permissions to change itself: a fifth toggle, "Start projects".
- MCP: `create_project` and `import_project` (license-blind, like the rest
  of projects.ts), each saying the grant is needed and what the owner keeps.

The enforcement and the create/import themselves are in the enterprise
projects module (abilityai/trinity-enterprise, same issue), which checks
projects.manage before any read. Until this lands on dev the enterprise
routes refuse every agent — the capability is not in the closed set — so
the merge order is this PR, then the enterprise PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vybe added a commit that referenced this pull request Oct 9, 2026
….manage + create_project / import_project (Abilityai/trinity-enterprise#588) (#3339)

* feat(projects): an agent can start a project for its owner — projects.manage + create_project / import_project (trinity-enterprise#588)

Re-scope of ent#588 (ruling 2026-09-29, ent#661): on Trinity, /project-init
creates the project through the platform. ent#661 planned the agent right as
"a new capability beside skills.manage; changing members or visibility stays
person-only" — this is that capability, on the #3236 seam:

- db/capability_grants: `projects.manage` joins the closed set — start a
  Workspace project ON BEHALF OF the agent's owner (owner = creator and
  first member, agent = steward, members-only) and import one of the
  agent's OWN folder projects. People and visibility are never grantable.
- dependencies: its named refusal (`project_management_not_permitted`)
  with the ask instruction.
- Settings → Permissions to change itself: a fifth toggle, "Start projects".
- MCP: `create_project` and `import_project` (license-blind, like the rest
  of projects.ts), each saying the grant is needed and what the owner keeps.

The enforcement and the create/import themselves are in the enterprise
projects module (abilityai/trinity-enterprise, same issue), which checks
projects.manage before any read. Until this lands on dev the enterprise
routes refuse every agent — the capability is not in the closed set — so
the merge order is this PR, then the enterprise PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* merge-train: five capabilities, not four — stale wording after projects.manage (ent#588) — mechanical, per the merge-train note on the PR

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Eugene Vyborov <1073874+vybe@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ui PR touches the frontend UI — triggers Playwright e2e tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants