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
28 changes: 28 additions & 0 deletions .claude/rules/22-readonly-proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,31 @@ This is the only flag-only invocation permitted from `/api/*`. Any new
flag-only or subcommand entry requires an updating Forgeplan artifact and a
revision of this rule. See PRD-012 / RFC-011.

## Allow-list extension: `/api/update-check` (non-forgeplan)

`/api/update-check` is the **single** non-forgeplan endpoint permitted from
`/api/*`. It probes the npm registry for the latest published version of
`@forgeplan/web` so the UI can surface an "Update available" affordance.

Constraints (every one of these is enforceable from the diff):

- Method: `GET` only.
- URL: the **string literal** `https://registry.npmjs.org/@forgeplan/web/latest`.
No interpolation, no query params, no user input on the URL path.
- No spawn, no host filesystem write, no Forgeplan invocation. The only
side-effect is a process-local in-memory cache (5 min TTL, single
inflight promise).
- Headers: `accept: application/json` and a static `user-agent`. No cookies,
no credentials.
- Response shape mirrors the standard envelope: `{ ok, data: { current,
latest, hasUpdate }, cmd, error? }` with `current = __FORGEPLAN_WEB_VERSION__`.
- Network failures (timeout, non-2xx, JSON parse error) MUST fall back to
`{ ok: false, error, data: { ..., hasUpdate: false } }` — never throw.

Any additional non-forgeplan endpoint (whether it hits npm, GitHub,
crates.io, or anything else) requires a new Forgeplan artifact and a fresh
amendment to this rule. See PRD-013 / RFC-012.

## Forbidden `forgeplan` subcommands from any `/api/*` endpoint

Any subcommand that mutates the workspace:
Expand Down Expand Up @@ -69,3 +94,6 @@ browser invalidates that.
`args[0] ∈ READ_ONLY_SUBCOMMANDS` before spawning, and the constant MUST
match this allow-list (see rule above). The check is the runtime backstop
for review-time enforcement.
- `grep -RIn "fetch(" template/src/routes/api/` must show external URLs
only inside `update-check/+server.ts`, and the URL must appear as a
string literal (`https://registry.npmjs.org/@forgeplan/web/latest`).
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
depth: standard
id: EVID-017
kind: evidence
last_modified_at: 2026-05-06T16:58:57.023400+00:00
last_modified_by: claude-code/2.1.131
links:
- target: PRD-013
relation: informs
- target: RFC-012
relation: informs
status: draft
title: smoke + svelte-check + live endpoint probe for shared UI + update checker
---

# EVID-017: smoke + svelte-check + live endpoint probe for shared UI + update checker

| Field | Value |
| ----------- | ---------------------- |
| Status | Draft |
| Created | 2026-05-06 |
| Valid Until | 2026-08-06 |
| Target | PRD-013 / RFC-012 |

## Structured Fields

evidence_type: test
verdict: supports
congruence_level: 3

## Measurement

Three direct probes against the surface introduced by PRD-013 / RFC-012:

1. `cd template && npm run check` — `svelte-kit sync` followed by
`svelte-check --tsconfig ./tsconfig.json`. Exercises every `.svelte`,
`.svelte.ts`, and `.ts` file under `template/src/` (including the new
`shared/ui/`, `shared/services/modal/`, and version-footer additions)
for type errors and a11y warnings.
2. `npm run smoke` at the repo root — rebuilds `dist/` from scratch
(`scripts/build.mjs --clean`), `init -y` against a scratch dir, then
`init -y --force`, then boots `node dist/index.js`, and probes
`/api/health`, `/api/list`, `/`. This is the same smoke harness
already used to gate releases.
3. Live spawn of the freshly built `dist/index.js` on PORT=15999 with
FORGEPLAN_CWD=/tmp + curl against the new endpoint.

## Result

```
$ npm run check # in template/
COMPLETED 462 FILES 0 ERRORS 0 WARNINGS 0 FILES_WITH_PROBLEMS

$ npm run smoke # at root
[smoke] /api/health: ok (project=shim)
[smoke] /api/list: ok (0 entries)
[smoke] GET /: ok (HTML returned)
[smoke] PASS

$ curl -s http://127.0.0.1:15999/api/update-check
{"ok":true,"data":{"current":"0.1.11","latest":"0.1.11","hasUpdate":false},
"cmd":"GET registry.npmjs.org/@forgeplan/web/latest"}

$ curl -s http://127.0.0.1:15999/api/version
{"ok":true,"data":{"web":"0.1.11","cli":"0.27.0"},"cmd":"forgeplan --version"}
```

Vite build also reports `entries/endpoints/api/update-check/_server.ts.js
1.78 kB │ gzip: 0.83 kB`, confirming the new endpoint is bundled into
`dist/`.

## Interpretation

- **PRD-013 SC-1** (primitives exist + 1 widget imports): satisfied —
`template/src/widgets/version-footer/ui/UpdateDialog.svelte` imports
`Button`, `Code`, `Dialog` from `@/shared/ui`.
- **PRD-013 SC-2** (caller line count ≤ 3 lines to open a dialog): met
by the `modalManager.open(UpdateDialog, { current, latest })` call
inside `VersionFooter.svelte`.
- **PRD-013 SC-3** (update notice within ≤ 30 min of npm publish):
meets the upper bound by `THIRTY_MINUTES_MS` poll interval +
immediate `start()` on mount; live probe confirms the endpoint is
reachable and returns a well-formed envelope.
- **PRD-013 SC-4** (dialog renders manual update command): rendered by
`UpdateDialog.svelte` via `<Code code="npx @forgeplan/web update" />`.
- **PRD-013 SC-5** (endpoint is GET-only, respects rule 22): the only
HTTP method exported is `GET`; no `spawn` or `runForgeplan` is called
from `update-check/+server.ts`; URL is a string literal.
- **NFR-002** (endpoint never throws on registry failure): try/catch
wraps `getLatestCached`; failure path returns `{ ok: false, ...,
hasUpdate: false }`. Live probe with reachable registry returns the
success path; failure path is exercised structurally.
- **NFR-004** (zero new runtime deps): `git diff develop --
template/package.json` shows no change in `dependencies`.

The svelte-check pass over 462 files (up from 460 — the two new
modules) with zero errors and zero warnings means the new types
(ModalEntry, UpdateData, compareSemver) compose cleanly with existing
code. The smoke pass means `init` + `start` still work end-to-end after
the addition. The live curl proves the endpoint is wired.

## Congruence Level Justification

CL3 — the measurement is run against the exact files PRD-013 and
RFC-012 prescribe (same surface, same project, same commit). The smoke
test exercises the full ship-path (`bin/forgeplan-web.mjs init` →
`node dist/index.js` boot), and the curl probes the new endpoint by
its actual URL. evidence_type=test (binary pass/fail) +
verdict=supports (every assertion held).

## Related Artifacts

| Artifact | Relation |
| -------- | -------- |
| PRD-013 | informs |
| RFC-012 | informs |


Loading
Loading