Files
sim/scripts
Waleed 81108d5a4e fix(v2): serve HEAD, advertise PATCH, and document the reachable 403 (#6623)
* fix(v2): serve HEAD, advertise PATCH, and document the reachable 403

Three HTTP-semantics defects on the v2 surface, all found by probing the
published contract rather than the happy path.

**HEAD answered 500 on every v2 endpoint.** Next implements a missing `HEAD`
export by aliasing it onto `GET` and dropping the body when it sends, so a
route's `GET` legitimately runs with `request.method === 'HEAD'`. The builders'
method guard compared that against the contract's declared method and threw, so
`HEAD /api/v2/workflows` and every sibling replied 500 — which is what health
checkers, uptime monitors, link checkers, and some CDNs send, all of them
reading the API as hard-down. RFC 9110 §9.3.2 makes HEAD identical to GET but
for the body, which is exactly what running the GET path produces. Fixed once in
`methodMatchesContract`, shared by all five route builders; every other mismatch
stays a hard error so a handler exported under the wrong verb still fails loudly.

**CORS advertised `GET,POST,OPTIONS,PUT,DELETE`** while the v2 spec has 17
`PATCH` operations, so a browser preflight for any of them was rejected. It also
advertised `PUT`, which two operations use — the shape of a hand-maintained list
outgrown by its surface. The list stays hand-written because middleware cannot
import the contract tree without pulling Zod into the edge bundle, but it is now
pinned by a test that sweeps the real contracts and fails on any method it omits.

**Six operations omitted a 403 their siblings documented** — three knowledge
reads and three file-upload operations. Traced from the code rather than the
spec: `requirePermission` throws `NoWorkspaceAccessError` for no access at all
(concealed as 404) but `InsufficientWorkspacePermissionsError` for access below
`minimumRole` (a real 403), and `PersonalApiKeysDisabledError` reaches every
operation a personal API key can call. So 403 was reachable on all six and the
omission was an accident of hand-assembled error lists, not a policy. They now
use the shared `RESOURCE_ERRORS` / `RESOURCE_CONFLICT_ERRORS` sets, and two
operations spelling those same sets by hand were normalized onto them.

All 128 documented operations now declare 403. The rules for HEAD, for the
403/404 split, and for using the shared error sets are recorded in
`.agents/skills/v2-api-conventions/SKILL.md`.

* test(proxy): update the CORS policy assertion to the served method list

`proxy.test.ts` pinned the previous hand-written method string, so widening
`resolveApiCorsPolicy` to advertise PATCH and HEAD left it asserting a list the
middleware no longer returns. The literal is kept rather than imported from
`proxy.ts` so the test still pins the exact wire value independently of the
implementation.

* refactor(v2): retire the error sets that could omit Forbidden

The three knowledge reads and three upload operations lost their `403` by
assembling `[...VALIDATED_ERRORS, ...]` by hand, and `VALIDATED_ERRORS` /
`STANDARD_ERRORS` were the only exported sets that omit `Forbidden`. Migrating
the last consumers to the shared `RESOURCE_*` sets left both unreferenced, so
deleting them turns the fix from a one-time cleanup into an invariant: there is
no longer a building block from which a workspace-scoped operation can assemble
an error list without `Forbidden`. Regenerating the specs produces no diff, so
the migration is output-neutral.

Also folds `method-match.test.ts` into `definition.test.ts` to match the
repo's `feature.ts` -> `feature.test.ts` convention, types `contractMethod`
as `HttpMethod` so a contract declaring `HEAD` is unrepresentable, and drops
the duplicated Next-aliasing rationale so `methodMatchesContract`'s TSDoc is
its single home.

* fix(cors): expose the API response headers a browser client needs

Without `Access-Control-Expose-Headers` a browser can read only the six
CORS-safelisted response headers, so the rate-limit budget, the `Retry-After`
a 429 or 503 asks the caller to observe, and the request/run correlation ids
were all on the wire but invisible to `fetch()`. Server-to-server callers were
unaffected, which is why it went unnoticed.

Exposed on the default `/api` policy only. The per-route `CORS_RULES` entries
are wildcard-origin public endpoints and opt in individually if they ever need
it, so this does not widen what an anonymous cross-origin caller can read from
them.
2026-08-12 11:09:33 -07:00
..

Integration documentation generator

generate-docs.ts compiles the per-service integration pages under apps/docs/content/docs/en/integrations/ from the block/tool/trigger registry in apps/sim. The ontology it encodes: everything is a block, and an integration is one block that has Actions and, optionally, a Trigger.

Golden rule: the generated .mdx files are derived artifacts, not the source of truth. Do not hand-edit them — your changes are overwritten on the next run. The only editable region is the MANUAL-CONTENT block (see below). To change what a page says, edit the TypeScript in apps/sim and regenerate.

Where an integration lives canonically

For a service like Gmail, three TS sources define it:

Source What it is What it feeds in the page
apps/sim/blocks/blocks/<service>.ts The block: type, name, category (tools for integrations), bgColor, config sub-blocks, tools.access (which actions it exposes), an optional triggers capability, outputs Header / BlockInfoCard, Usage Instructions, and which actions + trigger appear
apps/sim/tools/<service>/*.ts Each action's params + outputs Every ### <action>#### Input / #### Output under ## Actions
apps/sim/triggers/<provider>/ The trigger's config fields + outputs The ## Triggers section
apps/sim/components/icons.tsx The brand glyph The page icon

The block references actions by id in tools.access; the generator looks each one up in apps/sim/tools/.

What the generator does

Run with cd apps/sim && bun run generate-docs (or bun run scripts/generate-docs.ts from the repo root). One pass (generateAllBlockDocs):

  1. Copies icons apps/sim/components/icons.tsxapps/docs/components/icons.tsx and builds apps/docs/components/ui/icon-mapping.ts.
  2. Block pass — for each integration block (category: 'tools', plus the memory / knowledge / table exceptions), writes integrations/<service>.mdx: BlockInfoCard + Usage Instructions + ## Actions.
  3. Trigger pass (generateAllTriggerDocs) — reads apps/sim/triggers/<provider>/ and appends a ## Triggers section to that service's page, or writes a standalone page for trigger-only services.
  4. Writes integrations/meta.json and regenerates the landing page's integrations.json.

Hand-written pages it never touches

Core block pages (blocks/*), the native trigger pages (triggers/{start,schedule,webhook,rss,table}), the integrations overview (integrations/index.mdx), and the service-account pages are fully hand-written. The generator skips them via HANDWRITTEN_INTEGRATION_DOCS, HANDWRITTEN_TRIGGER_DOCS, and SKIP_TRIGGER_PROVIDERS. Add a page name to those sets if you hand-author a page the generator would otherwise produce.

Manual content (the one editable region)

Each generated page may carry hand-written prose inside marker comments. The generator preserves anything between the markers and overwrites everything else, so this survives every regeneration:

{/* MANUAL-CONTENT-START:intro */}
[AgentMail](https://agentmail.to/) is an API-first email platform…
{/* MANUAL-CONTENT-END */}

Supported section names: intro (after the BlockInfoCard — the most common), usage, configuration, outputs, notes. The merge is by marker name (extractManualContent + mergeWithManualContent), so a section is re-inserted at the matching spot in the freshly generated structure.

If you move the output folder, reseed manual content from the old location first — the generator only preserves markers it finds in the existing output file, so a fresh folder starts with none.

Practical: to change…

  • An action's params/outputs, a trigger, or to add a service → edit apps/sim/{blocks,tools,triggers} and re-run the generator.
  • A page's prose intro → edit its MANUAL-CONTENT:intro block directly; it survives regen.
  • The overview / service-account / core-block / native-trigger pages → hand-edit freely.

Gotchas

  • Never hand-edit apps/docs/components/icons.tsx — step 1 overwrites it from the sim app. Components that need an icon the sim app lacks should define it locally or use @sim/emcn/icons (see components/workflow-preview/block-icons.tsx).
  • The generator is the source of truth for integrations/ and its meta.json; manual edits there are transient.

CI

The generator runs in CI on pushes to the main branch and commits the regenerated docs back. Keep block/tool/trigger metadata accurate in apps/sim and the docs follow.