Files
PaperClipAI/maps/agent-access-map/activity.md
T
DottaandPaperclip bd382eee53 fix(recovery): explain retained source verification failures (#17773)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Workspace recovery must prove the identity of retained work before
it uses an allocation.
> - The verifier rejects several different contracts with the same
`source_binding_invalid` code.
> - A saved Retry can therefore stop before file preservation without
enough evidence for a safe repair.
> - Operators need a bounded observation of the selected source without
another attempt or weaker guards.
> - This pull request records closed failure reasons and adds an
explicit read-only inspection on the run page.
> - The benefit is a precise next diagnostic step while the saved work,
intent, and ownership fences remain intact.

## Linked Issues or Issue Description

Refs: #17741, #17053. This is a diagnostic follow-up. It does not change
recovery admission or the retained-source acceptance policy.

**What happened?**

A retained-source recovery exhausted in `verify_execution` with only
`source_binding_invalid`. Run and task diagnostics could not distinguish
an invalid reference, lease policy, source binding, or reopened
allocation. The normal recovery-actions read can also revalidate
recovery state, so it was not a safe substitute for a current read-only
inspection.

**Expected behavior**

Record an allowlisted reason at the rejected guard. Allow a reader to
inspect one exact task/run/action without changing its attempts, saved
input, receipts, or ownership. Clearly separate the recorded failure
from current identity observations.

**Steps to reproduce**

1. Prepare a terminal native run with a retained-source action and a
lease policy of `reuse_by_environment`.
2. Attempt source verification. The guard must still reject that policy.
3. Open the failed run and choose Inspect recovery, then Inspect current
source. The diagnostic should name `lease_policy_not_retained` and the
allowlisted policy.

## What Changed

- Split existing source-binding guards into closed reason codes. Keep
each acceptance predicate and physical recovery action unchanged.
- Persist validated, path-free reason/policy detail with future system
recovery failures. Keep historical generic records compatible.
- Add an explicit query on the existing task recovery-actions GET. Exact
company/task/run/action selection bypasses read revalidation. Run
selection lists at most 20 actions; inspection stops before per-lease
lookup when a record has more than eight sources.
- Add a manual inspection control on native export-failure run pages.
Keep resolved retained actions inspectable. Show recorded failure and
current recorded-source identity separately.
- Update execution semantics and feature/access recipes. The shared type
is the normalized server/UI contract; it contains no provider payload or
secret.

## Verification

- RED: the prior verifier failed both reason assertions for rejected
warm policy and foreign-run lease binding. GREEN: the final focused
source/HTTP, system-state, and UI suites pass 23 tests. The UI follow-up
passes 8 tests after the review fixes.
- HTTP tests prove exact selection, cross-company denial,
resolved-action access, reused-allocation rejection, redaction, and
unchanged actions, runs, leases, tasks, wakeup receipts, workspace
operations, and activity. A nine-source case proves bounded inspection
while the existing worker verifier still accepts all nine valid sources.
- Review follow-up: show allowlisted saved detail beside a changed
current observation, preserve all listed actions, and follow
exact-action selection in verified/limited/error tests. The prior
component fails the saved/current drift regression in an in-memory RED
control; the current component passes. UI typecheck/build pass again.
- `pnpm -r typecheck` and `pnpm build` pass. Token gates, maps, module
boundaries, and `git diff --check` pass.
- Rebased onto master `46de15ff4b` without conflicts. Range-diff proves
both diagnostic commits are unchanged. On new head
`a7e2bc87fcea75e76b6b736304d1287257f71174`, all 23 focused tests, full
typecheck/build, token gates, maps, and module checks pass again. Both
fresh reviews are 5/5 with zero unresolved threads. Final exact-head
GitHub status is CLEAN: 55 success, 2 skipped, 1 neutral, no pending
checks or failures. The [ci / verify
aggregate](https://github.com/paperclipai/paperclip/actions/runs/40387002325/job/121802415824)
passed in eight seconds. All test/build/typecheck/E2E lanes passed.
- Existing full workspace recovery suite: 56/56 pass (103.5 seconds)
before the byte-equivalent rebase. `pnpm test:run` hit repeated embedded
PostgreSQL initialization failure before database-suite assertions and
was stopped. No full local suite pass is claimed. The owned external
`_test` database provided the focused HTTP/source verification. The
prior browser attempt also stalled at Network.enable before its click;
that attempt did not establish browser acceptance.
- Current-head local browser acceptance passes through the actual run
page: Inspect recovery lists the exact action, Inspect current source
shows recorded generic failure separately from the current rejected
policy, and reload resets inspection to the explicit control. Explicit
selection works again after reload. The app used isolated data, an empty
inherited environment, a paused synthetic agent, and no provider
credentials. Final state remains failed/exhausted with zero attempts,
zero wakeup receipts, zero workspace operations, and unchanged original
generic failure. The local app and temporary tab were stopped. This
proves the diagnostic journey, not the deployed source's actual lease
policy or its recovery.
- Final merge verification at
`a7e2bc87fcea75e76b6b736304d1287257f71174`: GitHub reports CLEAN and
mergeable. Both repository-required checks, `ci / verify` and `ci /
e2e`, pass, as does the exact-head Greptile check (5/5, zero unresolved
threads). The separate [Entire
trail](https://entire.com/gh/paperclipai/paperclip/trails/7) has no
recorded reviewer approval; no such approval is claimed. It is not a
configured GitHub merge gate.
- Initial local setup failures were distinct from product assertions:
localhost sandbox EPERM required scoped local access; a browser harness
import path was corrected. A test UUID type inference error and
historical native-export UI selection gap were corrected before the
green run. The live task remains blocked; this diagnostic does not claim
it recovered.

## Risks

- This change improves evidence. It does not prove provider termination,
local descriptor integrity, or successful file preservation, and it does
not repair the original source.
- Current inspection spans multiple database reads. It is an
observation, not a historical cause or execution authority. It performs
no provider/filesystem calls and starts no retry or executor.
- The optional persisted detail is backward compatible. Unknown values
and extra fields are excluded. Oversized source lists remain uninspected
through this explicit diagnostic path; physical recovery rules do not
change.

## Model Used

OpenAI Codex based on GPT-6, with code execution and browser tools. The
exact serving model ID and context window were not exposed in this
session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-27 08:21:28 -05:00

238 lines
11 KiB
Markdown

# Dashboards, activity, and audit trails — agent access
Product map: [Dashboards, activity, and audit trails](../feature-map/activity.md).
Feature ID: `activity`.
Review date: 2026-10-12. Source commit: `f981fa3cc10cb8663a87dec60345fbff46d54d0a`.
This is a source-reviewed baseline of representative operations and known gaps.
Each capability needs operation-level review before its entire scope can be
qualified. No live agent execution is claimed. Follow the [map contract](../README.md)
for format, permission checks, evidence and maintenance.
## `overview`
Capability ID: `activity/overview`.
Review: `baseline`.
API: `present` — representative operation IDs:
- `GET /api/companies/{companyId}/dashboard`.
Agent access: `available` — REST / native call_api / CLI dashboard.
Policy: Same company plus visibility/audit/cost/run permissions where the route requires
them.
Evidence: `source-only` — [implementation](../../server/src/routes/activity.ts#L224) at
the reviewed commit above.
Verification: `not-run` — exercise a listed permitted operation with actual agent
credentials/run context; inspect its result and applicable persistence/attribution.
Assert the relevant denied actor, owner, company or mode from Policy. Review the
remaining operations before qualifying the whole capability.
Gaps: Dashboard data is accessible; live-work rendering and event streaming are separate
surfaces.
## `activity`
Capability ID: `activity/activity`.
Review: `baseline`.
API: `present` — representative operation IDs:
- `GET /api/companies/{companyId}/activity`.
- `GET /api/issues/{id}/activity` (UUID or issue identifier).
- `GET /api/companies/{companyId}/audit/agent-actions?actorScope=all&entityType=case&entityId={caseRef}`
(case UUID or identifier).
Agent access: `partial` — REST / native call_api / CLI activity for company
activity. Sandbox agents can use GET issue activity through the default
[callback bridge](../../packages/adapter-utils/src/sandbox-callback-bridge.ts).
The bridge still rejects company activity and company audit routes.
Policy: Company activity requires same-company access and `company_scope:read`,
with issue visibility filtering. Issue activity requires same-company resource
access and `issue:read` for the resolved issue. The sandbox transport does not
replace those controller checks or permit activity writes.
Evidence: `source-only` — [activity routes](../../server/src/routes/activity.ts),
[agent authorization tests](../../server/src/__tests__/activity-routes.test.ts),
and [bridge tests](../../packages/adapter-utils/src/sandbox-callback-bridge.test.ts).
Issue activity transport reviewed at `89770ba707fb1ebd4cabd184eec2e7fee226e0c3`;
other operations retain the baseline source review above.
Verification: `not-run` — no live agent-credential proof. Local route and bridge
regressions pass: permitted agent issue read, denied company/resource scope,
denied writes, extra path segments, and company audit routes. With a real
sandbox agent, read an authorized issue's activity, then verify denial for an
inaccessible issue, another company, and company-wide audit.
Gaps: Audit export and raw activity creation have extra restrictions; do not
equate them with issue activity reading. Live sandbox access remains unverified.
Working-tree case history update (2026-10-20): the all-actors Audit endpoint
accepts case resource/action/date filters for ordinary company readers who can
read those cases. Agent, responsible-person and run attribution filters still
require `audit:view_agent_actions`; the basic tier still strips those fields and
details. `case_events` mutations lacking an activity-log counterpart are merged
with logged case events before the retained-source permission predicate and
cursor limit. Case snippets are enriched only after that filter. Missing,
inaccessible and other-company cases return no rows; exact identifiers are
case-insensitive. The raw company activity API remains its existing stored-log
view.
Local verification: real temporary-Postgres
[audit route regressions](../../server/src/__tests__/agent-action-audit-routes.test.ts)
cover historical creation/fields, UUID/identifier filters, one-row cursor
pagination, hidden private sources, and duplicate handoff suppression. This is
local transport/database evidence, not a deployed agent run or a sandbox bridge
qualification.
## `runs`
Capability ID: `activity/runs`.
Review: `baseline`.
API: `present` — representative operation IDs:
- `GET /api/companies/{companyId}/heartbeat-runs`.
- `GET /api/companies/{companyId}/live-runs`.
- `GET /api/heartbeat-runs/{runId}`.
- `GET /api/heartbeat-runs/{runId}/events`.
- `GET /api/heartbeat-runs/{runId}/log`.
- `GET /api/heartbeat-runs/{runId}/workspace-operations`.
- `GET /api/heartbeat-runs/{runId}/issues`.
- `GET /api/issues/{id}/runs`.
- `GET /api/issues/{id}/recovery-actions`.
Agent access: `available` — REST / native call_api / CLI run. Sandbox agents can
use these diagnostic GET paths through the default queue and HTTP/2
[callback bridge](../../packages/adapter-utils/src/sandbox-callback-bridge.ts).
Use the injected run credential; see the [bounded read workflow](../../skills/paperclip/references/run-diagnostics.md).
Policy: Same-company and `company_scope:read` checks apply to company run
inventory. Run content requires run/task visibility; private run list entries
may be redacted stubs. Task run/recovery reads require `issue:read`. Run-linked
tasks also pass the caller's task-read SQL predicate individually, including
activity links and the original task. Existing credential redaction remains.
Bridge admission grants no cancellation, retry, recovery resolution or
provider-trace administration authority.
The recovery-actions GET also accepts `inspectSourceRunId` and optional
`inspectSourceActionId`. The explicit path requires the same readable task and
exact company/task/run/action binding, including resolved retained actions.
Run-only selection lists at most 20 actions; action selection observes current
recorded-source identity with an eight-lease ceiling. This path does not run
read revalidation, mutate recovery/Retry state, call providers or inspect files.
Only closed reason/policy values supplement the existing recorded failure;
the observation is neither historical cause nor permission to execute. Local
[HTTP/database regressions](../../server/src/services/workspace-recovery-source.test.ts)
cover exact selection, foreign action denial, reused allocations, redaction,
bounded lookup and unchanged persisted state. Deployed agent use is unverified.
Evidence: `source-only` — scoped review of `f10210990a3c9cad1a4359318388f237de3169dd`:
[run routes](../../server/src/routes/agents.ts),
[task links](../../server/src/routes/activity.ts),
[task filtering](../../server/src/services/activity.ts),
[bridge regressions](../../packages/adapter-utils/src/sandbox-callback-bridge.test.ts),
[HTTP/2 transport regressions](../../packages/adapter-utils/src/http2-bridge-server.test.ts),
and [private-run regressions](../../server/src/__tests__/heartbeat-run-privacy-routes.test.ts).
Verification: `not-run` for a deployed sandbox agent with these new routes.
Isolated regressions cover admitted GETs, denied writes/extra segments, query
forwarding, controller denials, private run stubs, and mixed public/private task
links. They do not qualify a live run. After deployment, use a real agent run to
read an authorized failure and assert denial/redaction for another company,
private tasks and restricted key modes. Do not count a skipped database suite
as successful verification.
Gaps: Telemetry/run visibility is checked independently from seeing the source task.
Company heartbeat history defaults to 200 rows and caps explicit limits at 1,000
in both the route and service. The response remains an array. Scope, visibility,
and secret redaction checks apply to the bounded result set.
Live updated-bridge admission and useful incident diagnosis remain unverified.
## `routine-audit`
Capability ID: `activity/routine-audit`.
Review: `baseline`.
API: `present` — representative operation IDs:
- `GET /api/routines/{id}/runs`.
Agent access: `available` — REST / native call_api / CLI routine runs.
Policy: Same company plus visibility/audit/cost/run permissions where the route requires
them.
Evidence: `source-only` — [implementation](../../server/src/routes/activity.ts#L224) at
the reviewed commit above.
Verification: `not-run` — exercise a listed permitted operation with actual agent
credentials/run context; inspect its result and applicable persistence/attribution.
Assert the relevant denied actor, owner, company or mode from Policy. Review the
remaining operations before qualifying the whole capability.
Gaps: Routine execution history is distinct from configuration revisions and scheduling
changes.
## `timeline`
Capability ID: `activity/timeline`.
Review: `baseline`.
API: `present` — representative operation IDs:
- `GET /api/companies/{companyId}/timeline`.
Agent access: `available` — REST / native call_api.
Policy: Same company plus visibility/audit/cost/run permissions where the route requires
them.
Evidence: `source-only` — [implementation](../../server/src/routes/activity.ts#L224) at
the reviewed commit above.
Verification: `not-run` — exercise a listed permitted operation with actual agent
credentials/run context; inspect its result and applicable persistence/attribution.
Assert the relevant denied actor, owner, company or mode from Policy. Review the
remaining operations before qualifying the whole capability.
Gaps: API exists; no dedicated timeline tool is necessary for this requirement.
## `agent-commentary`
Capability ID: `activity/agent-commentary`.
Review: `baseline`.
API: `present` — `GET /api/companies/{companyId}/agent-commentary` and
`GET /api/companies/{companyId}/agent-commentary/{id}`.
Agent access: `restricted` — the browsing API is board-only. Agent submission
continues through the existing POST and native submit tools.
Policy: Company membership, `commentary:view`, and source-task visibility. All human company roles,
including viewers, receive the permission by default, with an upgrade grant for existing active human members.
Human invitation approval adds the default to older saved grant lists without widening existing scopes.
Agent-action audit access remains separate. Counts and previews
use the same visibility predicate as full messages; cross-company IDs return 404.
Feedback stays hidden after source-task deletion, even when its task foreign key clears.
Evidence: `source-only` — [routes](../../server/src/routes/agent-commentary.ts) and
[reader](../../server/src/services/agent-commentary-reader.ts).
Verification: `not-run` for a deployed instance. Test list/detail denials with
agent credentials, another company, and an unreadable task.
Gaps: Storybook simulates the API and does not qualify deployed authorization.