nerdsystem 0.0.15 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/deploy.js +4 -3
- package/dist/commands/init.js +1 -1
- package/dist/gates/rules.js +1 -0
- package/dist/gates/smoke.d.ts +6 -1
- package/dist/gates/smoke.js +15 -1
- package/dist/gates/tests.d.ts +5 -0
- package/dist/gates/tests.js +12 -4
- package/package.json +1 -1
- package/template/.claude/skills/nerdsystem/SKILL.md +1 -1
- package/template/.cursor/rules/nerdsystem.mdc +2 -2
- package/template/.github/copilot-instructions.md +2 -2
- package/template/AGENTS.md +2 -2
- package/template/GEMINI.md +2 -2
package/dist/commands/deploy.js
CHANGED
|
@@ -7,6 +7,7 @@ import { formatMb } from "../snapshot.js";
|
|
|
7
7
|
import { CliError, log, printJson, runOrThrow, table, dashboardUrl, ensureDeps } from "../util.js";
|
|
8
8
|
import { runChecks } from "./check.js";
|
|
9
9
|
import { appendLineage, cliVersion, LOCAL_ONLY_NOTE, packProject, readLineage, uploadSnapshot, uploadStopMessage } from "./source.js";
|
|
10
|
+
import { lockedGetPaths } from "../gates/tests.js";
|
|
10
11
|
import { BUILD_GATES, gateSummary, knownSecretsFrom, probeDeployment, runGates, skippedProbe, SOURCE_GATES, waitForDeployment } from "../gates/index.js";
|
|
11
12
|
const MODULE_EXT = new Set([".js", ".mjs", ".wasm"]);
|
|
12
13
|
function walk(dir, base = dir) {
|
|
@@ -62,7 +63,7 @@ export async function applyRemoteMigrations(root, api) {
|
|
|
62
63
|
* Probes the live URL after an upload, the same checks the builder runs after a git build.
|
|
63
64
|
* Informational: the app is already live, so findings are printed, never thrown.
|
|
64
65
|
*/
|
|
65
|
-
async function probeAfterDeploy(deployment, url, skip) {
|
|
66
|
+
async function probeAfterDeploy(root, deployment, url, skip) {
|
|
66
67
|
if (skip)
|
|
67
68
|
return skippedProbe("--skip-probe was given");
|
|
68
69
|
if (deployment.status !== "live")
|
|
@@ -71,7 +72,7 @@ async function probeAfterDeploy(deployment, url, skip) {
|
|
|
71
72
|
// headers or routes; probing that would report false auth failures. Wait, and say so when it never came.
|
|
72
73
|
if (!(await waitForDeployment(url)))
|
|
73
74
|
return skippedProbe("the app had not started answering within 30 seconds; open the URL to check, or run nerd deploy again to probe it");
|
|
74
|
-
return probeDeployment(url);
|
|
75
|
+
return probeDeployment(url, undefined, { lockedGets: lockedGetPaths(root) });
|
|
75
76
|
}
|
|
76
77
|
function printProbe(url, probes) {
|
|
77
78
|
if (probes.status === "skip")
|
|
@@ -183,7 +184,7 @@ export async function shipBuild(args) {
|
|
|
183
184
|
log.ok(`Deployed ${deployment.id} (${deployment.status}) → ${project.url}`);
|
|
184
185
|
if (!opts["skip-probe"] && deployment.status === "live")
|
|
185
186
|
log.step(`Probing ${project.url}`);
|
|
186
|
-
const probes = await probeAfterDeploy(deployment, project.url, Boolean(opts["skip-probe"]));
|
|
187
|
+
const probes = await probeAfterDeploy(root, deployment, project.url, Boolean(opts["skip-probe"]));
|
|
187
188
|
return { deployment, url: project.url, migrations: mig, probes, source, sourceSkipped, lineage, warnings: pack.warnings };
|
|
188
189
|
}
|
|
189
190
|
/** The one line about the code after a live deploy, keyed on what the API recorded, not on what we sent. */
|
package/dist/commands/init.js
CHANGED
|
@@ -183,4 +183,4 @@ export async function init(dir, args) {
|
|
|
183
183
|
log.ok(`Ready. Next:\n cd ${dir}\n pnpm install\n nerd dev\n\nThen open the folder in your coding agent and ask it to build the first feature; the nerdsystem skill walks it through.\nDeploys go to ${project.url}`);
|
|
184
184
|
}
|
|
185
185
|
// Kept in sync with packages/sdk/package.json by the release script.
|
|
186
|
-
const SDK_VERSION = "0.0.
|
|
186
|
+
const SDK_VERSION = "0.0.16";
|
package/dist/gates/rules.js
CHANGED
|
@@ -70,6 +70,7 @@ export const RULES = {
|
|
|
70
70
|
"gates/retry-cap": { title: "Automatic fixing stopped after three attempts", severity: "block", audience: "human", risk: "The same rule has failed three runs in a row; further automatic attempts are unlikely to help and may make things worse.", fix: "Read the findings above and decide: fix it by hand, ask the agent a different way, or approve a suppression in the dashboard if the finding is wrong." },
|
|
71
71
|
"smoke/unreachable": { title: "Deployed app did not respond", severity: "block", audience: "human", risk: "The app is not serving requests.", fix: "Check `nerd logs` for startup errors and the deployment status." },
|
|
72
72
|
"smoke/header-missing": { title: "A security header is missing", severity: "warn", audience: "agent", risk: "Browsers lose a protection against clickjacking, content sniffing, or script injection.", fix: "Keep createNerdApp()'s default security headers; if you disabled them, re-enable or set the header yourself." },
|
|
73
|
+
"smoke/route-open": { title: "A locked route answers strangers on the live app", severity: "block", audience: "agent", risk: "A route the tests found locked is answering signed-out visitors on the live app, so its data may be readable by anyone right now.", fix: "Run `nerd rollback` to return to the previous version now. Then find why the live app differs from the tests (a route registered only in production, or the `public` list in `createNerdApp`), fix it, and deploy again." },
|
|
73
74
|
"smoke/auth-me": { title: "Session endpoint misbehaves", severity: "block", audience: "agent", risk: "Visitors cannot tell whether they are signed in, so the app's login flow is broken.", fix: "/api/auth/me must return 200 with {user:null} when signed out. Do not override the SDK's auth routes." },
|
|
74
75
|
"smoke/auth-login": { title: "Wrong credentials are not rejected", severity: "block", audience: "agent", risk: "The login endpoint does not return 401 for a wrong password.", fix: "Do not override the SDK's /api/auth/login route." },
|
|
75
76
|
};
|
package/dist/gates/smoke.d.ts
CHANGED
|
@@ -12,7 +12,12 @@ export declare function waitForDeployment(url: string, opts?: {
|
|
|
12
12
|
/**
|
|
13
13
|
* Runs the live-URL probes: security headers on GET /, GET /api/auth/me answers 200 with
|
|
14
14
|
* {"user":null} when signed out, and POST /api/auth/login with wrong credentials answers 401.
|
|
15
|
+
* With `lockedGets` (paths the app's access tests found locked), each is also fetched signed out on
|
|
16
|
+
* the live app and must still answer 401 or 403: the same question the tests asked, put to the real
|
|
17
|
+
* thing. Only GETs are tried, so a probe can never change anyone's data.
|
|
15
18
|
*/
|
|
16
|
-
export declare function probeDeployment(url: string, fetchImpl?: FetchLike
|
|
19
|
+
export declare function probeDeployment(url: string, fetchImpl?: FetchLike, opts?: {
|
|
20
|
+
lockedGets?: string[];
|
|
21
|
+
}): Promise<GateResult>;
|
|
17
22
|
/** The result recorded when the probe did not run, so --json consumers see why rather than a missing key. */
|
|
18
23
|
export declare function skippedProbe(reason: string): GateResult;
|
package/dist/gates/smoke.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import { finding, fingerprint } from "./secrets.js";
|
|
8
8
|
import { verdict } from "./types.js";
|
|
9
9
|
const PROBE_TIMEOUT_MS = 10_000;
|
|
10
|
+
/** Most routes fetched signed out after a deploy. */
|
|
11
|
+
const MAX_ROUTE_PROBES = 25;
|
|
10
12
|
const REQUIRED_HEADERS = ["x-content-type-options", "content-security-policy", "x-frame-options"];
|
|
11
13
|
/** Default fetch wrapped so it is never called with a foreign `this` (workerd throws "Illegal invocation"). */
|
|
12
14
|
const defaultFetch = (input, init) => fetch(input, init);
|
|
@@ -51,8 +53,11 @@ export async function waitForDeployment(url, opts = {}) {
|
|
|
51
53
|
/**
|
|
52
54
|
* Runs the live-URL probes: security headers on GET /, GET /api/auth/me answers 200 with
|
|
53
55
|
* {"user":null} when signed out, and POST /api/auth/login with wrong credentials answers 401.
|
|
56
|
+
* With `lockedGets` (paths the app's access tests found locked), each is also fetched signed out on
|
|
57
|
+
* the live app and must still answer 401 or 403: the same question the tests asked, put to the real
|
|
58
|
+
* thing. Only GETs are tried, so a probe can never change anyone's data.
|
|
54
59
|
*/
|
|
55
|
-
export async function probeDeployment(url, fetchImpl = defaultFetch) {
|
|
60
|
+
export async function probeDeployment(url, fetchImpl = defaultFetch, opts = {}) {
|
|
56
61
|
const startedAt = Date.now();
|
|
57
62
|
const base = url.replace(/\/$/, "");
|
|
58
63
|
const findings = [];
|
|
@@ -93,6 +98,15 @@ export async function probeDeployment(url, fetchImpl = defaultFetch) {
|
|
|
93
98
|
if (login.status !== 401)
|
|
94
99
|
findings.push(smokeFinding("smoke/auth-login", `Wrong credentials returned ${login.status} instead of 401.`, "/api/auth/login"));
|
|
95
100
|
}
|
|
101
|
+
for (const p of (opts.lockedGets ?? []).slice(0, MAX_ROUTE_PROBES)) {
|
|
102
|
+
const res = await get(p);
|
|
103
|
+
if (!res)
|
|
104
|
+
continue;
|
|
105
|
+
await res.body?.cancel().catch(() => { });
|
|
106
|
+
if (res.status !== 401 && res.status !== 403) {
|
|
107
|
+
findings.push(smokeFinding("smoke/route-open", `GET ${p} answered ${res.status} to a signed-out visitor on the live app, although the tests found it locked.`, `route:${p}`));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
96
110
|
return probeResult(findings, startedAt);
|
|
97
111
|
}
|
|
98
112
|
/** The result recorded when the probe did not run, so --json consumers see why rather than a missing key. */
|
package/dist/gates/tests.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ import { type Gate } from "./types.js";
|
|
|
4
4
|
* characters a "METHOD /path" needs survive and the length is capped: a route cannot carry a sentence.
|
|
5
5
|
*/
|
|
6
6
|
export declare function safeRoute(name: unknown): string | null;
|
|
7
|
+
/**
|
|
8
|
+
* Concrete paths for the GET routes the last test run found locked, for the live probe after a deploy:
|
|
9
|
+
* every `:param` becomes "1". Empty when there is no report (an app without access tests).
|
|
10
|
+
*/
|
|
11
|
+
export declare function lockedGetPaths(root: string): string[];
|
|
7
12
|
/**
|
|
8
13
|
* Runs the app's own tests and reads what they checked about access. The person who owns the app cannot
|
|
9
14
|
* read the code, and the agent that forgot a sign-in check tends to forget the test for it too, so the
|
package/dist/gates/tests.js
CHANGED
|
@@ -26,6 +26,18 @@ function readJson(file) {
|
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Concrete paths for the GET routes the last test run found locked, for the live probe after a deploy:
|
|
31
|
+
* every `:param` becomes "1". Empty when there is no report (an app without access tests).
|
|
32
|
+
*/
|
|
33
|
+
export function lockedGetPaths(root) {
|
|
34
|
+
const report = readJson(path.join(root, REPORT));
|
|
35
|
+
if (!report || report.everyRouteChecked !== true)
|
|
36
|
+
return [];
|
|
37
|
+
return list(report.locked)
|
|
38
|
+
.filter((r) => r.startsWith("GET ") && !r.includes("*"))
|
|
39
|
+
.map((r) => r.slice(4).split("/").map((seg) => (seg.startsWith(":") ? "1" : seg)).join("/"));
|
|
40
|
+
}
|
|
29
41
|
/** True when a file under test/ uses the SDK's harness, which is what writes the access report. */
|
|
30
42
|
function hasHarnessTests(root) {
|
|
31
43
|
const dir = path.join(root, "test");
|
|
@@ -62,10 +74,6 @@ export const testsGate = {
|
|
|
62
74
|
const f = finding(required ? "tests/required-missing" : "tests/missing", { file: "test/access.test.ts" });
|
|
63
75
|
return result(this, required ? "fail" : "warn", "No access tests in this app", [f], t);
|
|
64
76
|
}
|
|
65
|
-
// Git builds do not run this yet (the build container has not been proven to run the local database);
|
|
66
|
-
// the terminal path, which every `nerd deploy` takes, does.
|
|
67
|
-
if (ctx.committed)
|
|
68
|
-
return result(this, "skip", "Runs before `nerd deploy`; not yet part of git builds", [], t);
|
|
69
77
|
if (!existsSync(path.join(ctx.root, "node_modules"))) {
|
|
70
78
|
return result(this, "fail", "Dependencies are not installed", [finding("tests/not-installed", {})], t);
|
|
71
79
|
}
|
package/package.json
CHANGED
|
@@ -36,7 +36,7 @@ Tell the person plainly what does not carry. Their users sign up again on the ne
|
|
|
36
36
|
1. Make the change, and write or update its tests in the same step (see "Build it so it is safe and it works").
|
|
37
37
|
2. `nerd check` (static rules, typecheck, then the tests and access checks). Fix anything it reports.
|
|
38
38
|
3. `nerd dev` to run locally when you need to try it. The local app talks to the real auth service, so signup and login work exactly as in production.
|
|
39
|
-
4. `nerd deploy` when it is ready. It runs the checks, builds, stores a copy of the code, applies pending migrations, uploads, prints the URL, and probes the live URL (headers
|
|
39
|
+
4. `nerd deploy` when it is ready. It runs the checks, builds, stores a copy of the code, applies pending migrations, uploads, prints the URL, and probes the live URL (headers, the sign-in routes, and a signed-out GET of every route the tests found locked; a `smoke/route-open` finding means a locked route is answering strangers on the live app, so run `nerd rollback` first and investigate second). With `--json` the output carries `probes`, the same shape as a check result, with `status: "skip"` and the reason when it did not run; probe findings never fail the deploy.
|
|
40
40
|
5. If something is wrong after deploying: `nerd logs` shows recent output from the deployed app (`--q` to search, `--pr 12` for a preview), `nerd deployments` shows status and errors, `nerd db query "..."` inspects production data.
|
|
41
41
|
|
|
42
42
|
Run `nerd check --json` when you want a machine-readable list of problems. Its summary says how many gates ran and which were skipped (`skipped` in the JSON): semgrep and gitleaks only run locally when installed; git builds always run them in the builder.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: How to build, run, and deploy this app on nerdsystem. Applies to server, database, auth, and deploy work.
|
|
3
3
|
alwaysApply: true
|
|
4
4
|
---
|
|
5
|
-
<!-- generated from .claude/skills/nerdsystem/SKILL.md (
|
|
5
|
+
<!-- generated from .claude/skills/nerdsystem/SKILL.md (e719563eac29); edit the skill, then run: nerd agents sync -->
|
|
6
6
|
|
|
7
7
|
# Building on nerdsystem
|
|
8
8
|
|
|
@@ -37,7 +37,7 @@ Tell the person plainly what does not carry. Their users sign up again on the ne
|
|
|
37
37
|
1. Make the change, and write or update its tests in the same step (see "Build it so it is safe and it works").
|
|
38
38
|
2. `nerd check` (static rules, typecheck, then the tests and access checks). Fix anything it reports.
|
|
39
39
|
3. `nerd dev` to run locally when you need to try it. The local app talks to the real auth service, so signup and login work exactly as in production.
|
|
40
|
-
4. `nerd deploy` when it is ready. It runs the checks, builds, stores a copy of the code, applies pending migrations, uploads, prints the URL, and probes the live URL (headers
|
|
40
|
+
4. `nerd deploy` when it is ready. It runs the checks, builds, stores a copy of the code, applies pending migrations, uploads, prints the URL, and probes the live URL (headers, the sign-in routes, and a signed-out GET of every route the tests found locked; a `smoke/route-open` finding means a locked route is answering strangers on the live app, so run `nerd rollback` first and investigate second). With `--json` the output carries `probes`, the same shape as a check result, with `status: "skip"` and the reason when it did not run; probe findings never fail the deploy.
|
|
41
41
|
5. If something is wrong after deploying: `nerd logs` shows recent output from the deployed app (`--q` to search, `--pr 12` for a preview), `nerd deployments` shows status and errors, `nerd db query "..."` inspects production data.
|
|
42
42
|
|
|
43
43
|
Run `nerd check --json` when you want a machine-readable list of problems. Its summary says how many gates ran and which were skipped (`skipped` in the JSON): semgrep and gitleaks only run locally when installed; git builds always run them in the builder.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- generated from .claude/skills/nerdsystem/SKILL.md (
|
|
1
|
+
<!-- generated from .claude/skills/nerdsystem/SKILL.md (e719563eac29); edit the skill, then run: nerd agents sync -->
|
|
2
2
|
|
|
3
3
|
# Building on nerdsystem
|
|
4
4
|
|
|
@@ -33,7 +33,7 @@ Tell the person plainly what does not carry. Their users sign up again on the ne
|
|
|
33
33
|
1. Make the change, and write or update its tests in the same step (see "Build it so it is safe and it works").
|
|
34
34
|
2. `nerd check` (static rules, typecheck, then the tests and access checks). Fix anything it reports.
|
|
35
35
|
3. `nerd dev` to run locally when you need to try it. The local app talks to the real auth service, so signup and login work exactly as in production.
|
|
36
|
-
4. `nerd deploy` when it is ready. It runs the checks, builds, stores a copy of the code, applies pending migrations, uploads, prints the URL, and probes the live URL (headers
|
|
36
|
+
4. `nerd deploy` when it is ready. It runs the checks, builds, stores a copy of the code, applies pending migrations, uploads, prints the URL, and probes the live URL (headers, the sign-in routes, and a signed-out GET of every route the tests found locked; a `smoke/route-open` finding means a locked route is answering strangers on the live app, so run `nerd rollback` first and investigate second). With `--json` the output carries `probes`, the same shape as a check result, with `status: "skip"` and the reason when it did not run; probe findings never fail the deploy.
|
|
37
37
|
5. If something is wrong after deploying: `nerd logs` shows recent output from the deployed app (`--q` to search, `--pr 12` for a preview), `nerd deployments` shows status and errors, `nerd db query "..."` inspects production data.
|
|
38
38
|
|
|
39
39
|
Run `nerd check --json` when you want a machine-readable list of problems. Its summary says how many gates ran and which were skipped (`skipped` in the JSON): semgrep and gitleaks only run locally when installed; git builds always run them in the builder.
|
package/template/AGENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- generated from .claude/skills/nerdsystem/SKILL.md (
|
|
1
|
+
<!-- generated from .claude/skills/nerdsystem/SKILL.md (e719563eac29); edit the skill, then run: nerd agents sync -->
|
|
2
2
|
|
|
3
3
|
# Building on nerdsystem
|
|
4
4
|
|
|
@@ -33,7 +33,7 @@ Tell the person plainly what does not carry. Their users sign up again on the ne
|
|
|
33
33
|
1. Make the change, and write or update its tests in the same step (see "Build it so it is safe and it works").
|
|
34
34
|
2. `nerd check` (static rules, typecheck, then the tests and access checks). Fix anything it reports.
|
|
35
35
|
3. `nerd dev` to run locally when you need to try it. The local app talks to the real auth service, so signup and login work exactly as in production.
|
|
36
|
-
4. `nerd deploy` when it is ready. It runs the checks, builds, stores a copy of the code, applies pending migrations, uploads, prints the URL, and probes the live URL (headers
|
|
36
|
+
4. `nerd deploy` when it is ready. It runs the checks, builds, stores a copy of the code, applies pending migrations, uploads, prints the URL, and probes the live URL (headers, the sign-in routes, and a signed-out GET of every route the tests found locked; a `smoke/route-open` finding means a locked route is answering strangers on the live app, so run `nerd rollback` first and investigate second). With `--json` the output carries `probes`, the same shape as a check result, with `status: "skip"` and the reason when it did not run; probe findings never fail the deploy.
|
|
37
37
|
5. If something is wrong after deploying: `nerd logs` shows recent output from the deployed app (`--q` to search, `--pr 12` for a preview), `nerd deployments` shows status and errors, `nerd db query "..."` inspects production data.
|
|
38
38
|
|
|
39
39
|
Run `nerd check --json` when you want a machine-readable list of problems. Its summary says how many gates ran and which were skipped (`skipped` in the JSON): semgrep and gitleaks only run locally when installed; git builds always run them in the builder.
|
package/template/GEMINI.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- generated from .claude/skills/nerdsystem/SKILL.md (
|
|
1
|
+
<!-- generated from .claude/skills/nerdsystem/SKILL.md (e719563eac29); edit the skill, then run: nerd agents sync -->
|
|
2
2
|
|
|
3
3
|
# Building on nerdsystem
|
|
4
4
|
|
|
@@ -33,7 +33,7 @@ Tell the person plainly what does not carry. Their users sign up again on the ne
|
|
|
33
33
|
1. Make the change, and write or update its tests in the same step (see "Build it so it is safe and it works").
|
|
34
34
|
2. `nerd check` (static rules, typecheck, then the tests and access checks). Fix anything it reports.
|
|
35
35
|
3. `nerd dev` to run locally when you need to try it. The local app talks to the real auth service, so signup and login work exactly as in production.
|
|
36
|
-
4. `nerd deploy` when it is ready. It runs the checks, builds, stores a copy of the code, applies pending migrations, uploads, prints the URL, and probes the live URL (headers
|
|
36
|
+
4. `nerd deploy` when it is ready. It runs the checks, builds, stores a copy of the code, applies pending migrations, uploads, prints the URL, and probes the live URL (headers, the sign-in routes, and a signed-out GET of every route the tests found locked; a `smoke/route-open` finding means a locked route is answering strangers on the live app, so run `nerd rollback` first and investigate second). With `--json` the output carries `probes`, the same shape as a check result, with `status: "skip"` and the reason when it did not run; probe findings never fail the deploy.
|
|
37
37
|
5. If something is wrong after deploying: `nerd logs` shows recent output from the deployed app (`--q` to search, `--pr 12` for a preview), `nerd deployments` shows status and errors, `nerd db query "..."` inspects production data.
|
|
38
38
|
|
|
39
39
|
Run `nerd check --json` when you want a machine-readable list of problems. Its summary says how many gates ran and which were skipped (`skipped` in the JSON): semgrep and gitleaks only run locally when installed; git builds always run them in the builder.
|