Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
26 changes: 26 additions & 0 deletions .claude/rules/51-runtime-boundary-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Runtime boundary validation

External values stay `unknown` until a runtime boundary makes them safe.

Use the current Valibot helpers for API JSON and external service responses:

- `jsonValidator(schema)` in `apps/api/src/schemas/_validator.ts` for required Hono request bodies, followed by `c.req.valid('json')`.
- `parseOptionalBody(req, schema, fallback)` for optional request bodies where every schema field is optional.
- `parseWithSchema(schema, value, context)`, `expectJsonRecord`, `maybeJsonRecord`, `parseJsonRecord`, `readRequestJsonRecord`, and `readResponseJson` in `apps/api/src/lib/runtime-validation.ts` for non-Hono runtime JSON boundaries.

Sanctioned bounded patterns:

- Env access may use a narrow local env interface or a guard-then-cast when a Durable Object receives a structural subset/superset of the Worker env. Keep the cast local to the boundary and document why the fields exist.
- Durable Object stubs may use the existing typed RPC cast pattern after `env.<DO>.get(id)` or service-layer helpers, because Cloudflare's generated stub type cannot express the project-specific RPC surface.
- RPC/tool handlers may use a bounded handler cast at the registration boundary when the runtime dispatcher enforces the call shape elsewhere.
- Guard-then-cast is acceptable for small structural checks when schema parsing would be excessive: check object/null/array shape and required field types immediately before the cast.

Do not replace established bounded Zod subsystems incidentally. New API/runtime-validation work should prefer the Valibot helpers above unless the touched subsystem already has a contained Zod boundary.

Avoid these patterns in new code:

- `await c.req.json<T>()` without `jsonValidator` or an equivalent parser.
- `JSON.parse(raw) as T` except for `as unknown` followed by validation.
- Local `isRecord`/`isObject` helpers that recreate shared validation helpers.
- D1/Durable Object row arrays narrowed directly from `.toArray()`, `.first()`, `.all()`, or raw SQL results without a row mapper, schema parse, or guard.
- External webhook/fetch/request payloads narrowed directly to domain types without validation.
6 changes: 0 additions & 6 deletions .eslintignore

This file was deleted.

4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ updates:
directory: '/packages/harness'
schedule:
interval: weekly
- package-ecosystem: 'gomod'
directory: '/scripts/quality/govulncheck-tool'
schedule:
interval: weekly
- package-ecosystem: 'docker'
directory: '/apps/api'
schedule:
Expand Down
119 changes: 117 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:

env:
DEVCONTAINERS_CLI_VERSION: 0.80.2
GITLEAKS_VERSION: 8.30.1

permissions:
contents: read
Expand Down Expand Up @@ -39,6 +40,7 @@ jobs:
devcontainer: ${{ steps.filter.outputs.devcontainer }}
devcontainer-volume-mount: ${{ steps.filter.outputs.devcontainer-volume-mount }}
web-ui: ${{ steps.filter.outputs.web-ui }}
go-modules: ${{ steps.filter.outputs.go-modules }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
Expand Down Expand Up @@ -68,6 +70,11 @@ jobs:
- 'packages/terminal/**'
- 'packages/acp-client/**'
- '.github/workflows/ci.yml'
go-modules:
- '**/go.mod'
- '**/go.sum'
- 'scripts/quality/check-go-vulnerability-diff.ts'
- '.github/workflows/ci.yml'

preflight-evidence:
name: Preflight Evidence
Expand Down Expand Up @@ -142,6 +149,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9

Expand All @@ -153,9 +162,69 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Lint
- name: Check formatting
run: pnpm format:check

- name: Run Oxlint shadow
run: pnpm lint:oxlint

- name: Run ESLint authoritative layer
run: pnpm lint

- name: Enforce type-boundary ratchet
run: pnpm quality:type-boundaries

- name: Run SAM RuleTester fixtures
run: pnpm --filter @simple-agent-manager/eslint-plugin-sam test

- name: Shadow SAM fixtures through Oxlint alpha host
continue-on-error: true
run: pnpm lint:oxlint:sam-shadow

secret-scan:
name: Secret Scan
if: github.event_name == 'pull_request' || github.repository == 'raphaeltm/simple-agent-manager'
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts

- name: Install verified Gitleaks release
env:
GH_TOKEN: ${{ github.token }}
run: |
mkdir -p "$RUNNER_TEMP/gitleaks"
cd "$RUNNER_TEMP/gitleaks"
gh release download "v${GITLEAKS_VERSION}" \
--repo gitleaks/gitleaks \
--pattern "gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
--pattern "gitleaks_${GITLEAKS_VERSION}_checksums.txt"
sha256sum --ignore-missing --check "gitleaks_${GITLEAKS_VERSION}_checksums.txt"
tar -xzf "gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"

- name: Scan current tree
env:
SAM_GITLEAKS_BIN: ${{ runner.temp }}/gitleaks/gitleaks
run: pnpm quality:gitleaks:current

- name: Scan pull request commit range
if: github.event_name == 'pull_request'
env:
SAM_GITLEAKS_BIN: ${{ runner.temp }}/gitleaks/gitleaks
run: pnpm quality:gitleaks:pr

typecheck:
name: Type Check
if: github.event_name == 'pull_request' || github.repository == 'raphaeltm/simple-agent-manager'
Expand Down Expand Up @@ -332,6 +401,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9

Expand Down Expand Up @@ -369,8 +440,52 @@ jobs:

- name: Dependency governance pinning check
run: pnpm quality:dependency-governance

- name: Direct dependency evidence
run: pnpm quality:direct-dependency-evidence

- name: Runtime-boundary semantic shadow
run: pnpm quality:runtime-boundary-semantics

- name: CI workflow wiring tests
run: pnpm exec vitest run --config scripts/quality/vitest.config.ts scripts/quality/ci-worker-suite.test.ts
run: pnpm exec vitest run --config scripts/quality/vitest.config.ts ci-quality-program.test.ts ci-worker-suite.test.ts

go-vulnerability-diff:
name: Go Vulnerability Diff
needs: [changes]
if: needs.changes.outputs.go-modules == 'true'
timeout-minutes: 20
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- name: Setup Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: '1.25'

- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts

- name: Install govulncheck
run: |
mkdir -p "$RUNNER_TEMP/go/bin"
cd scripts/quality/govulncheck-tool
go build -o "$RUNNER_TEMP/go/bin/govulncheck" golang.org/x/vuln/cmd/govulncheck

- name: Scan changed Go modules
env:
SAM_GOVULNCHECK_BIN: ${{ runner.temp }}/go/bin/govulncheck
run: pnpm quality:govulncheck-diff

durable-object-workers:
name: Durable Object Workers
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ jobs:
BUCKET_NAME="${{ vars.PULUMI_STATE_BUCKET || format('{0}-pulumi-state', steps.prefix.outputs.value) }}"

set +e
OUTPUT=$(npx wrangler r2 bucket create "$BUCKET_NAME" 2>&1)
OUTPUT=$(pnpm --filter @simple-agent-manager/api exec wrangler r2 bucket create "$BUCKET_NAME" 2>&1)
STATUS=$?
set -e
echo "$OUTPUT"
Expand Down
48 changes: 48 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"categories": {
"correctness": "warn"
},
"env": {
"browser": true,
"builtin": true,
"node": true,
"worker": true
},
"ignorePatterns": ["**/coverage/**", "**/dist/**", "**/node_modules/**", "**/*.cjs", "**/*.js"],
"options": {
"denyWarnings": false,
"reportUnusedDisableDirectives": "off",
"typeAware": false,
"typeCheck": false
},
"plugins": ["eslint", "jsx-a11y", "react", "typescript"],
"rules": {
"eslint/no-console": "off",
"typescript/consistent-type-imports": [
"warn",
{
"disallowTypeAnnotations": false,
"fixStyle": "inline-type-imports",
"prefer": "type-imports"
}
],
"typescript/no-explicit-any": "warn",
"typescript/no-non-null-assertion": "warn",
"typescript/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }]
},
"overrides": [
{
"excludeFiles": ["apps/api/src/lib/logger.ts"],
"files": ["apps/api/src/**/*.ts"],
"rules": {
"eslint/no-console": "warn"
}
}
],
"settings": {
"react": {
"version": "19.2.7"
}
}
}
21 changes: 21 additions & 0 deletions .oxlintrc.sam-shadow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"jsPlugins": [
{
"name": "sam",
"specifier": "./packages/eslint-plugin-sam/src/index.js"
}
],
"options": {
"denyWarnings": false,
"reportUnusedDisableDirectives": "off",
"typeAware": false,
"typeCheck": false
},
"plugins": [],
"rules": {
"sam/no-local-record-guard": "warn",
"sam/no-unsafe-json-parse-assertion": "warn",
"sam/no-unvalidated-request-json": "warn"
}
}
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ dist/
build/
coverage/
.turbo/
.cache/
apps/www/.astro/
apps/www/public/scripts/blog-mermaid.js
apps/www/public/scripts/docs-mermaid.js
apps/www/public/scripts/tracker.js
package-lock.json
yarn.lock
pnpm-lock.yaml
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ packages/
├── terminal/ # Shared terminal component
├── cloud-init/ # Cloud-init template generator
├── acp-client/ # Shared ACP React components (MessageBubble, MessageActions, AudioPlayer)
├── eslint-plugin-sam/ # Unpublished repository-specific ESLint boundary rules
├── ui/ # Design system tokens and shared UI components
└── vm-agent/ # Go VM agent (PTY, WebSocket, ACP, MCP tool endpoints)
tasks/ # Task tracking (backlog -> active -> archive)
Expand All @@ -33,8 +34,13 @@ pnpm test # Run tests
pnpm typecheck # Type check
pnpm lint # Lint
pnpm format # Format
pnpm check:fast # Deterministic local quality contract used by CI leaf commands
```

`pnpm check:fast` runs the formatting ratchet, report-only Oxlint shadow, authoritative
ESLint workspace checks (including the SAM custom-rule tail), and the blocking type-boundary
ratchet. Scanner and quality-policy commands are documented in `scripts/quality/README.md`.

## Build Order

Build packages in dependency order: `shared` -> `providers` -> `cloud-init` -> `api` / `web`
Expand Down
Loading
Loading