Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ ROADMAP.md
.mcp.json
# Owned disposable agent verification state (contains test credentials).
.agent-state/

# tsforge review cache (local tooling by the same author; never part of the template)
.tsforge/
12 changes: 4 additions & 8 deletions apps/api/security-spec/f04-mfa-attempt-accounting.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import { beforeEach, describe, expect, test } from "bun:test";
import { Redis } from "ioredis";

import { getValkeyAppClientOptions } from "../src/clients/valkey/valkey.utils";
import { now } from "../src/lib/time/now";
import { mfaService } from "../src/api/auth/services/mfa.service";
import { MFA_MAX_CHALLENGE_ATTEMPTS } from "../src/api/auth/mfa.constants";
Expand All @@ -45,16 +46,11 @@ import {
import { seedVerifiedUser } from "../tests/helpers/auth";
import { raceAll, requireDbOrFail, requireValkeyOrFail } from "./harness";

/** Opens a raw client against the same Valkey the cache provider uses. */
/** Opens a raw client against the same Valkey (and database index) the app uses. */
const valkeyClient = async (): Promise<Redis> => {
const client = new Redis({
host: process.env.VALKEY_HOST ?? "127.0.0.1",
port: Number(process.env.VALKEY_PORT ?? 6379),
password: process.env.VALKEY_PASSWORD,
lazyConnect: true,
});
const client = new Redis(getValkeyAppClientOptions({ connectTimeout: 500 }));

await client.connect();
await client.ping();

return client;
};
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"check:components": "bun run scripts/check-unused-components.mjs",
"build:site": "bun run generate:scaffold-manifest && bun run generate:og-image && astro build && bun run sanitize:llms",
"build": "bun run generate:scaffold-manifest && bun run generate:og-image && astro build && bun run sanitize:llms",
"build:ci": "bun run check:docs-data && bun run check:components && bun run generate:og-image && astro build && bun run sanitize:llms && bun run check:fragments && bun run check:rendered-markdown && bun run check:agent-surface",
"build:ci": "bun run check:docs-data && bun run check:components && astro build && bun run sanitize:llms && bun run check:fragments && bun run check:rendered-markdown && bun run check:agent-surface",
"preview": "bun run build:site && wrangler dev",
"astro": "astro",
"deploy": "bun run build:ci && wrangler deploy",
Expand Down
1 change: 1 addition & 0 deletions apps/ui/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ scripts/lint-meta/RULES.md
# Untracked working docs (code-review swarms, scratchpads). Anything matching
# this glob is operator-local and should not gate format:check.
code-review-swarm-*.md
.tsforge
2 changes: 1 addition & 1 deletion docs/maintenance/product-feedback.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ agent-built product. Existing security and coverage gates remain enforced.
| 29 | Feature-page size budgets are set for demo-sized pages. | Policy retained: route budgets remain reviewed per feature; no blanket increase to 25 KB. |
| 30 | `bun test` at the api root runs the infra-gated security spec. | Addressed: API contract and validation guide specify bun run test, separate from security specs. |
| 31 | The agent runtime migrates but never seeds. | Implemented: db:prepare runs migrations then the seed hook in owned runtimes and production; product reference seeds must be idempotent. |
| 32 | `agent:verify --profile=feature` takes ~5 minutes and is strictly serial. | Partial: human verification output includes durations. Stateful lanes remain serial; parallelization needs isolation evidence. |
| 32 | `agent:verify --profile=feature` takes ~5 minutes and is strictly serial. | Implemented: `agent:verify` runs its lanes in parallel within a per-core budget (`AGENT_VERIFY_PARALLEL` overrides); the stateful lanes each own a database and Valkey index inside the sandbox, which is the isolation evidence the serial design was waiting for. Durations stay in the human output. |
| 33 | Local `bun run test` silently skips every database test. | Implemented: explicitly requested unreachable DB fails; unconfigured unit-only runs remain distinct. Regression rejects old silent return. |
| 34 | `docker compose -p <project> run …` outside `dev.sh` silently recreates the dev stack. | Documented: use infra/compose/compose/dev.sh for project/overlay consistency. |
| 35 | Component anatomy vs. real pages: the `single-semantic-module` rule fights every non-trivial file. | Implemented upstream with 21 and 51: only exported declarations define a module's category, so page files keep their private helpers and constants; two exported categories still conflict, which is the boundary the rule exists for. |
Expand Down
2 changes: 2 additions & 0 deletions tools/agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Local release evidence excludes GitHub-only CodeQL, dependency review, secret sc

Each `sandbox:up` creates pinned Postgres and Valkey containers with random loopback ports, random Postgres and Valkey credentials and checkout/run ownership labels. Credentials live only in a mode-0600 ignored descriptor under `.agent-state/sandboxes`; JSON output omits them. Verification ignores caller database URLs and uses the descriptor after checking ownership and live bindings. Database tests are destructive **inside that sandbox**.

Within one run, verification lanes execute in parallel. Stateless lanes (tooling quality, API and UI checks, UI unit tests, builds, size gates) share nothing; each stateful lane (API tests, security spec, browser run, coverage) owns its own Postgres database and Valkey index inside the sandbox (`app_tests`, `app_security`, `app_e2e`, `app_coverage`, created and migrated by the runner), so they never truncate each other's tables or share rate-limit counters. The budget defaults to roughly one lane per three cores, between two and six; `AGENT_VERIFY_PARALLEL=1` restores a strictly sequential run and `AGENT_VERIFY_PARALLEL=8` widens it. Reports list checks in a fixed order whatever the completion order.

Separate checkouts and sandboxes can run concurrently. Operations that generate/build/verify in the same checkout are serialized by a checkout lock, even with different sandbox IDs. A lease prevents overlapping verification suites on the same ID. `sandbox:down` refuses live leases, checks labels again, and removes only that run's containers and volumes. After an interrupted process exits, `sandbox:down -- --id=<id>` can reclaim its stale lease. It never globally prunes Docker or flushes a shared cache. If Docker cleanup fails, preserve the descriptor and retry the same ID.

The harness disables optional outbound providers and does not inherit developer secrets. This is development tooling for reviewed source, not a security sandbox for hostile code. API code still executes locally and can access local files/network. Do not execute an untrusted submission under credentials or mistake Docker data isolation for an OS sandbox.
Expand Down
17 changes: 14 additions & 3 deletions tools/agent/checks.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import type { SandboxLaneName } from "./sandbox/lifecycle";

export interface ICommandCheck {
id: string;
app: "api" | "ui" | "docs" | "root";
script: string;
nodeEnv?: "production";
/** Stateful scripts run against their own lane database. */
lane?: SandboxLaneName;
/** Runs only after the named check has finished (it reads that check's output). */
after?: string;
}
/** References existing package scripts; parity tests reject drift. No arbitrary shell fragments. */
export const STATIC_CHECKS: readonly ICommandCheck[] = [
Expand All @@ -15,11 +21,16 @@ export const STATIC_CHECKS: readonly ICommandCheck[] = [
{ id: "docs.data", app: "docs", script: "check:docs-data" },
];
export const RELEASE_CHECKS: readonly ICommandCheck[] = [
{ id: "api.coverage", app: "api", script: "test:coverage" },
{ id: "api.coverage", app: "api", script: "test:coverage", lane: "coverage" },
{ id: "api.build", app: "api", script: "build" },
{ id: "ui.build", app: "ui", script: "build", nodeEnv: "production" },
{ id: "ui.bundle", app: "ui", script: "size:check" },
{ id: "ui.modulepreload", app: "ui", script: "size:check:modulepreload" },
{ id: "ui.bundle", app: "ui", script: "size:check", after: "ui.build" },
{
id: "ui.modulepreload",
app: "ui",
script: "size:check:modulepreload",
after: "ui.build",
},
{ id: "docs.build", app: "docs", script: "build:ci", nodeEnv: "production" },
];
export const PROFILES = [
Expand Down
3 changes: 3 additions & 0 deletions tools/agent/inventories/api.tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,9 @@
"[\"tests/lib/audit-log/audit-log.service.test.ts\",\"returns success=false without throwing when the DB insert fails\"]",
"[\"tests/lib/audit-log/audit-log.service.test.ts\",\"still returns events matched by the account:{id} resource convention\"]",
"[\"tests/lib/audit-log/audit-log.service.test.ts\",\"writes a row with the given action and metadata\"]",
"[\"tests/lib/cache/cache.generation.test.ts\",\"an untouched namespace is at generation 0 and keys carry it\"]",
"[\"tests/lib/cache/cache.generation.test.ts\",\"bumping moves every scoped key so cached reads become unreachable\"]",
"[\"tests/lib/cache/cache.generation.test.ts\",\"bumps are cumulative and return the new generation\"]",
"[\"tests/lib/cache/cache.service.test.ts\",\"del removes a previously set key\"]",
"[\"tests/lib/cache/cache.service.test.ts\",\"exposes the full ICacheService contract\"]",
"[\"tests/lib/cache/cache.service.test.ts\",\"round-trips a value through the singleton (memory provider in test env)\"]",
Expand Down
38 changes: 26 additions & 12 deletions tools/agent/inventories/ui.tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -187,21 +187,9 @@
"[\"src/features/auth/Auth.queries.test.tsx\",\"useLogin &gt; surfaces 401 as a mutation error\"]",
"[\"src/features/auth/Auth.queries.test.tsx\",\"useLogin &gt; throws on an empty response body (defensive guard)\"]",
"[\"src/features/auth/Auth.queries.test.tsx\",\"useLogout &gt; succeeds on 204\"]",
"[\"src/features/auth/Auth.queries.test.tsx\",\"useMe &gt; propagates 401 as an ApiError (consumer distinguishes auth failure from anonymous)\"]",
"[\"src/features/auth/Auth.queries.test.tsx\",\"useMe &gt; propagates 5xx server errors instead of silently logging the user out\"]",
"[\"src/features/auth/Auth.queries.test.tsx\",\"useMe &gt; propagates network errors so the offline fallback can render\"]",
"[\"src/features/auth/Auth.queries.test.tsx\",\"useMe &gt; returns null when the API responds 200 `{ user: null }` (anonymous probe)\"]",
"[\"src/features/auth/Auth.queries.test.tsx\",\"useMe &gt; returns null when the response data is absent\"]",
"[\"src/features/auth/Auth.queries.test.tsx\",\"useMe &gt; returns the full session payload when the API responds 200 with the authenticated shape\"]",
"[\"src/features/auth/Auth.queries.test.tsx\",\"useMfaStatus &gt; propagates 401 as an ApiError\"]",
"[\"src/features/auth/Auth.queries.test.tsx\",\"useMfaStatus &gt; propagates non-ApiError failures\"]",
"[\"src/features/auth/Auth.queries.test.tsx\",\"useMfaStatus &gt; returns the status payload from data.data\"]",
"[\"src/features/auth/Auth.queries.utils.test.ts\",\"isAuthenticatedMe &gt; returns false for non-object primitives\"]",
"[\"src/features/auth/Auth.queries.utils.test.ts\",\"isAuthenticatedMe &gt; returns false for null\"]",
"[\"src/features/auth/Auth.queries.utils.test.ts\",\"isAuthenticatedMe &gt; returns false for the anonymous shape `{ user: null }`\"]",
"[\"src/features/auth/Auth.queries.utils.test.ts\",\"isAuthenticatedMe &gt; returns false for undefined\"]",
"[\"src/features/auth/Auth.queries.utils.test.ts\",\"isAuthenticatedMe &gt; returns false when `user` key is absent (openapi-fetch empty-content branch)\"]",
"[\"src/features/auth/Auth.queries.utils.test.ts\",\"isAuthenticatedMe &gt; returns true when `user` is a non-null object (authenticated shape)\"]",
"[\"src/features/auth/Auth.queries.utils.test.ts\",\"resolveAuthStatus &gt; returns &apos;anonymous&apos; for an explicit null data\"]",
"[\"src/features/auth/Auth.queries.utils.test.ts\",\"resolveAuthStatus &gt; returns &apos;authed&apos; for an IMe payload\"]",
"[\"src/features/auth/Auth.queries.utils.test.ts\",\"resolveAuthStatus &gt; returns &apos;offline&apos; for a 5xx ApiError (not a forced-logout)\"]",
Expand Down Expand Up @@ -498,6 +486,13 @@
"[\"src/lib/guards/isRecord.test.ts\",\"isRecord &gt; rejects functions\"]",
"[\"src/lib/guards/isRecord.test.ts\",\"isRecord &gt; rejects null and undefined\"]",
"[\"src/lib/guards/isRecord.test.ts\",\"isRecord &gt; rejects primitives\"]",
"[\"src/lib/i18n/config.test.ts\",\"application configuration does not preload secondary locale resources\"]",
"[\"src/lib/i18n/locale-backend.test.ts\",\"locale backend &gt; keeps secondary dictionaries unloaded until the language is requested\"]",
"[\"src/lib/i18n/locale-backend.test.ts\",\"locale backend &gt; reports an unknown namespace as a load failure\"]",
"[\"src/lib/i18n/locale-backend.test.ts\",\"locale backend &gt; retains the bundled fallback when a dictionary does not exist\"]",
"[\"src/lib/i18n/useNamespace.test.tsx\",\"useNamespace &gt; allows a loaded fallback when the selected language is missing\"]",
"[\"src/lib/i18n/useNamespace.test.tsx\",\"useNamespace &gt; returns the loaded dictionary\"]",
"[\"src/lib/i18n/useNamespace.test.tsx\",\"useNamespace &gt; throws when no usable dictionary exists\"]",
"[\"src/lib/logger/logger.events.test.ts\",\"LOG_EVENTS &gt; has no duplicate event names\"]",
"[\"src/lib/logger/logger.events.test.ts\",\"LOG_EVENTS &gt; is a non-empty list of event names\"]",
"[\"src/lib/logger/logger.events.test.ts\",\"LOG_EVENTS &gt; namespaces every event as dotted lowercase segments\"]",
Expand All @@ -513,6 +508,18 @@
"[\"src/lib/logger/logger.utils.test.ts\",\"emit (logger.utils) &gt; routes error level to console.error, not console.log\"]",
"[\"src/lib/logger/logger.utils.test.ts\",\"emit (logger.utils) &gt; writes an info entry to console.log with level + timestamp + app\"]",
"[\"src/lib/logger/logger.utils.test.ts\",\"emit (logger.utils) in production mode &gt; records a Sentry breadcrumb and never writes to the console\"]",
"[\"src/lib/session/session.utils.test.ts\",\"isAuthenticatedMe &gt; returns false for non-object primitives\"]",
"[\"src/lib/session/session.utils.test.ts\",\"isAuthenticatedMe &gt; returns false for null\"]",
"[\"src/lib/session/session.utils.test.ts\",\"isAuthenticatedMe &gt; returns false for the anonymous shape `{ user: null }`\"]",
"[\"src/lib/session/session.utils.test.ts\",\"isAuthenticatedMe &gt; returns false for undefined\"]",
"[\"src/lib/session/session.utils.test.ts\",\"isAuthenticatedMe &gt; returns false when `user` key is absent (openapi-fetch empty-content branch)\"]",
"[\"src/lib/session/session.utils.test.ts\",\"isAuthenticatedMe &gt; returns true when `user` is a non-null object (authenticated shape)\"]",
"[\"src/lib/session/useMe.test.tsx\",\"useMe &gt; propagates 401 as an ApiError (consumer distinguishes auth failure from anonymous)\"]",
"[\"src/lib/session/useMe.test.tsx\",\"useMe &gt; propagates 5xx server errors instead of silently logging the user out\"]",
"[\"src/lib/session/useMe.test.tsx\",\"useMe &gt; propagates network errors so the offline fallback can render\"]",
"[\"src/lib/session/useMe.test.tsx\",\"useMe &gt; returns null when the API responds 200 `{ user: null }` (anonymous probe)\"]",
"[\"src/lib/session/useMe.test.tsx\",\"useMe &gt; returns null when the response data is absent\"]",
"[\"src/lib/session/useMe.test.tsx\",\"useMe &gt; returns the full session payload when the API responds 200 with the authenticated shape\"]",
"[\"src/lib/storage/localStorage.test.ts\",\"localStore &gt; clear() removes only the namespaced keys, not foreign ones\"]",
"[\"src/lib/storage/localStorage.test.ts\",\"localStore &gt; namespaces keys under the configured prefix + version\"]",
"[\"src/lib/storage/localStorage.test.ts\",\"localStore &gt; returns null and does not throw when JSON is corrupted\"]",
Expand Down Expand Up @@ -559,6 +566,12 @@
"[\"tests/factories/factories.test.ts\",\"makeUser &gt; produces a payload that passes the userSchema\"]",
"[\"tests/factories/factories.test.ts\",\"makeUser &gt; resetUserFactory makes the next call&apos;s id predictable again\"]",
"[\"tests/factories/factories.test.ts\",\"makeUser &gt; respects overrides\"]",
"[\"tests/lint-meta/eslint-cache.test.ts\",\"the dictionary digest in settings invalidates the ESLint result cache\"]",
"[\"tests/lint-meta/eslint-cache.test.ts\",\"without the digest a deleted key hides behind the cached result\"]",
"[\"tests/lint-meta/feature-namespace.test.ts\",\"a namespace collision is rejected before the feature is written\"]",
"[\"tests/lint-meta/feature-namespace.test.ts\",\"namespace scaffolding wires dictionaries, lint scope, and a separate bundle budget\"]",
"[\"tests/lint-meta/i18n-locales.test.ts\",\"all shipped locale namespaces contain matching non-empty translations\"]",
"[\"tests/lint-meta/i18n-plugin.test.ts\",\"the installed translation rule accepts counted plurals without hiding missing keys\"]",
"[\"tests/lint-meta/lint-meta.test.ts\",\"RULES.md catalog &gt; matches generate-rules-md output\"]",
"[\"tests/lint-meta/lint-meta.test.ts\",\"checkCanonicalHelpersSingleHome &gt; returns no violations on a clean file when the registry is empty\"]",
"[\"tests/lint-meta/lint-meta.test.ts\",\"checkDependencyPairs &gt; exact-deps fixture has no overlap\"]",
Expand Down Expand Up @@ -648,6 +661,7 @@
"[\"tests/lint-meta/lint-meta.test.ts\",\"lint-meta guardrails &gt; checkScriptRawFetch flags fetch in scripts outside allowlist\"]",
"[\"tests/lint-meta/lint-meta.test.ts\",\"lint-meta guardrails &gt; checkUiEnvCascadeDrift flags vite-config-only keys missing from vite-env.d.ts\"]",
"[\"tests/lint-meta/lint-meta.test.ts\",\"lint-meta guardrails &gt; parseDotenvKeys ignores comments and blank lines\"]",
"[\"tests/render-with-providers.test.tsx\",\"each provider render has independent query and translation state\"]",
"[\"tests/sw/sw-url-sanitize.test.ts\",\"clientPathMatches &gt; matches exact same-origin path+search+hash\"]",
"[\"tests/sw/sw-url-sanitize.test.ts\",\"clientPathMatches &gt; rejects malformed client URLs\"]",
"[\"tests/sw/sw-url-sanitize.test.ts\",\"clientPathMatches &gt; rejects off-origin clients\"]",
Expand Down
Loading
Loading