Repository navigation
DO NOT MERGE — merge train: 3833,3730,3820,3882,3848,3817,3847 - #3887
Closed
trinity-ability wants to merge 26 commits into
Closed
trinity-ability wants to merge 26 commits into
trinity-ability wants to merge 26 commits into
Conversation
…ove never clips at phone width (#3664, #3663) "People this agent serves" (AgentAssignmentsSection.vue), found in the 1.0 release testing: #3664 — two pieces of stale state. - The "Shared with … so they can use it in the Workspace." note was a plain string reset only at the next Add. It now records who it is about and goes when the fact does: that person's row leaves the roster, or the people list, re-read after an unshare in the list below, no longer has them. - The platform-role badge read the users list before the people list. The users list was guarded by `!usersLoaded`, so it loaded once per SPA session, while the people list is re-read on every visit. A role changed in Settings showed in the list below but not on the row. roleOf() now reads the people row first, and the users list is re-read each time the section starts (the list on screen stays until the new one lands), which also refreshes the pickers' role labels. #3663 — the kind select (min 9.5rem) and Remove sat in a strip that could not wrap, so Remove was clipped at the card edge on narrow screens. Below `sm` the strip now spans the row and wraps. The select gives way from 9.5rem to an 8.5rem floor (still wider than "Collaborator") before Remove goes under it. In the app the row is one line from about 392px; narrower (375px included), Remove wraps under the select. Nothing clips at any width, and `sm` and up is unchanged. Tests: mounted specs for both note paths (row removed, unshared) and the negative case; the badge from a re-read people list and from a re-read users list; the strip's wrap classes. Each fix piece was mutated separately and its test goes red. Fixes #3664 Fixes #3663 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… windows (#3302) Every operator surface derived its counts and lists from one capped page of GET /api/operator-queue, so past the cap the numbers stopped being true and nothing said so. Because the route lists pending rows first, 200 pending asks also pushed every ended ask out of the desktop window (the Resolved tab read empty), and 100 did the same to /m's "Recently ended". - utils/operatorQueueRead.js (new) is the one read path. readPendingQueue reads every pending ask: one 500-row offset page, then a keyset walk past it; a walk that cannot finish says so (complete: false), a null total is never shown as 0. readQueueSnapshot keeps today's first page as the common path (one request for a normal queue) and reads the ended window after the pending section only when that page cannot hold it. - Operations store: counts live in refs with explicit writers, never derived from a window; reads are single-flight with one trailing rerun over the bounded client; a write generation re-reads a read an answer overtook; off the Operations page a queue event reads only the two counts (limit=1 -> total). Clear All over the route's 500 ids runs in batches packed by agent, so each agent's asks still end in one event. - Operations subtitle states the Resolved window ("340 resolved · latest 200 shown") and a partial pending read ("703 pending responses (500 shown)"). - NavBar reads the two counts on mount (deferred a tick, skipped while a list is live), so the Ops badge is true on every page. - Fleet-grid chips, /m, the agent Overview "needs attention" count and the self-change permission-request match read the same way; a partial read keeps the last complete chip map and never prunes /m's typed answers. - tests/unit/test_3302_queue_list_order.py pins the offset-mode order the reader relies on (pending before ended, ended newest-ending first, total over the WHERE, flags independent of the filters). Red with the fix reverted: every new spec ran against the pre-fix code first (reader 20, store 7, Operations subtitle 3, /m 3, fleet grid 3, grants 2, Overview 1, NavBar 1 failing); 14 mechanism mutants (walk/replace, prune guard, keep-last-map, write generation, re-read, single-flight, in-flight join, agent batches, counts-only refresh, NavBar list-live skip, /m poll skip, Overview floor, bounded reads x2) and the backend ORDER BY mutant each turn at least one test red. Fixes #3302 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ery door and read (trinity-enterprise#780)
The agent's own readback (`get_my_ask`) already refuses a row about a person
on dev: the check landed with trinity-enterprise#751. Nothing pinned it. A
narrowing to `gate-` rows would have left every test green while the inbox
collision alert and the problem report leaked again. Tests only; no product
code changes.
- Door sweep (`TestPlatformRowsAboutAPerson`): every handler on both queue
routers is driven with a real minted agent key and the real system key
acting as trinity-system, over both heads-ups filed through their real
emitters.
- The readback and the item route answer a heads-up exactly like an
unknown id.
- Both lists leave the heads-ups out and still list the key's own ask.
- respond, cancel and bulk-cancel refuse a machine by name.
- A raise with a heads-up's id is refused 422 before the replay lookup.
- A withdraw is a 404 that leaves the row pending.
- /stats returns numbers only.
The issue's test is the `get_my_ask` cases (AC2, AC3, AC5).
- §6 door census (AC4): every handler is placed, and the #2996 walker and
the live routers must agree on the set. Every door that returns rows must
consult `is_about_a_person` (checked by the shape of the code), and a
person-only door must refuse before it reads.
- §7 read census: every read of a queue row in the backend is classified,
along with every queue accessor, ending observer, table spelling and
data module (the read-side twin of test_1677).
Mutation: 16 call-site mutants, each red for the intended test.
- Readback check: reverted, name kept with the branch dropped, narrowed and
shadowed. Each turns the four get_my_ask sweep cases red, plus the
predicate census.
- A new unplaced handler.
- `_for_principal` dropped from a door and from its own filter.
- The person check moved below the read.
- The raise replay lookup moved above validation (four raise sweep cases).
- A new unclassified read, a new accessor, the table reached through a re-export,
a new ending observer and a new data module importing the table.
Seeds 12345, 67890, 99999, 1, 4242 and 31337 all pass. 201 neighbouring unit
files run with and without this file show no failure unique to it.
Fixes Abilityai/trinity-enterprise#780
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pe smoke (#3302) agent-detail-request-dedupe.spec.js counts requests by pathname, so the three operator-queue reads an Agent Detail load now makes — the NavBar's two badge counts (status=pending&limit=1, +priority=critical) and the Overview's per-agent count (agent_name=…&status=pending&limit=1) — read as one GET fetched three times. They are three different questions to one route. For that route only, the key is path + query string, so a read repeated with the SAME query is still caught (replayed offline: the CI page load yields no offender; a repeated count read or a repeated agent GET is still flagged). The run's other hit (/api/agents/<name>/compatibility x2, attempt 1 of 3 only) is pre-existing timing: CompatibilityPanel fetches on mount and again on an agentName change, and this branch touches neither it nor AgentDetail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stead of redirecting (#3821) Caddy's automatic HTTP->HTTPS redirects for the on-demand wildcard site are routed by host pattern and run ahead of the user's `http://` block. `https://*.*.*.*` matches a dotted IPv4 host, so `http://<tailnet-ip>` got a 308 before the `@private remote_ip` handler saw it; only a dotless MagicDNS name reached it. When PRIVATE_NETWORK_CIDRS is set, the render now adds `auto_https disable_redirects`, leaving the `http://` block as the only route on :80, and its fallback redirect is 308 (the code the automatic redirects used). Caddy answers ACME HTTP challenges ahead of every route, so certificate issuance is unaffected. With the variable unset the file renders byte-identical to before. Fixes #3821 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ers mark the row on screen (#3302) Second review of #3817: - A trailing read queued behind an awaited one erased the "your response was not recorded" notice before it rendered: respondToItem awaited its own run, wrote the notice, and the queued read's prologue cleared `error` a microtask later. A 409 means someone else ended the ask, and that ending broadcasts, so this was the common case. coalesced() now settles every caller — the one that started the chain and any that joined it — when the chain drains (capped at 3 reads; a trigger past the cap starts the next). The same ordering covers bulk cancel's partial-failure notice. - respondToItem updates and counts the row on screen: a read that landed while the POST was out had replaced the row objects, so the badge dropped while the card still rendered pending. - readQueueSnapshot path C: a partial walk whose total could not be verified asks a one-row count where the pending section ends, so the Resolved window is no longer falsely empty. - e2e request-dedupe: the NavBar's two badge-count keys are refreshed on every queue WebSocket event, like /api/notifications/count, and are exempt; the Overview's per-agent count still counts once per mount. - Two WebSocket assertions are exact again (the two count reads, by params). - Docs: the flow's sample uses the bounded client; "drops a read" now says "reads it again"; learnings fragment on settling coalesced callers. Red without each fix: the not-recorded notice (chain starter and joiner paths), the replaced-row answer, and the partial-walk Resolved window each fail; a mutant returning the joiner its own run survived the first test, which is why the join test exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nswers an unverified total exactly (#3302 review) From the review on #3817: - loadCounts drops its result when a view renders the list by the time it lands. A count read started on another page by a queue event could land after the Operations list read and paint an older total over it, so the badge disagreed with the list until the next poll. The existing write-generation guard there is now tested too (removing it was green). - Agent Overview: an unverified `total` is answered exactly instead of showing a page length as if it were the count. With no more rows the page's own rows are the count (the withheld row was not this viewer's); with more, the agent's pending asks are read once and counted, after every other write, and only written while the page still shows that agent. Never the number already on screen (the panel is reused across agents). - Fleet grid: a complete chip map outranks partial reads for 5 minutes (OP_QUEUE_PARTIAL_GRACE_MS), then the partial map is shown, so an outage that makes every read partial cannot freeze the chips forever. - readQueueSnapshot: a comment on the ended-read margin (more than 50 asks ending during the walk stay listed until the trailing read corrects it). - Flows updated for the three rules. Red without each: the stale count read over a live list, the overtaken count read, the Overview fallback (two tests), the mid-fallback agent switch, and the grace period each turn a test red when reverted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and accepts a positional 404 (review)
Review follow-up on trinity-enterprise#780.
- The §6 shape check now states the expected shape when it fails, instead
of printing only the handler key: an `if` on the module's own
`is_about_a_person(row)`, with `row` from a `db.` lookup in the handler, and
one `raise HTTPException(404, ...)` above the first `return`. It also says
that moving the lookup or the predicate into a helper changes the rule.
- `HTTPException(404, ...)` written positionally is accepted, like the
`status_code=404` keyword.
- The table-read detector also sees a function-local alias
(`from db import tables as t`), `metadata.tables['operator_queue']` and
`table('operator_queue')`, each with a meta-test case. An audit
`target_type='operator_queue'` literal is the negative control.
- Learnings fragment: 55 call sites in 45 functions, not "45 sites".
Checked with the reviewer's three refactors as mutants. The positional 404
passes. The lookup moved into a helper and the predicate wrapped in one both
fail, with the new message. A function-local alias in a real service now fails
the bypass check. The readback mutants (reverted, kept-but-dropped, narrowed,
shadowed) are still red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a fresh module copy (#3390) #3468 (36711e7) already fixed the 40 failures #3390 named: putting the pre-#3468 test_deploy_writable_templates.py back still fails 41 tests (ent666 x21, ent477 git_refresh_hook x10, ent477 definitions_endpoint x6, retention_floor x4). This closes the class those 40 belonged to. A test that evicts `pkg.mod` and re-imports it rebinds the package attribute `pkg.mod` to the fresh copy, and monkeypatch.delitem, patch.dict and every hand-written snapshot restore put back only the sys.modules entry. The rest of the run then holds two copies of one module: a later test patches one while the code under test reads the other. Five recurrences so far (#3272, #3470, #2572, trinity-enterprise#752, #2859). A probe over the whole unit island found 64 files that leave such a split. - tests/module_split_guard.py: scan() finds a split, Guard repairs it, unbind() drops a stale binding, evict() is the eviction that leaves no split behind (a package takes its loaded submodules with it, #2859). - tests/unit/conftest.py runs the Guard around every test, after the item's last teardown: - a split whose sys.modules entry is still the pre-test module is repaired, by putting the attribute back to its pre-test value; - a split the test made by replacing the entry fails the file's last test, judged once its module fixtures have torn down, so a module fixture that restores its own copy is not a leak; - collection-time splits are left alone. Healing them toward sys.modules broke four test_ent692_skill_sources_mcp tests, because the entry was a test file's private copy. - TRINITY_SPLIT_GUARD=off switches the guard off. It is the control arm of the guard's end-to-end test. - The conftest's own _POP_PREFIXES pops now drop the package binding of backend modules. Before, `from services import task_execution_service` (pull_coordination_service.py) kept handing out the popped copy. - tests/lint_sys_modules.py stops recommending raw monkeypatch.delitem. A delitem of a dotted or computed name is now flagged outside tests/unit/, where nothing repairs it (3 root files baselined), and the hint points at evict(). - Fixed the two files that left a copy in sys.modules for good: test_session_operations (db.sessions) and test_audit_retention_prune (db.audit). Both now use monkeypatch.setitem. - test_deploy_writable_templates.py: #3468's inline loop becomes evict(). The other 62 polluting files need no edit: the guard repairs them. Proof on the real class, with the pre-#3468 polluter back plus the 4 victim files, -p no:randomly: 121 passed with the guard; 41 failed with TRINITY_SPLIT_GUARD=off. Fixes #3390 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent — Workspace, public links, Telegram groups, rooms (#3402) The gate reads only what a sender types each turn; reading history would re-hold every later turn after one mention. But a held `/pay-invoice …` and its "Not run…" notice stayed in each store and were replayed into later turns' history, where the model could act on a request nobody approved (on Codex and Gemini there is no in-container hook). Following the #3526 Chat-tab precedent, a turn the gate held or refused, and the platform's answer to it, now never reach a later prompt, whatever was decided. Rows people see are unchanged. - Workspace: `enterprise_portal_messages.gate_hold` marks the held turn's message and notice (and a refused turn's message); `get_portal_thread_window` / `get_portal_messages` leave marked rows out by default (cold history, the voice model's summary, and the voice-delta cursor). The thread UI, reply poll, retry dedupe, role card and ask card opt in. A retry that reuses a refused row clears it. Rows held before the column (since #3166) are backfilled once from the gate record that names their turn. - Public links + Telegram groups: `public_chat_messages.gate_hold`. A public visitor's or channel sender's message that invokes a gated skill is marked `named` as it is stored (they never self-approve). This also covers untagged group chatter the gate never sees, and a turn built while this one is still in the gate's check. The verdict overwrites it with the request id or `refused:<code>`. Prompt reads (context prompt, group history block, MEM-001) exclude marked rows by default; the visitor's own history opts in. - Rooms: fixes the re-hold loop. A held wake left the cursor in place, so every later wake asked again for the same post, up to the cap, then refused it forever. `room_gate_holds` now records, per agent, the held post (or one refused for a reason that stands) and its notice. Every wake leaves those rows out of what the agent is shown and what the gate scans, from one list, and so does MCP `read_room` by that agent. The cursor keeps its success-only rule, so an ordinary post in the same delta is still delivered. A transient refusal records nothing. Migrations: SQLite `held_gate_turns` + Alembic `0101_held_gate_turns` (two nullable columns, one table, one exact backfill sharing its SQL). Tests: three new files, 39 tests, over the real schema and the real gate backstop. Red first: the room loop reproduced (2 approvals for one post), and the replay shown on every surface. Mutation-proven: reverting each of 20 parts of the fix turns tests red, e.g. dropping the room record → 7 red, the Workspace predicate → 8, the public default exclusion → 6, the hold mark → 5, the backfill → 1, the write-time mark → 1. Follow-up: #3846 (two races with the gate's own check). Fixes #3402 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-history Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in-guard # Conflicts: # tests/registry.json
…gration test
The test faked the old shape by dropping `gate_hold` with SQLite's
`ALTER TABLE … DROP COLUMN`. The CI runner's SQLite cannot rewrite
`enterprise_portal_messages`' DDL that way ("error in table … after drop
column: incomplete input" — a comment sits before its last column), while
the macOS build tolerates it, so the test passed locally and failed in the
regression diff. It now creates the two old tables explicitly. That also
lets it check the backfill on the old shape: a Workspace turn a gate record
names is marked, and nothing else is. Removing the backfill turns it red.
The migration itself never drops a column.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in-guard # Conflicts: # tests/registry.json
…or good (#3402 review I1) `gate_hold_on_write` marked a public-link or channel row `named` whenever the gate map could not be read. Nothing ever clears `named` — a verdict overwrites it, a run never touches it — so after a transient blip a turn that then ran lost its message from every later prompt while its reply stayed, and group chatter stored during the blip was hidden for good. The write-time mark now fails open: the row is stored unmarked. The verdict still marks each turn the gate holds or refuses — the dispatch-time check re-reads the map and refuses `gate_unavailable` while it is still unreadable. Stated limit: a message invoking a gated skill that is stored during the blip and never dispatched (group chatter, or a turn that fails before its dispatch). Tests: a public-link turn stored during the blip keeps its message in the next turn's context once the map is back, and is refused and marked when it is not; chatter recorded during the blip is stored unmarked. Both red with the old mark (mutation-checked). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cap room cost (#3495) ent#387 made a room's budget an operator control, but drew the line at "platform principal" — which since #3488 includes the Workspace-only `user` role. Such an account kept whatever it sent: an uncapped cost, 500 messages, or a TTL of 0 (a room that never expires), while a portal client sending the same values was cut back to the operator defaults. - `_resolve_budget` now also requires a role at or above `operator` (`dependencies.is_workspace_only_role`, ent#837). Below it — the `user` rung, an MCP key such an account owns, a role outside the ladder, or no role at all — the caller's values are ignored and the operator defaults apply: the ent#387 rule, not a clamp. Operator and above may still bound a room, TTL 0 included. - `max_cost_usd` gets an upper bound, `MAX_COST_USD_CEILING` = $1,000 per room, on `RoomCreate` and on the operator default (`RoomBudgetDefaultsUpdate`; the two ranges mirror each other). A stored default above it is CLAMPED to it by `_coerce_cost` — reading it as invalid would mean no cap at all. The defaults response reports the ceiling beside the message and TTL limits. - MCP `create_room` declares the same 500 / $1,000 / 168 h bounds, and its stale "default 60" / "default 24" descriptions now point at the operator defaults. Fixes #3495 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 tasks done
Merged
4 tasks done
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 #3833, #3730, #3820, #3882, #3848, #3817, #3847. Never merged; members merge individually once green.
🤖 Generated with Claude Code