Skip to content

fix(operator-queue): machine keys read the queue without a person's identity (abilityai/trinity-enterprise#715) - #3090

Merged
vybe merged 4 commits into
devfrom
feature/715-queue-person-emails
Sep 29, 2026
Merged

vybe merged 4 commits into
devfrom
feature/715-queue-person-emails

Conversation

@webmixgamer

Copy link
Copy Markdown
Contributor

Summary

Agent, system and other machine keys could read the email of the person who answered an ask, and of the person it was for, from the operator queue's reads. The file write-back also put the answerer's email into the agent's own ~/.trinity/operator-queue.json. This PR closes both paths.

  • Machine view is an allowlist. GET /api/operator-queue, /{id} and /agents/{name} give every non-person principal _MACHINE_ROW_FIELDS. That excludes responded_by_id, responded_by_email, addressed_to_email (file rows too), disposed_by_email and resolved_to. A column added later stays out until someone classifies it. A person (a JWT session, or the person's own user-scoped key: is_person_principal) still reads the whole row, so the web UI is unchanged.
  • The platform's heads-ups about a person never reach a machine. Two rows carry a person in their text: the ent#499 problem report (a client's email and their verbatim comment, which ent#366 withholds from the rated agent) and the ent#308 inbox-collision alert (client addresses, and an id built from an email slug). Machine reads skip both, and the item route answers 404 (the same body as a missing id). portal-inbox-collision- joins the reserved prefixes, so that alert also stays out of the agent's file, its resume turn and the Execution Context "Ended asks" line.
  • The file write-back carries the answer, not who gave it. No responded_by on delivery or reconstruction. The prompt now says response, response_text and responded_at. Entries written before keep their old value.
  • The clear-resolved /ws trigger is the count only. cleared_by is gone: a fleet-level trigger reaches every connection, and any key can mint a ticket.

Changes

  • src/backend/routers/operator_queue.py: _MACHINE_ROW_FIELDS + _for_principal (allowlist, rows-about-a-person skipped), the item-route 404, the /ws payload
  • src/backend/services/operator_queue_service.py: is_about_a_person, the reserved portal-inbox-collision- prefix, the two write-back lines
  • src/backend/services/platform_prompt_service.py + config/trinity-meta-prompt/prompt.md: one sentence
  • src/backend/client_portal/service.py: docstring (the residual it stated is closed)
  • src/mcp-server/src/types.ts: comment only; MCP tools pass the narrower response through
  • Docs: operating-room.md, requirements/security.md, requirements/core-agent.md, architecture/{api-endpoints,database,workspace}.md, docs/requirements/OPERATOR_QUEUE_OPERATING_ROOM.md (also removes an internal address from an example payload), a learnings fragment, and docs/security-reports/cso-diff-2026-09-29-ent715-queue-person-fields.{md,json}

No DB migration, no dependency, Docker or CI change.

Test Plan

  • New: cd tests && pytest unit/test_ent715_queue_person_fields.py -v. 63 passed. It pins the machine key set as a literal across seven machine principals plus one real minted agent key; the person side doubles as the classification guard. It also drives both platform emitters for real, runs the write-back through the bug(operator-queue): a pending approval can sit unseen for days — the human's card goes stale while the container↔platform sync stays silent #2915 harness, and pins the /ws payload and the composed prompt.
  • Mutations, each reverted from a scratch copy and red: the allowlist (42), the row filter (3), the item 404 (3), each write-back line (1 each), the reserved prefix (2), the trigger (1), the prompt (1), and a new unclassified column (6, the classification test).
  • Existing tests: one assertion flipped (test_ent611_native_ask.py, where a file row's addressee is now withheld). Full unit suite on the branch merged with dev: 19049 passed, 0 failed.
  • /verify-local --skip-agent --skip-unit: backend image build + import smoke, isolated boot + health, and integration (70 passed) are green. Its sequential unit stage fails 40 tests in test_ent666_objective_join.py, test_ent477_* and test_retention_floor.py. That exact set also fails on the base commit e87163125: order-dependent pollution that predates this change, not a regression. Every one passes on its own and under -n auto.
  • /cso --diff: no findings at the 8/10 gate.
  • Eyeball on localhost (main checkout detached at the branch commit, the ask answered through the UI):
    • Person (JWT) list: the answered ask carries responded_by_email, and both platform alerts are listed.
    • Agent key, same list: 0 alerts; person-field keys []; 0 example.com hits (3 in the JWT view).
    • GET /api/operator-queue/{alert_id}: agent key 404 (the same body as a nonexistent id), JWT 200.
    • UI: the Resolved card still names the answerer; the alerts are listed under Needs Response.
    • /ws on Clear All: {"type":"operator_queue_cleared","data":{"scope":"resolved","count":72}}, no cleared_by.

Not in this PR (registered)

  • The answer and ending wakes still attribute the turn to the person (source_user_email), and the executions API returns that field to keys. This is the platform-wide "who triggered this turn" attribution, out of scope by ruling and registered as debt.
  • REST row breadth for an admin-owned agent key (MCP narrows to {self} ∪ permitted, REST does not) stays with abilityai/trinity-enterprise#719.
  • notifications_cleared.cleared_by carries the same /ws pattern in another router; registered as debt.

Fixes abilityai/trinity-enterprise#715

🤖 Generated with Claude Code

webmixgamer and others added 4 commits September 29, 2026 15:51
…dentity (Abilityai/trinity-enterprise#715)

The operator queue's reads handed agent-, system- and other machine keys the
email of the person who answered an ask and of the person it was for, and the
file write-back put the answerer's email into the agent's own queue file.

- GET /api/operator-queue, /{id} and /agents/{name}: a non-person principal
  gets an ALLOWLIST projection (`_MACHINE_ROW_FIELDS`) with no
  responded_by_id/responded_by_email, addressed_to_email (file rows too),
  disposed_by_email or resolved_to. A person (JWT or their own user-scoped
  key, `is_person_principal`) still reads the whole row.
- The platform's heads-ups ABOUT a person (the ent#499 problem report: a
  client's email and verbatim words; the ent#308 inbox collision: client
  addresses, an email-slug id) are not returned to a machine at all (skipped
  on list, 404 on the item route). `portal-inbox-collision-` joins the
  reserved prefixes, so that alert also stays out of the agent's file, its
  resume turn and the Execution Context "Ended asks" line.
- The file write-back no longer writes `responded_by` (delivery and
  reconstruction); the prompt says `response`, `response_text` and
  `responded_at`.
- The clear-resolved /ws trigger is the count only (no `cleared_by`).

Tests: tests/unit/test_ent715_queue_person_fields.py (63). Mutations, each
reverted from a scratch copy and red: the allowlist (42 red), the row filter
and the item 404 (3 each), both write-back lines (1 each), the reserved
prefix (2), the trigger (1), the prompt (1), and a new unclassified column
(the person-side classification test, 6 red). One existing assertion flipped:
test_ent611_native_ask.py (a file row's addressee is now withheld).

Registered residuals: the answer/ending wakes still attribute the turn to the
person (source_user_email on the execution row); REST row breadth for an
admin-owned agent key stays with trinity-enterprise#719.

Fixes Abilityai/trinity-enterprise#715

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… read rule (Abilityai/trinity-enterprise#715)

The ent#499 problem report's "the agent never sees the words" now holds through the
agent's own key too (workspace.md, core-agent.md). /cso --diff: no findings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is reserved (Abilityai/trinity-enterprise#715)

/review I1 on #3090. `_ABOUT_A_PERSON_ID_PREFIXES` said it was a subset of
`_RESERVED_ID_PREFIXES`, and nothing checked that. The machine reads key on the first
tuple; the file write-back, the resume wake and the ended-asks line key on the
second. A heads-up prefix outside the reserved set would be hidden from a machine's
read yet still written into the agent's file.

Mutation: a third, unreserved prefix on the tuple turns exactly this test red
(1 failed, 63 passed); the emitter-driven tests stay green, which is the gap.

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

@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.

merge-train: batch validated on train/20260929-1926 (#3095)

@vybe
vybe merged commit cace70a into dev Sep 29, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants