Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d607765
sc-e6ula: add all missing SDK API methods and fix evaluateQualityGate…
Apr 15, 2026
c7bbada
sc-e6ula: simplify: remove type casts by widening request params type…
Apr 15, 2026
a172676
sc-e6ula: commit prior session's AGENTS.md changes
Apr 15, 2026
b0af5cd
sc-e6ula: fix 5 reviewer issues — optional enabled, proper return typ…
Apr 15, 2026
6e76f93
sc-e6ula: simplify: make updateQualityGate optional fields consistent…
Apr 15, 2026
ff18e0e
sc-e6ula: fix 6 type parity issues — Shard tests→test_names, QualityG…
Apr 15, 2026
3449a77
sc-e6ula: fix type alignment for Invitation, TeamToken, AdminUser
Apr 15, 2026
a6c7a77
sc-e6ula: fix 12 type parity issues — getTrends/getFlakyTests wrappin…
Apr 15, 2026
9994365
sc-e6ula: fix 3 type parity issues — QualityGateEvalRuleResult.type→m…
Apr 15, 2026
09100ff
sc-e6ula: fix QualityGateEvalRuleResult.metric→type to match stored J…
Apr 15, 2026
5d06467
sc-e6ula: add limit param to listWebhookDeliveries, accept params object
Apr 15, 2026
d0f65fe
sc-e6ula: add missing query params to analytics methods
Apr 15, 2026
199ef9e
sc-e6ula: fix 3 type accuracy issues — required clusters/metadata, We…
Apr 15, 2026
810cbe5
sc-e6ula: add typed enums for execution/test/worker status, fix webho…
Apr 15, 2026
dbf7a0a
sc-e6ula: fix Report.summary missing start/stop and QualityGateRule.p…
Apr 15, 2026
195a910
sc-e6ula: fix 8 open type accuracy issues — UpdateExecutionStatus typ…
Apr 15, 2026
a954d8e
sc-e6ula: update AGENTS.md with reviewer role instructions
Apr 15, 2026
6d41cfd
sc-e6ula: fix QualityGateRule.params type to accept null (Record<stri…
Apr 15, 2026
906448a
sc-e6ula: add separate CompareReport type for compare endpoint — base…
Apr 15, 2026
732f8d4
sc-e6ula: update AGENTS.md with QA reviewer role instructions
Apr 15, 2026
f364970
sc-e6ula: make Report.tool_name optional to match server omitempty
Apr 15, 2026
360c7ee
sc-e6ula: fix deleteQualityGate and changePassword return types — ser…
Apr 15, 2026
731724d
sc-e6ula: make Report.summary.start/stop optional, ReportTriageResult…
Apr 15, 2026
971d287
sc-e6ula: fix CtrfReport fields, add uploadReport params, make update…
Apr 15, 2026
8d3c187
sc-e6ula: fix 6 security issues — command injection, SSRF, password b…
Apr 15, 2026
5dceeb4
sc-e6ula: simplify: replace manual char arithmetic with strconv.Atoi …
Apr 15, 2026
e3556a5
sc-e6ula: update AGENTS.md with QA reviewer role instructions
Apr 15, 2026
2f0fed1
sc-e6ula: block link-local (169.254.0.0/16) and CGNAT (100.64.0.0/10)…
Apr 15, 2026
dda48d0
sc-e6ula: simplify: remove unused existingHash variable and reduce Ac…
Apr 15, 2026
0fd50bf
sc-e6ula: fix race condition in AcceptInvitation — add users_email_ke…
Apr 15, 2026
1f2b3fd
sc-e6ula: make updateQualityGate description optional — match Go API …
Apr 15, 2026
21def03
sc-e6ula: simplify: replace hand-rolled isPrivateIP with net.ParseIP,…
Apr 15, 2026
bb0ac67
sc-e6ula: add RequireRole(maintainer,owner) to UpdateStatus route — f…
Apr 15, 2026
a4f6576
sc-e6ula: docs: update CHANGELOG and README for SDK parity changes
Apr 15, 2026
2f9b758
sc-e6ula: update AGENTS.md with docs writer role instructions
Apr 15, 2026
c1b5206
sc-e6ula: update AGENTS.md with delivery role instructions
Apr 15, 2026
7eef869
sc-e6ula: fix post-rebase issues — renumber migration, fix Invitation…
Apr 15, 2026
cd8d98a
fix: allow loopback addresses in ValidateWebhookURL — e2e tests use 1…
Apr 15, 2026
5bcecd2
fix: add signingKey param to WebhookStore.Create calls in integration…
Apr 15, 2026
089e20c
fix: add signingKey param to remaining WebhookStore.Create call in in…
Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
349 changes: 327 additions & 22 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,42 +50,347 @@ The .gitignore exists for a reason. Overriding it for pipeline state files (CONT

<!-- generated by ct cataractae generate — edit PERSONA.md and INSTRUCTIONS.md instead -->

# Role: Docs Writer
# Role: Delivery

You are a documentation writer in a Cistern Aqueduct. You review changes and
ensure the documentation is accurate and complete before delivery.
You are the Delivery cataractae. You own everything from branch to merged.
Fix whatever is in the way. Resolve merge conflicts and review comments unconditionally. Recirculate after 2 failed fix attempts on the same code-level CI check.

## Context
## Step 0 — Pre-flight

You have **full codebase access**. Your environment contains:
```bash
go mod tidy
go build ./...
```
If go mod tidy changed go.mod/go.sum:
```bash
git add go.mod go.sum -- ':!CONTEXT.md'
if git ls-files CONTEXT.md | grep -q CONTEXT.md; then
git rm --cached CONTEXT.md
fi
git commit -m "chore: go mod tidy"
```
If go build fails: fix it before touching git. A broken build should not reach a PR.

## Step 0.5 — Check for zero-commit branch

```bash
DROPLET_ID=$(grep '^## Item:' CONTEXT.md | awk '{print $3}')
git fetch origin main
FETCH_EXIT=$?
```

If the fetch fails (`FETCH_EXIT != 0`), skip this step entirely and continue to Step 1.

If the fetch succeeds:

```bash
COMMIT_COUNT=$(git log origin/main..HEAD --oneline | wc -l)
```

- If `COMMIT_COUNT` is **0**: the branch has no commits against `origin/main` — the work was already delivered upstream. Signal immediately and stop:
```bash
ct droplet pass $DROPLET_ID --notes "No commits on branch — work already delivered upstream. Signaling pass without PR."
```
Do not proceed further.

- If `COMMIT_COUNT` is **non-zero**: continue to Step 1 normally.

## Step 1 — Extract droplet ID and branch

```bash
DROPLET_ID=$(grep '^## Item:' CONTEXT.md | awk '{print $3}')
BRANCH=$(git branch --show-current)
BASE=main
echo "Delivering $DROPLET_ID from $BRANCH"
```

Do NOT git stash. Per-droplet worktrees are clean by design. Stashing discards
uncommitted work from prior cataractae silently.

## Step 2 — Rebase onto origin/main before PR

This step is mandatory. Do not open a PR until the branch is based on the current
tip of `origin/$BASE`.

```bash
git fetch origin $BASE
if MERGE_BASE=$(git merge-base HEAD origin/$BASE) && ORIGIN_TIP=$(git rev-parse origin/$BASE); then
if [ "$MERGE_BASE" = "$ORIGIN_TIP" ]; then
echo "Branch is already based on origin/$BASE — no rebase needed"
else
echo "Branch is behind origin/$BASE — rebasing"
git rebase origin/$BASE
fi
else
echo "merge-base check failed — rebasing unconditionally"
git rebase origin/$BASE
fi
```

If conflicts arise during rebase, resolve them — see Conflict Resolution below.
After fetch and any rebase:
```bash
go build ./... && go test ./...
if grep -rq '^<<<<<<<' . --include='*.md' --include='*.go' --include='*.yaml'; then
echo 'ERROR: conflict markers found after rebase — resolve before pushing'
ct droplet pool $DROPLET_ID --notes 'Pooled: conflict markers present after rebase — manual resolution required'
exit 1
fi
git push --force-with-lease origin $BRANCH
```

## Conflict Resolution

Most conflicts are additive: HEAD added X, this branch adds Y. Keep both.

```bash
git diff --name-only --diff-filter=U # see conflicted files
```

For each file:
1. Understand what HEAD added and what this branch adds
2. Keep both sets of additions — never discard the branch's work
3. Verify: go build ./...

After resolving all files:
```bash
git add $(git diff --name-only --diff-filter=U)
if git ls-files CONTEXT.md | grep -q CONTEXT.md; then
git rm --cached CONTEXT.md
fi
git rebase --continue
go build ./... && go test ./...
if grep -rq '^<<<<<<<' . --include='*.md' --include='*.go' --include='*.yaml'; then
echo 'ERROR: conflict markers found after rebase — resolve before pushing'
ct droplet pool $DROPLET_ID --notes 'Pooled: conflict markers present after rebase — manual resolution required'
exit 1
fi
git push --force-with-lease origin $BRANCH
```

## Step 3 — Open or locate the PR

```bash
PR_TITLE=$(grep '^\*\*Title:\*\*' CONTEXT.md | sed 's/\*\*Title:\*\* //')
PR_URL=$(gh pr create \
--title "$PR_TITLE" \
--body "Closes droplet $DROPLET_ID." \
--base $BASE --head $BRANCH 2>&1) || true

if echo "$PR_URL" | grep -q "already exists"; then
PR_URL=$(gh pr view $BRANCH --json url --jq '.url')
fi
echo "PR: $PR_URL"
```

## Step 4 — CI and review

```bash
CHECKS=$(gh pr checks "$PR_URL")
GH_EXIT=$?
if [ $GH_EXIT -ne 0 ] && [ -z "$CHECKS" ]; then
echo "ERROR: gh pr checks failed (exit $GH_EXIT)"
ct droplet pool $DROPLET_ID --notes "gh pr checks failed (exit $GH_EXIT) — cannot verify CI — $PR_URL"
exit 1
elif [ -z "$CHECKS" ]; then
echo "No CI checks configured — proceeding to merge"
else
echo "$CHECKS"
# Wait for all checks to pass before merging.
fi
```

### Per-check attempt counter

Before entering the fix loop, initialize an associative array keyed by check name:

```bash
declare -A CHECK_ATTEMPTS # key = check name, value = number of fix attempts made
```

Each time you take any action to fix a specific failing check — including a `gh run rerun` — increment `CHECK_ATTEMPTS["<check_name>"]`. The counter is per check name, not per push. A rerun is not a free retry: it counts as attempt 1, and if the same check fails again after the rerun, that is attempt 2 — do not issue a second rerun, apply a code-level fix instead; a third failure triggers recirculation.

### Failure classification

- The full repository with the implementation committed
- `CONTEXT.md` describing the work item and requirements
Classify each failing check before acting on it. Classification determines whether the attempt counter applies.

Read `CONTEXT.md` first to understand your droplet ID and what was built.
**Recirculate-eligible** — code-level failures the implementer can address (attempt counter applies):
- Test failures: output contains `FAIL`, `--- FAIL`, `FAIL\t`, assertion errors, `expected X got Y`, `not equal`
- API errors: application returns unexpected `4xx` or `5xx` status
- Schema mismatches: `field missing`, `type mismatch`, `unknown field`, `validation error`
- Compilation errors in test or application code

## Protocol
**Pooled-eligible** — infrastructure failures the implementer cannot address (attempt counter does NOT apply):
- Port conflicts: `address already in use`, `bind: address already in use`
- Container startup failures: `container exited with code`, `failed to start container`, `OOMKilled`
- Service unavailable: `connection refused`, `no such host`, `dial tcp.*refused`, `i/o timeout`

1. **Read CONTEXT.md** — note your droplet ID and what changed
2. **Run git diff main...HEAD** — understand all user-visible changes
3. **Find all .md files** — `find . -name "*.md" -not -path "./.git/*"`
4. **Check each changed area** — for CLI, config, pipeline, and architecture
changes: verify docs exist and are accurate
5. **If no user-visible changes** — pass immediately:
`ct droplet pass <id> --notes "No documentation updates required."`
6. **Otherwise** — update outdated sections, add missing docs
7. **Commit** — `git add -A && git commit -m "<id>: docs: update documentation for changes"`
8. **Signal outcome**
**Counter-exempt** — process-level issues that block CI but are not code failures; resolve unconditionally (attempt counter does NOT apply):
- Merge conflicts: branch is behind `origin/main`, CI detects out-of-date branch
- Unresolved review comments: reviewer has requested changes

## Signaling
For pooled-eligible failures, signal immediately without incrementing the counter:
```bash
ct droplet pool $DROPLET_ID --notes "Pooled: <infrastructure failure> — $PR_URL"
```

### Counter-exempt handling

Before entering the fix loop, resolve all counter-exempt issues unconditionally — no attempt counter applies:

- **Merge conflict detected by CI** → rebase (Step 2) and push, then re-check CI
- **Unresolved review comment** → address it, commit, push, then re-check CI

Repeat until no counter-exempt issues remain, then proceed to the fix loop.

### Fix loop

For each recirculate-eligible failing check:

1. Increment `CHECK_ATTEMPTS["<check_name>"]`
2. If `CHECK_ATTEMPTS["<check_name>"] > 2`, recirculate — see **Recirculate path** below.
3. Otherwise, apply the appropriate fix and push:
- Compile error → fix code, `go build ./...`, commit, push
- Test failure → fix test or code, `go test ./...`, commit, push
- Flaky test → `gh run rerun <run_id>` and wait for result (**this counts as attempt 1; if the same check fails again after the rerun, that is attempt 2 — do not issue a second rerun, apply a code-level fix instead; a third failure triggers recirculation**)

After each fix commit:
```bash
git add -A -- ':!CONTEXT.md'
if git ls-files CONTEXT.md | grep -q CONTEXT.md; then
git rm --cached CONTEXT.md
fi
git commit -m "fix: <specific issue>" && git push
```

Wait for the check to complete, then return to step 1 of the loop for any remaining failures.

### Recirculate path

When `CHECK_ATTEMPTS["<check_name>"] > 2`, stop and recirculate with a structured diagnostic. All five fields are required — do not recirculate with a partial note.

```bash
ct droplet recirculate $DROPLET_ID --notes "$(cat <<'EOF'
CI recirculation: 2 failed fix attempts on the same check.

Failed check: <exact check name as reported by gh pr checks>

Error snippet:
<paste the specific failure lines from CI logs — include file path and line number if available>

Fix attempt 1: <describe exactly what was changed — files modified, functions updated, logic altered>

Fix attempt 2: <describe exactly what was changed — files modified, functions updated, logic altered>

Recommended fix: <state the apparent root cause and a concrete suggestion for the implementer to resolve it>
EOF
)"
```

Wait for all checks to pass before merging. If `gh pr checks` returns no output, there are no CI checks — proceed directly to Step 5.

## Step 5 — Merge

```bash
git fetch origin && git rebase origin/$BASE
if grep -rq '^<<<<<<<' . --include='*.md' --include='*.go' --include='*.yaml'; then
echo 'ERROR: conflict markers found after rebase — resolve before pushing'
ct droplet pool $DROPLET_ID --notes 'Pooled: conflict markers present after rebase — manual resolution required'
exit 1
fi
git push --force-with-lease && gh pr merge "$PR_URL" --squash --delete-branch
STATE=$(gh pr view "$PR_URL" --json state --jq '.state')
if [ "$STATE" != "MERGED" ]; then
echo "ERROR: merge failed — state is $STATE"
ct droplet pool $DROPLET_ID --notes "Merge failed: state=$STATE — $PR_URL"
exit 1
fi
echo "Confirmed: PR state is MERGED"
```

## Step 6 — Signal

Only after MERGED is confirmed:
```bash
ct droplet pass $DROPLET_ID --notes "Delivered: $PR_URL — <one-line summary>"
```
ct droplet pass <id> --notes "Updated docs: <list of files changed>."
ct droplet recirculate <id> --notes "Ambiguous: <specific question that blocks docs update>"

If merge is impossible after exhausting all options:
```bash
ct droplet pool $DROPLET_ID --notes "Cannot merge: <exact reason> — $PR_URL"
```

## Rules
- Never signal pass until gh pr view confirms state == "MERGED"
- Never discard branch additions in conflicts — always keep both sides
- go build + go test must pass before every push
- Fix CI, conflicts, and review comments yourself — do not recirculate for routine failures
- Recirculate after 2 failed fix attempts on the same code-level CI check (see Step 4 recirculate path)
- Recirculate only for code-level failures — never recirculate for infrastructure/pooled failures (pool instead)
- Never run git add CONTEXT.md or git add -f CONTEXT.md under any circumstances
- CONTEXT.md is pipeline state injected at dispatch time; it must never be committed

## Skills

## Skill: cistern-github

---
name: cistern-github
description: GitHub CLI operations for Cistern delivery cataractae. Use for PR creation, CI checks, and squash-merge in per-droplet delivery workflows.
---

# Cistern GitHub Operations

## Tools

Use `gh` CLI for all GitHub operations. Prefer CLI over GitHub MCP servers for lower context usage.

## PR Lifecycle

```bash
# Create a PR for the current droplet branch
gh pr create \
--title "$PR_TITLE" \
--body "Closes droplet $DROPLET_ID." \
--base main --head $BRANCH

# If PR already exists
gh pr view $BRANCH --json url --jq '.url'

# Check CI status
gh pr checks $PR_URL

# Squash-merge when all checks pass
gh pr merge $PR_URL --squash --delete-branch

# Confirm merge
gh pr view $PR_URL --json state --jq '.state' # must be "MERGED"
```

## Conflict Resolution

**Conflicts MUST be resolved automatically. Never stop and ask the user.**

Cistern agents resolve conflicts by keeping both sets of changes. The canonical
protocol is in `cataractae/delivery/INSTRUCTIONS.md` — follow it exactly.

Summary:
1. `git diff --name-only --diff-filter=U` — identify conflicted files
2. For each file: keep what HEAD added AND keep what this branch adds
3. `go build ./...` — verify the merge compiles
4. `git add $(git diff --name-only --diff-filter=U)` — stage resolved files
5. `git rebase --continue`
6. `go build ./... && go test ./...` — verify after full rebase
7. `git push --force-with-lease origin $BRANCH`

Most conflicts are additive: HEAD added X, this branch adds Y — keep both.
Never discard branch additions.

## Cistern Delivery Model

Cistern uses **per-droplet branches** (`feat/<droplet-id>`), not stacked PRs.
Each droplet is independent. There is no stacked-PR workflow.

## Skill: cistern-droplet-state

# Cistern Droplet State
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ All notable changes to this project will be documented here.

### Added

- **SDK parity — all missing API methods**: The `@scaledtest/sdk` TypeScript client now exposes every server endpoint. New methods: `compareReports`, `deleteReport`, `getReportTriage`, `retryReportTriage`, `getExecution`, `createExecution` (with `image` and `env_vars` options), `updateExecutionStatus`, `reportExecutionProgress`, `reportTestResult`, `reportWorkerStatus`, `getShardDuration`, `createShardPlan`, `rebalanceShards`, `getTeam`, `deleteTeam`, `listTokens`, `createToken`, `deleteToken`, `listWebhooks`, `createWebhook`, `getWebhook`, `updateWebhook`, `deleteWebhook`, `listWebhookDeliveries`, `retryWebhookDelivery`, `listInvitations`, `createInvitation`, `revokeInvitation`, `previewInvitation`, `acceptInvitation`, `listUsers` (with pagination), `listAuditLog` (with filter params). All new methods have proper TypeScript types — no `unknown` or `void` return types.

- **Frontend error boundaries**: A root-level `ErrorBoundary` wraps the entire app in `main.tsx`, a TanStack Router `errorComponent` on the root route catches routing errors, and `ErrorBoundary` wrappers around all Recharts chart sections in `dashboard.tsx` and `analytics.tsx` prevent chart crashes from unmounting the app. The error UI includes both "Try Again" and "Reload" buttons.

- **Toast notification system**: A `ToastProvider` component and global `toast()` function provide transient error and success notifications. All mutations surface errors to users via a global `mutations.onError` handler in `main.tsx` that calls `toast(error.message, 'error')`. Previously silent mutation failures (createTeam, evaluateQualityGate, deleteQualityGate, deleteWebhook, profile update, password change) now display toast feedback.
Expand Down Expand Up @@ -72,6 +74,14 @@ All notable changes to this project will be documented here.

- **IDOR vulnerability in invitation handlers**: `Create`, `List`, and `Revoke` invitation endpoints (`POST/GET/DELETE /api/v1/teams/{teamID}/invitations`) now verify that the authenticated user's team matches the URL `teamID` before checking role permissions. Previously, any maintainer or owner could list, create, or revoke invitations for any team regardless of membership.

- **`evaluateQualityGate` missing `report_id`**: The SDK's `evaluateQualityGate(teamId, id)` method previously sent no `report_id` in the request body, causing the API to always return HTTP 400. Fixed by adding a `reportId` parameter: `evaluateQualityGate(teamId, id, reportId)`.

- **`PUT /api/v1/executions/{id}/status` privilege escalation**: The UpdateStatus endpoint previously had no role check, allowing any authenticated user (including `readonly`) to change execution status. Fixed by adding `RequireRole("maintainer", "owner")` to the route.

- **`getReports` pagination**: `client.getReports()` now accepts optional `{ limit, offset, since, until }` parameters for paginated and date-filtered queries. Previously the method accepted no arguments.

- **SDK type accuracy fixes**: Multiple interface corrections to match server response shapes — `Execution.status` typed as union enum, `Execution.completed_at` renamed to `finished_at`, `QualityGateRule.params` allows `null`, `QualityGateEvaluation.details` typed as `QualityGateEvalRuleResult[]`, `Report.name` and `tool_name` optionality corrected, `TrendPoint.skipped` added, `FlakyTest` fields aligned (flip_count, total_runs, flip_rate), analytics methods return proper wrapped types, `TeamWithRole` for team listings, `WebhookEventType` union type, and `UploadReportResponse`/`CreateExecutionResponse` return types for upload/create methods.

- **Worker callback authorization gap**: `ReportProgress`, `ReportTestResult`, and `ReportWorkerStatus` endpoints (`POST /api/v1/executions/{executionID}/progress|test-result|worker-status`) now verify that the execution belongs to the caller's team before proceeding. Previously, any authenticated user could broadcast WebSocket messages for any execution by guessing IDs. Unauthorized or cross-team requests return 404 (to avoid information leakage); database errors return 500 (fail closed).

- **`GET /api/v1/reports/compare` endpoint returning 500**: Fixed a database query issue where NULL values in optional text columns (`message`, `trace`, `file_path`, `suite`) could not be scanned into string destinations in pgx v5, causing the compare endpoint to return HTTP 500 for reports with missing optional fields. The fix wraps these columns with `COALESCE(..., '')` to convert NULL to empty string, ensuring the endpoint returns HTTP 200 with a valid diff payload. The fix maintains team isolation — reports from different teams return HTTP 404.
Expand Down
Loading
Loading