Skip to content

DO NOT MERGE — merge train: 3067,3040,3043,3028 - #3070

Closed
trinity-ability wants to merge 23 commits into
devfrom
train/20260929-0815
Closed

trinity-ability wants to merge 23 commits into
devfrom
train/20260929-0815

Conversation

@trinity-ability

@trinity-ability trinity-ability commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

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

webmixgamer and others added 19 commits September 27, 2026 11:31
…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.
…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>
@vybe vybe added the ui PR touches the frontend UI — triggers Playwright e2e tests label Sep 29, 2026
@vybe vybe changed the title DO NOT MERGE — merge train: 3067,3040,3043,3044,3028 DO NOT MERGE — merge train: 3067,3040,3043,3028 Sep 29, 2026
@vybe
vybe force-pushed the train/20260929-0815 branch from 8a3308f to bda6f2b Compare September 29, 2026 08:33
@vybe

vybe commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Train green; members #3067, #3040, #3043, #3028 merged individually. Closing the integration surface.

@vybe vybe closed this Sep 29, 2026
@vybe
vybe deleted the train/20260929-0815 branch September 29, 2026 08:46
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.

4 participants