DO NOT MERGE — merge train: 3067,3040,3043,3028 - #3070
Closed
trinity-ability wants to merge 23 commits into
Closed
trinity-ability wants to merge 23 commits into
trinity-ability wants to merge 23 commits into
Conversation
…ipts, re-asks (Abilityai/trinity-enterprise#611, PR B) An agent raises an ask through ONE platform call instead of appending to ~/.trinity/operator-queue.json: POST /api/agents/{name}/operator-queue and the MCP tool ask_operator, both self-acting (the agent comes from the key; any other principal gets 403 agent_identity_required). - ask_service.raise_ask is the create entry point of the ask sink, the seam the gate path (trinity-enterprise#164) calls with raised_by="gate". Static validation with named 422s; replay BEFORE every time-dependent check; the 15-minute deadline floor; the own-expired-predecessor link and the reask_requires_link refusal; role addressing; the #1632 rate buckets; an atomic per-agent create (replay, depth cap and insert under one lock: a PG advisory transaction lock, SQLite BEGIN IMMEDIATE); one `raised` audit row and a thin operator_queue_new trigger. - The receipt names the role, never an email. A replay returns the first receipt with `differs`; `wakes_on_ending` says whether the owner's wake opt-in is on. - Native rows sit outside the file contract everywhere. The poller skips a file entry that re-uses a native id before any branch reads it (logged once), creates through create_operator_queue_item_with_outcome and counts only what it inserted, and names the file channel's deprecation once per agent per process. - The Operator Communication prompt leads with ask_operator by its bare name; the file is the fallback for two releases (the meta-prompt copy and the agent guide mirror it). - The Operations cards link a re-ask and the expired ask it re-raises. Rebuilt on dev after #3023 squash-merged (f6dfb00): this commit is the net delta of the former PR B commits 19b2e53..3a2dffd (the feat above, the two review fixes: the `raised` audit row is the agent's, and reads never name a native ask's resolved person; plus the docs), replayed onto dev unchanged. Same 47 files and the same added/removed lines as B's old diff against #3023's final head dfcb7c0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Abilityai/trinity-enterprise#611, PR B) The native ask path reads OPERATOR_QUEUE_PROPOSAL_MAX_BYTES (default 8192), but no compose file passed it into the backend container, so setting it in .env had no effect on a deployed instance. The 14 sibling operator-queue caps are all in the three compose files and .env.example; this one now is too. It sits right after OPTIONS_MAX_BYTES, the same position as the constant in operator_queue_service.py. So prod and hosted keep identical environment lists, and the #2280 parity guard compares them in order. The deploy guide's env table and requirement §26.10 name it as well. Found by /validate-pr §4.9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le agents (trinity-enterprise#713) Requirements EVT-001 and the agent-event-subscriptions flow describe the access scoping of GET /api/events before the handler change: roster filter pushed into SQL with limit applied after it, one uniform 403 on a targeted agent outside the caller's roster, admin unrestricted.
Records the rule in security.md §20.10 ahead of the change: POST /api/mcp/keys and POST /api/mcp/keys/ensure-default take a signed-in (JWT) session, and ensure-default writes the same key_create audit row as the create route.
…person, require_interactive Two allowlist rules over mcp_scope, both fail-closed on a principal without one: PERSON (a JWT session or the person's own user-scoped key, via is_person_principal) and INTERACTIVE (a JWT session only). The Depends forms make a route's rule a one-line signature change an AST guard can see. Both also refuse a principal carrying vouched_source_agent. The ent#611 ask-endings detail and predicate are unchanged, and pinned so.
…in SQL (trinity-enterprise#713) EventSubscriptionOperations.list_events takes agent_names: None is unrestricted, an empty list returns [] before any query (never IN ()), and a non-empty list becomes source_agent IN (...) in the WHERE clause, so LIMIT applies after the filter. The facade passes it through; existing callers are unchanged. Tests run the production query on the tmp SQLite schema: limit after the filter, no engine call on an empty list, None unrestricted.
…(trinity-enterprise#713) Non-admin callers get the accessible roster (db.get_accessible_agent_names, the roster both event sockets use) passed to list_agent_events, so the filter and limit run in SQL; admins are unrestricted. A targeted source_agent goes through assert_agent_access and must also be in the roster, giving one uniform 403 for a nonexistent, inaccessible or soft-deleted-but-shared agent. An empty source_agent means no source filter; the roster still applies. Tests drive the real handler with real principals over users, ownership and sharing rows written by the real db writers.
…ne refs (trinity-enterprise#713) tests/registry.json gains the unit file; the agent-event-subscriptions flow points at the current lines of list_all_events, list_agent_events and list_events.
POST /api/mcp/keys (every scope) and POST /api/mcp/keys/ensure-default now take Depends(require_interactive): a credential minter is at least as strict as the principal it produces. The existing per-scope checks stay as they were. ensure-default also writes the same key_create audit row as the create route, so every created key is attributable. Tests go through the real get_current_user with seeded key rows and assert the mcp_api_keys row count is unchanged on every refusal.
…t registry mcp-api-keys.md: POST /keys and ensure-default are signed-in-session only; ensure-default is audited as key_create. Adds the feature-flows index row, a learnings fragment and the two new test files to tests/registry.json.
…inity-enterprise#713) GET /api/events resolved the roster with `email or ""`, so a non-admin principal with a blank email was matched against any other account whose email is also blank. Return an empty roster instead, as the /ws socket does for a falsy email.
… non-admin (trinity-enterprise#713)
…ed prefix, raised_by on the receipt, primary's empty answer, rate check first (Abilityai/trinity-enterprise#611, PR B) From the changes-requested review and the merge-train note on #3028: - `gate-` is a platform-reserved id prefix. A gate raise (`raised_by="gate"`, `channel="gate"`) must use it, and an agent's raise may not. So an agent can no longer pre-create the gate's id and have the gate's raise answered as a replay of the agent's own proposal. And it is no longer woken to redo an action the gate resumes itself, because `is_platform_minted` keys on the reserved tuple and gates both wakes. The receipt now says who raised the ask (`raised_by`), and an unknown raiser or channel is a ValueError. - A provider that answers "nobody" (`[]`) for `primary` now sends the ask to the operators with `resolved: false` (the ent#606 fallback). The owner stands in only when no provider answers at all. - The rate check runs right after the replay, before the re-ask scan and the owner lookup read the database. A refused ask now spends a token; a replay still spends none. - The native insert takes ON CONFLICT DO NOTHING. The file poller does not take the per-agent lock, so on PostgreSQL a file entry re-using the id can land between the replay check and the insert. That now comes back as a replay of the file row, not a 500. - `get_my_ask` names a re-ask's predecessor by request_id, as the receipt does. The row still stores its uuid. - Docstrings: a gate raise shares the agent's rate and depth budget (it fails closed), and ending observers run in-process and at most once. `channel` is documented as file|mcp|gate. Tests came first: 18 went red before their fixes. New tests also run the real owner lookup, and a real minted key through the route: key resolution, the identity check and the ephemeral fence. Each of 13 mutations of the fixed lines turns a test red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ision is made (Abilityai/trinity-enterprise#611, PR B) The prompt tells agents to put the exact action in `proposal` "so the operator can verify what they are approving", but nothing rendered it. An agent that followed the prompt moved the one thing an approver must check out of view (review on #3028). - `components/operator/QueueProposal.vue` renders it read-only. The rows come from `utils/operatorQueue.js::proposalRows`: top-level fields in the agent's order, text verbatim, anything else as compact JSON. It is agent-authored, so the values are text only (never v-html). The component uses gray tokens only, and the list scrolls inside its own box. - It appears on every card that offers the decision: the open Operations card (above the collapsed context, never behind "Show details"), the /m ops card and the Workspace ask. - The Workspace projection (`WorkspaceAsk`) now includes `proposal`; `context` stays out. An ended ask keeps its proposal, so the person sees what was decided. - `QueueItemDetail.vue` has no importer and reads store state that no longer exists, so it is left alone. Mounted specs cover the three surfaces, the rule and the text-only rendering (12 tests), with two projection tests beside them. Each of 7 mutations of the wiring turns a test red. Checked by eye on a local instance in light and dark. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…est, gate rows' receipts, replay and re-asks stay with their raiser, proposal on Resolved (Abilityai/trinity-enterprise#611, PR B) From the approving review on #3028: - `test_an_agent_may_not_author_a_gate_id` now has its own agent and ids. A sibling test creates `gate-call-abc` on the same agent, and the unit database keeps rows between tests. The test failed on 2 of the 3 nightly seeds and passed on the one seed PR CI uses. It now passes under 20 seeds, the nightly three included. - A gate row's receipt no longer promises a wake: `wakes_on_ending` is false on a platform-minted row, which `_wake_filer` skips. - `channel="gate"` if and only if `raised_by="gate"`; anything else is a ValueError. - A gate raise is never answered as a replay of a row it did not raise, at either replay site (409 `request_id_taken`). Example: a file row named `gate-…` written before the prefix was reserved. - The re-ask guard (C6) and the re-ask link compare with the SAME raiser's expired asks. An agent's expired ask no longer blocks, or gets linked by, a gate raise, and the reverse. `list_expired_proposals_for_agent` gains a `raised_by` filter. `_raiser_of` treats a row older than the column as the agent's own, and a platform alarm as nobody's. - The Operations Resolved card keeps the proposal too, so the record says what was decided (the Workspace already did). Tests came first: 9 went red before their fixes, and 2 more pin the legacy rows. Each of 12 mutations of the fixed lines, both replay call sites included, turns a test red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s globals (Abilityai/trinity-enterprise#611, PR B) The full unit island at nightly seed 67890 failed the staged-race test on PostgreSQL's sqlite leg with `created` where `replayed` was expected. The holds were installed with `monkeypatch.setattr(dbq, "make_insert", …)` after `import db.operator_queue as dbq`. In a full-suite run a sibling test can leave a stand-in for `db.operator_queue` that re-exports the real class. The patch lands on the stand-in, while the real method reads its own module dict, so the hold never fires. Reproduced with a plugin that installs such a stand-in: - the committed staged-race tests fail; - the concurrent-cap test passes even with the lock removed. All four holds now go through `create_native_item.__globals__` (test_1632's `_patch_engine`), and the table object comes from the same dict. With the stand-in installed, the fixed tests pass, and removing the lock turns the cap test red again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve tests/registry.json: keep both appended entries (ent#713 and ent#530).
Top hotspot stable at client_portal/service.py (9918, +4.8%, inside noise band). Files >800 lines 64->71 and high fan-out files 12->14 both moved outside the +-10% band, consistent with the recent feature-landing pace. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 task done
vybe
force-pushed
the
train/20260929-0815
branch
from
September 29, 2026 08:33
8a3308f to
bda6f2b
Compare
This was referenced Sep 29, 2026
This was referenced Sep 29, 2026
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 #3067, #3040, #3043, #3028. Never merged; members merge individually once green. (#3044 ejected after round 1: its route census does not classify routes already on dev.)
🤖 Generated with Claude Code