Skip to content

fix(tests): board→in_review evidence gate test expects 200 bypass (DLD-2583) - #4

Open
nydamon wants to merge 951 commits into
masterfrom
fix/DLD-2560-skill-pack-persistence
Open

fix(tests): board→in_review evidence gate test expects 200 bypass (DLD-2583)#4
nydamon wants to merge 951 commits into
masterfrom
fix/DLD-2560-skill-pack-persistence

Conversation

@nydamon

@nydamon nydamon commented Apr 9, 2026

Copy link
Copy Markdown
Owner

Root Cause

DLD-2583: PR Verify failing 3x in 24h on Viraforge/paperclip

Failing test: board → in_review, code issue, no evidence → 422
Location: server/src/__tests__/browse-evidence-gate.test.ts:272

Root cause: Commit f422c4fe ("restore board-user bypass in assertEngineerBrowseEvidence") added req.actor.type !== 'agent' to the evidence gate, intentionally allowing board users to bypass the browse evidence gate. The test was never updated to reflect this.

What the test expected: 422 (gate fires for board, blocking without evidence)
What the code does: 200 (gate bypasses for non-agent actors incl. board users)

Fix

Update the test to expect 200 and rename it to board → in_review, code issue, no evidence → 200 (bypass).

Verification

npx vitest run server/src/__tests__/browse-evidence-gate.test.ts → 17/17 tests pass


DLD-2583

dotta and others added 30 commits March 23, 2026 19:23
…lish

fix(ui): polish issue and agent surfaces
Co-authored-by: lockfile-bot <lockfile-bot@users.noreply.github.com>
…-runtime

fix(runtime): improve agent recovery and heartbeat operations
…ny-portability

feat(portability): improve company import and export flow
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
…ted-agent-frontmatter

Fix imported agent bundle frontmatter leakage
…-instructions-tab-reset

Fix instructions tab state on agent switch
…on-aware-link-node

Extract mention-aware link node helper and add tests
The plugin framework landed without updating the Dockerfile. The
server now imports @paperclipai/plugin-sdk, so the deps stage needs
its package.json for install and the build stage needs to compile
it before building the server.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n-sdk

fix(docker): add plugin-sdk to Dockerfile build
…ping

Addresses Greptile review on PR paperclipai#1363: numeric entities decode via
code points; named entities use a small allowlist (amp, nbsp, etc.)
so M&amp;M resolves correctly; unknown named entities are preserved.

Adds mid-token tests for &amp; in agent names.

Made-with: Cursor
- findMentionedAgents: keep normalizeAgentMentionToken + extractAgentMentionIds
- decode @mention tokens with entities.decodeHTMLStrict (full HTML entities)
- Add entities dependency; expand unit tests for Greptile follow-ups

Made-with: Cursor
- Drop entities package (CI blocks pnpm-lock.yaml on PRs; reset lockfile to master)
- Restore numeric + allowlisted named entity decoding in issues.ts
- Split Greptile mid-token &amp; case into its own test with review comment

Made-with: Cursor
paperclipai#69)

* fix: convert all remaining Date objects to ISO strings for postgres.js parameter binding

Drizzle ORM's buildUpdateSet passes values directly to postgres.js without
calling mapToDriverValue. postgres.js v3.4.8 cannot serialize Date objects
as query parameters, causing TypeError crashes in .set() and .values() calls.

Previous fixes only covered heartbeat.ts and execution-workspaces.ts. This
commit fixes all remaining 23 server files that pass Date objects to Drizzle
.set(), .values(), or .onConflictDoUpdate() operations.

Critical path: costs.ts, workspace-runtime.ts, workspace-operations.ts,
issues.ts, and budgets.ts are all called during heartbeat run execution
and were causing agent runs to fail, leaving agents frozen.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

https://claude.ai/code/session_01F1WkS82kZiayD8aT4zYEpr

* ci: add temporary ai-review override workflow for PR paperclipai#69

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
…n agents

Drizzle ORM's PgTimestamp.mapToDriverValue already calls value.toISOString()
on Date objects before passing them to postgres.js. The prior PRs (paperclipai#62-paperclipai#69)
converted `new Date()` to `new Date().toISOString()` (a string), which then
failed when Drizzle called .toISOString() on the string — strings don't have
that method. This caused "value.toISOString is not a function" errors during
heartbeat recovery, freezing agents.

Reverts all 25 affected files to the original upstream Date object pattern.

https://claude.ai/code/session_01F1WkS82kZiayD8aT4zYEpr
…ents-88mhf

revert: remove incorrect .toISOString() conversions causing frozen agents
…ry-timestamp

fix: postgres.js Date parameter binding in process_lost retry WHERE clause
…binding in process_lost retry WHERE clause

The postgres.js driver requires timestamp parameters to be ISO-formatted strings, not JavaScript Date objects.
The WHERE clause was passing a Date object which caused ERR_INVALID_ARG_TYPE errors during parameter binding.

Fixed by converting 'now' to 'nowStr' using toISOString() before injecting into the SQL template.

This fixes the periodic 'failed to enqueue process_lost retry' errors visible in server logs.
nydamon and others added 29 commits April 6, 2026 17:48
The detectDirectDbClosures sweeper was re-flagging the same issues every
30-second sweep cycle because it checked a 60-minute window without
filtering already-flagged issues. Now skips issues that already have an
issue.db_bypass_detected activity log entry.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…te error messages

Root cause of 80% gate failures: agents push code to GitHub but never
register work products via POST /api/issues/:id/work-products. The gate
blocks them, the error message doesn't tell them what API to call, and
the documentation doesn't mention the endpoint at all.

Changes:
- Gate error messages now include the exact API endpoint and required fields
- AGENTS.md Code Delivery Protocol includes full curl examples for
  registering commits and PRs as work products
- Paperclip skill endpoint table now lists work product CRUD routes
- API reference now has a full Work Products section with examples

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…te error messages (paperclipai#210)

Root cause of 80% gate failures: agents push code to GitHub but never
register work products via POST /api/issues/:id/work-products. The gate
blocks them, the error message doesn't tell them what API to call, and
the documentation doesn't mention the endpoint at all.

Changes:
- Gate error messages now include the exact API endpoint and required fields
- AGENTS.md Code Delivery Protocol includes full curl examples for
  registering commits and PRs as work products
- Paperclip skill endpoint table now lists work product CRUD routes
- API reference now has a full Work Products section with examples

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The entrypoint drops from root to the node user via gosu, but the node
user was not a member of the Docker socket's group (GID 999). This made
hermes-bridge.sh unable to docker exec into the hermes-agent sidecar,
causing adapter_failed errors on every Hermes heartbeat run.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…en screenshots exist (paperclipai#212)

Two changes to reduce false evidence gate blocks:

1. Widened BROWSE_EVIDENCE_PATTERN to match actual agent output: goto,
   snapshot, gstack, playwright, VERIFIED, health score, browser
   verification/testing, console errors, screenshot attached.

2. Changed both engineer and QA evidence gates to accept screenshot
   attachments as primary evidence even without matching browse text.
   The text pattern is a v1 stopgap; the real proof of interactive
   testing is the screenshot itself. Text-only (no image) is still
   rejected.

Audit showed done_requires_qa_browse_evidence was the #1 remaining
false block — agents uploaded screenshots but their comments used
goto/snapshot/gstack/playwright commands that didn't match the regex.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…perclipai#213)

When a board user assigns an issue to themselves, agents (including
control-plane roles like CEO/CTO) could still reassign or change status
on the issue. This caused a cycle where the Monitor/CEO would escalate
board-parked issues through the management chain, burning tokens and
creating noise (observed on DLD-1100 with 39 comments).

Board-assigned issues are at the top of the SLA chain. The new gate
blocks agents from changing assignee or status when assigneeUserId is
set and assigneeAgentId is null. Agents can still comment. Only the
board can modify assignment or status on board-owned issues.

Gate: board_assigned_protected
Activity log: issue.board_assigned_protected

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…en screenshots exist (paperclipai#214)

Two changes to reduce false evidence gate blocks:

1. Widened BROWSE_EVIDENCE_PATTERN to match actual agent output: goto,
   snapshot, gstack, playwright, VERIFIED, health score, browser
   verification/testing, console errors, screenshot attached.

2. Changed both engineer and QA evidence gates to accept screenshot
   attachments as primary evidence even without matching browse text.
   The text pattern is a v1 stopgap; the real proof of interactive
   testing is the screenshot itself. Text-only (no image) is still
   rejected.

Audit showed done_requires_qa_browse_evidence was the #1 remaining
false block — agents uploaded screenshots but their comments used
goto/snapshot/gstack/playwright commands that didn't match the regex.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rclipai#215)

Every failed gate attempt bumps issue.updatedAt, which pushes the
evidence time window past uploaded screenshots. DLD-2162 had 15
screenshots but was SQL-bypassed because 10+ failed attempts drifted
updatedAt 30 minutes past the evidence.

QA evidence gate now checks for screenshots from the QA reviewer
without a time window. The QA PASS comment anchors the review cycle;
screenshots from that reviewer on the issue are sufficient proof.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…aperclipai#216)

Issues in "blocked" status were being marked "done" via direct SQL
without the blocker being resolved. This obscures real problems —
blocked work appears complete when nothing was done.

Changes to detectDirectDbClosures():
- Auto-revert: when a bypass is detected and the last API-set status
  was "blocked", revert the issue to "blocked" and log
  issue.blocked_bypass_reverted
- Improved dedup: checks if the most recent detection entry was created
  after the issue's current updatedAt, allowing re-detection after
  reverts (previously any historical detection caused permanent skip)
- Non-blocked bypasses continue to be flagged only (may be legitimate
  ops closures)

Triggered by DLD-2299 (poly-weather Phase 1A) which was blocked on SSH
credentials, then SQL-closed as done without performing the VPS audit.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…avior (paperclipai#217)

Agents using pi_local adapter resume conversations from session files.
When these sessions are days old, the LLM echoes stale conclusions
("Fleet clean") instead of re-checking its inbox. The CEO had 321
stale task sessions and a 2-day-old runtime session, causing it to
ignore all reopened/unblocked tasks.

New sweeper expireStaleAgentSessions() runs every ~30s:
- Clears runtime session pointers (agent_runtime_state.session_id)
  older than 4 hours, forcing fresh conversations
- Prunes agent_task_sessions older than 48 hours to prevent
  unbounded table growth

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…aperclipai#219)

* fix(sweeper): bump unpicked assignment retrigger limit from 1 to 3

MAX_RETRIGGERS=1 meant any issue that wasn't picked up on the first
retrigger became permanently stranded. With agents handling multiple
concurrent tasks, a single retry is insufficient — the agent may be
busy on the first attempt but available 8 minutes later.

Bumping to 3 gives the watchdog 24 minutes of retries (3 × 8min SLA
windows) before giving up, which covers most transient busy periods.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(test): update retrigger test to match new MAX_RETRIGGERS=3

Test seeded activationRetriggerCount=1 expecting skip, but with the
limit bumped to 3, count 1 is now below threshold. Updated to 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rements (paperclipai#218)

* feat(governance): add route guard standards, multi-role QA, and auth PR requirements

Engineering hardening across three layers — prevention, detection, enforcement —
to catch auth/routing bugs before they ship.

Prevention (thinking):
- H1: Route access matrix standard — every web project must maintain a
  living docs/route-access-matrix.md mapping routes to roles/guards/sidebar
- H5: Route guard rules — default-open policy, sidebar must match guards,
  core product pages never admin-gated

Detection (testing):
- H2: Role-matrix Playwright smoke test requirement for all web apps
- H3: Onboarding/critical-path smoke test requirement
- Definition of Done items 8-9 for auth/routing changes

Enforcement (process):
- H4: PR template section requiring route access matrix update and role
  test table for any PR touching auth/routing/sidebar
- H6: QA must test as BOTH admin AND member — single-role test is
  automatic FAIL, with hard rejection rules and evidence format

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve merge conflicts in hardening standards

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert accidental retrigger limit change from stash conflict

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ipai#221)

Board user comments are explicit instructions that should always reach
the agent. The gate_block_backoff was unconditionally skipping wakeups
after 3+ consecutive gate blocks, silently dropping board user comments.

Now requestedByActorType=user bypasses the backoff and resets the
gateBlockCount so the agent gets a fresh slate.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…paperclipai#220)

Backend/CLI/API-only code issues have no browser surface to screenshot,
causing the browse evidence gates to permanently block them. Agents were
escalating every backend task to the board for manual close.

Both evidence gates (in_review and done) now accept "no browser surface"
as an exemption phrase in the actor's comment. The agent must include a
justification (e.g. "Python backend module", "CLI tool"). The gate error
messages now mention this exemption path.

AGENTS.md updated with the full protocol: examples, rules, and when NOT
to use the exemption.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pai#222)

The 15-minute comment retrigger cooldown was suppressing @mention wakes.
When SPE @mentioned QA Agent to request a reworded QA PASS, the wakeup
was skipped because QA had a recent run on the same issue. @mentions are
deliberate requests for action and should always wake the target agent.

Changed: cooldown only applies to issue_comment_retrigger, not
issue_comment_mentioned. Updated test to expect mentions to bypass.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add gmail.js, ga4.js, and gws-token.js to tools/google-workspace/ so
they survive container rebuilds and volume wipes. The entrypoint symlinks
them into /paperclip/bin/ at startup. All three are zero-dependency
Node.js CLIs using service account DWD auth.

- gmail.js: search, read, send, drafts, labels via Gmail API
- ga4.js: GA4 reports, realtime, conversions, Measurement Protocol
- gws-token.js: JWT token generator for any Google API scope
- SKILL.md files for gmail and ga4-analytics skills

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Loops lifecycle and transactional email skill for agents operating
the Loops platform. Includes API endpoint patterns, security model,
evidence standards, and docs-first lookup protocol.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Senior Platform Engineer <spe@paperclip.local>
…ocket-permission

fix(hermes): grant node user Docker socket access after privilege drop
…ce-tools

feat: bake Google Workspace tools into Docker image
Fixes 404 API route not found errors affecting multiple agents (CEO, QA, Hermes)
who call /api/agents/me/issues instead of /api/agents/me/inbox-lite. Added as a
backward-compatible alias that accepts status and ids query params.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
…-me-issues-rebased

feat(DLD-2396): add /api/agents/me/issues alias route
… (DLD-2400) (paperclipai#230)

Co-authored-by: Paperclip <noreply@paperclip.ing>
* feat(skill): add posthog analytics skill with REST API patterns

Direct PostHog REST API skill — HogQL queries, events, insights,
feature flags, persons, sessions, dashboards. No MCP or OAuth needed,
uses personal API key auth via POSTHOG_API_KEY env var.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: wrap rg with memory safety limits to prevent OOM crashes

ripgrep consumed 6.2GB RSS searching unbounded files, hitting the 12GB
container mem_limit and triggering the kernel OOM killer. The wrapper
enforces --max-filesize 10M, --max-columns 10000, and --max-count 500.

Same relocation pattern as gh and docker wrappers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: make Google Workspace CLIs ESM-compatible

* test: align gate specs with current issue flow

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DLD-2560: Port the 13-skill pack (7 in skills/ + 6 in .agents/skills/)
into /app/skills/ so the pi-local adapter can discover all skills at
container startup.

Changes:
- Dockerfile: COPY 6 skills from .agents/skills/ into /app/skills/
  (company-creator, create-agent-adapter, doc-maintenance, pr-report,
  release, release-changelog)
- Dockerfile: COPY and chmod docker-entrypoint.sh + verify-skills.sh
- Dockerfile: Use ENTRYPOINT to call docker-entrypoint.sh
- scripts/verify-skills.sh: NEW — verifies all 13 skills have SKILL.md
- scripts/docker-entrypoint.sh: call verify-skills.sh before server start

The pi-local adapter resolves skills via
  PAPERCLIP_SKILL_ROOT_RELATIVE_CANDIDATES = ["../../skills", "../../../../../skills"]
which maps to /app/skills/ inside the container. The explicit COPY
ensures all 13 skills are present regardless of .dockerignore settings.
…bypass)

DLD-2583: PR Verify has been failing 3x in 24h due to this test expecting
422 when assertEngineerBrowseEvidence now bypasses for non-agent actors
(req.actor.type !== 'agent').

The board-user bypass was intentionally restored in f422c4f (DLD-2400).
Board users (req.actor.type === 'board') should be able to move code issues
to in_review without providing browse evidence — the board is a governance
tool, not a code-delivery actor. The gate correctly fires for agents.

Test update: expect 200 (gate bypassed) instead of 422.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d738cb67c3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ui/src/plugins/slots.tsx
Comment on lines +229 to +233
const { useState, useEffect, useCallback, useMemo, useRef, useContext,
createContext, createElement, Fragment, Component, forwardRef,
memo, lazy, Suspense, StrictMode, cloneElement, Children,
isValidElement, createRef } = R;
export { useState, useEffect, useCallback, useMemo, useRef, useContext,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Re-export full React API from the plugin shim

The generated react shim only exposes a hardcoded subset of React exports, so any plugin UI bundle that imports other standard names (for example useLayoutEffect, useReducer, or startTransition) will fail module linking with a missing export error and never render its slots. This is a runtime break for otherwise valid React plugin code; the shim needs to forward the complete React export surface instead of a curated list.

Useful? React with 👍 / 👎.

Comment thread ui/src/plugins/slots.tsx
Comment on lines +305 to +307
result = result.replaceAll(` from ${from}`, ` from ${to}`);
// Also handle `import "..."` (side-effect imports)
result = result.replaceAll(`import ${from}`, `import ${to}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Support minified import syntax in specifier rewriting

The bare-specifier rewrite only matches exact substrings with spaces ( from ... and import ...), but minified ESM commonly emits from"react"/import"react" without whitespace. In that case the bare imports are left unchanged, and the browser fails to resolve them when loading plugin modules. This makes plugin loading brittle for minified bundles; rewrite logic should tolerate optional whitespace (or parse imports structurally).

Useful? React with 👍 / 👎.

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.

7 participants