Skip to content

DO NOT MERGE — merge train: 3065,3049,3038,3044 - #3093

Closed
vybe wants to merge 48 commits into
devfrom
train/20260929-1807
Closed

vybe wants to merge 48 commits into
devfrom
train/20260929-1807

Conversation

@vybe

@vybe vybe commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Integration surface for #3065, #3049, #3038, #3044. Never merged; members merge individually once green.

AndriiPasternak31 and others added 30 commits September 28, 2026 01:14
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.
…PR A0)

The requirement (security.md §26.8), the endpoint note and the agent-page
flow state the new contract before the code: 503 asks_unavailable on a queue
or roster read fault, and a store that keeps the last good list.
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.
…er [] (ent#610 PR A0)

list_asks caught every error and returned [], and _on_roster turned an
unreadable roster into "not on the roster" — both made the Workspace say
"nothing needs you" during an outage (the #2915 class). The list now raises
AsksUnavailable (strict roster mode in list only) and the route answers 503.
A clean off-roster agent is still dropped; answer_ask keeps its uniform 404.

test_ent428's unreadable-roster case pinned the old == [] and is reversed
deliberately.
…so (ent#610 PR A0)

fetchAsks treated every error as absence: it cleared the list and set
asksAvailable=false, blanking every PortalAsks surface and zeroing the badge
on a 5xx. Now only 404/403 are absence; any other failure sets asksFailed,
keeps the list and leaves asksAvailable alone. asksLoaded latches on the first
success and asksLoadedAt feeds a stale banner.

workspaceAsks.spec.js's "clears the list rather than showing stale asks"
pinned the old behaviour and is reversed deliberately (ent#253).
…he route census

requirements/auth.md §2.8 (and the §2.7 helper table): the PERSON and
INTERACTIVE rules, which routes take which, the primitives, and the
route census with its shrink-only baseline. architecture/security.md §5
and §6, and one sentence on Invariant #8.

Refs #2996, trinity-enterprise#711
PUT /api/agents/{name}/autonomy takes Depends(require_person): a JWT
session or the person's own user-scoped key. Agent-scoped keys (on
their own agent or any other), the system key and every other scope get
a 403 with the human-only detail, before the owner check, so the
refusal discloses nothing about whether the agent exists.

Autonomy decides whether an agent's cron schedules fire unattended; it
is a grant, so the agent's own key must not be able to switch it on.

Tests go through the real get_current_user with seeded key rows and
assert the stored autonomy flag before and after.

Refs #2996
…2996)

PUT api-key-setting, read-only, resources, capabilities, capacity,
timeout, public-channel-model and guardrails take Depends(require_person),
the same rule as autonomy: the same file, the same owner configuration
surface, and no agent or system caller.

Each write is tested from its own non-ambient stored value: machine keys
through the real get_current_user leave it unchanged, a person changes
it. A router-level check pins that these and autonomy are every PUT in
the file.

Refs #2996
…a signed-in session

PUT /api/users/me/email and PUT/DELETE /api/users/me/github-pat take
Depends(require_interactive). The email is the account's sign-in
identity and the PAT is the credential future agent creations inherit;
both are credential/identity bindings, so no MCP key may change them,
the person's own user key included.

Tests: every key kind through the real get_current_user, with the stored
email and encrypted PAT checked before and after; the session path
(400 on a bad shape, 409 on a taken address, set and clear) unchanged;
and a record that email sign-in resolves the account by the email
column alone.

Refs trinity-enterprise#711
…2996)

Every OSS backend route, every method, must resolve to exactly one
class: gated in code (interactive / person / admin tier / widened admin
/ portal), listed with a reason (agent-callable / own-auth / delegated),
or in the frozen baseline, which only shrinks. A new route that is
neither gated nor classified fails the build, so the next human-only
route inherits the rule instead of relying on someone remembering it.

The AST side is import-free (rglob over src/backend, minus the private
submodule). The runtime side imports main in a subprocess and checks
every stored METHOD /path against the live table; it fails, never skips.
The human-only rule covers the autonomy grant, not the schedule tools it
bounds. Pins create/enable/disable with the agent's own key through the
real principal resolution, asserting the stored state moves.
Autonomy, read-only, resources, capabilities, capacity, guardrails,
public-channel model and the API-key setting flows note the person-only
rule on their writes; email-authentication, first-time-setup and
github-sync note the session-only rule on the sign-in email and the
personal GitHub PAT. Index rows, a learnings fragment, and the schedule
regression test in auth.md §2.8.

Refs #2996, trinity-enterprise#711
…d (trinity-enterprise#527 rider)

Operator ruling 2026-09-24 (ent#560's closure): readiness is a
role-companion property, and the owner's stamp should be visible on
the agents list and the fleet grid, not only on the role card.

- GET /api/agents attaches readiness {status, changed_at, source} from
  one batched read (get_role_readiness_for_agents, the display-label
  pattern, no N+1). An unstamped agent carries null and shows nothing:
  whether it is a companion is in its template.yaml, which a list must
  not read, so there is no guessed calibrating. The list says what and
  when, never who; the owner-scoped role card keeps the person.
- One predicate for both surfaces (utils/readinessBadge.js), with the
  role card's words and variants (ready = success, calibrating =
  warning, dot). The tooltip names a rollout stamp and what calibrating
  holds back.
- The list places it after the pressure badge (documented order
  updated) on both layouts; the grid tile places it after the runtime
  badge.

Tests: 7 backend (real DB + the endpoint), 6 helper, 3 mounted tile
(2 red without the tile change). Full vitest and ratchets green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tes call (#2996)

The read-only route imports its logic lazily from sys.modules, and a
sibling unit file evicts that entry at import, so under some orders the
route ran an unpatched copy and 404'd. The fixture now resolves and pins
the real module, and patches the bound logic through the globals of the
functions the router holds.
Drop the sign-in resolution test class and its registry note: they pinned
behaviour outside this change. Reword the census and MCP-boundary notes
neutrally; the baseline is a to-do marker, not a judgement.
… only (#2996)

An inherited PYTHONPATH (the verify-local unit stage sets one) let the
subprocess resolve `main` from another tree, so the fail-loud check on a
missing app could not fail.
Keeping the last good list on a failed read made it session-scoped state,
but signOut() never cleared it, so on a shared browser the next client's
first failed read showed the previous client's asks and badge. signOut now
resets asks, asksAvailable, asksLoaded, asksFailed and asksLoadedAt. A 404
also resets asksLoaded: a surface that no longer exists has no verdict.

Found by /cso and /review. Mutations (drop the sign-out reset; drop the 404
reset) each turn one spec red.
…ent#610 PR A0)

It passes on the pre-fix code too, since the old list_asks returned [].
The flow doc, registry entry and docstring now say so. The flow doc also
names the sign-out reset.
…(ent#610 PR A0)

The #2915 entry's Files and Tests lines now name the asks router, the
store, and both A0 test files. The A0 bullet also names the sign-out reset.
Found by /validate-pr.
…rols (#3055)

ent#429 put the "Open the conversation" button between the ending line and
the controls, so the controls' v-else-if bound to the link: whenever the link
rendered, the controls did not. Ingestion attaches every addressed ask to
Main, so every ask read outside Main (a non-Main chat, the rail's Work tab
"Waiting on you") showed a link and no way to answer.

The link gets its own v-if, the controls become <template v-if="!isEnded">,
and a threadLink prop (default true) lets a host that already sits beside
the ask's chat drop the link.

Red first on dev: portalAskCard.mount.spec.js failed 5/7 (options, Send,
answer box, Got it, and the PortalWork case missing beside the link).
Mutation: restoring only the v-else-if turns 4/7 red.

Fixes #3055
Refs trinity-enterprise#610
…ne is held

PR #3038 review item 1: the list/grid calibrating tooltip always said "its
scheduled brief is paused until its owner marks it ready", while the role
card says so only when _brief_held() is true (an enabled seat-delivery
schedule AND autonomy on). With autonomy off or no Workspace-delivery
schedule the owner would mark it ready and nothing would start.

- services/role_readiness_gate: is_seat_delivery_schedule + brief_is_held,
  the one predicate; role_card._brief_held now uses it.
- GET /api/agents attaches brief_held next to readiness: one batched,
  projected read over agent_schedules (get_workspace_delivery_schedules_for_agents),
  only for calibrating stamps with autonomy on; fail-soft to no claim.
- readinessBadge(readiness, briefHeld): the pause clause only when held,
  otherwise "not yet marked ready by its owner". Tile and list pass
  agent.brief_held.
- Nit: ready tooltips put the date first, as the role card does
  ("Ready since … (UTC) — carried over when the readiness gate shipped").
- Nit: AgentListPanel is now mounted in the spec — one badge per stamped
  row on both the lg and md secondary lines, none on the unstamped row.

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

PR #3038 review item 3: the 30s poll replaced agents only when the set of
names changed, and a readiness flip emits no WS event, so an open tab kept
showing calibrating until a reload. On an unchanged name set the poll now
patches readiness and brief_held on the rows already present (only when
the value changed) — no row replacement, no node rebuild. Pinned by a
vitest over the real poll body (fake timers, same row objects, same nodes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dolho and others added 18 commits September 29, 2026 10:05
…rid nameline

PR #3038 review item 2: dashboard-list-view.md (diagram and the D14
secondary-line contract) now reads slug · pressure · readiness · runtime ·
tags · +N; dashboard-grid-view.md gains the nameline badge order (runtime ·
readiness · SYSTEM / SKILL RUNNER) and the brief_held tooltip rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve tests/registry.json: dev's file plus this branch's ent#527 entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve tests/registry.json: dev's file plus this branch's entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve tests/registry.json: dev's file plus this branch's ent#527 entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sus (#2996)

Routes that landed on dev after the branch was cut were unclassified.
Skill-set assign/unassign are the USE of the ent#596 skills-manage
capability (fenced by get_skill_managed_agent_by_name; the grant is the
admin+interactive /skill-manager route), the two skill-set reads are
open reads, and raise_my_ask is MCP ask_operator, self-only via
get_self_acting_agent. All five go in AGENT_CALLABLE with a reason.
An agent key can no longer PUT /api/agents/{name}/timeout, so the cap
refusal on schedule and loop timeouts now says the owner sets it.
…10-a0

# Conflicts:
#	src/backend/client_portal/asks/router.py
#	src/backend/client_portal/asks/service.py
#	tests/registry.json
…ueue read (ent#610 PR A0)

The merge with dev brought #3059's paging, which splits the list into three
queue reads (agent set, count, page). The fault tests patched only the page
read with no ask seeded, so they no longer reached it. They now seed an ask
and parametrize over all three reads.

The 503 asks_unavailable now carries Retry-After: 20, the Workspace's asks
poll interval. The test app moves from module globals to a module-scoped
fixture.
A 401 was counted as asksFailed and the list was kept. For a portal-token
client nothing else ends the session: the 20s poll never re-reads the roster,
and portalHttp's 401 interceptor acts only for a platform session. So an
expired client's asks stayed on screen. fetchAsks now calls
endSession({expired: true, resumePath}), the roster fetch's own handling,
which clears the list through signOut().

A read that resolves after its session ended (sign-out, expiry, another
client signing in) is now dropped instead of writing the old list back.
…echanical, per the merge-train note on the PR

get_role_readiness_for_agents was the one unguarded decoration read on
GET /api/agents: a DB fault 500'd the whole list. Degrade to no stamp
(briefs_held_for_list already degrades to no pause), plus a test that
drives the endpoint with the read raising.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	tests/registry.json
# Conflicts:
#	tests/registry.json
@vybe

vybe commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

merge-train done: #3065, #3049, #3038, #3044 merged individually.

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

3 participants