From dd37242866ad4db7eebc34244bc0740e26a22570 Mon Sep 17 00:00:00 2001 From: Tyler Hogarth <5163494+tylerhogarth@users.noreply.github.com> Date: Fri, 28 Aug 2026 11:44:12 +0200 Subject: [PATCH 1/5] feat(cli): ship the prisma-platform-core-concepts skill The prisma package now carries an agent skill covering the platform: the workspace/project/branch model, preview environments, the two deploy paths, services and versions, the Compute runtime, Prisma Postgres, object storage, environment variables, and the local development stack. Packaging follows the composer pattern: the tracked source lives in skills/, stage-skills.mjs stages it into the tarball at prepack, set-version.ts stamps metadata.library_version on every bump, and check-skill-packaging.mjs (wired into pr-quality) packs the package and verifies the stamp and byte equality against the source. The prisma package joins the skills-sync allowlist so consumers receive the skill via prisma skills sync. Co-Authored-By: Claude Fable 5 --- .github/workflows/pr-quality.yml | 28 ++ .gitignore | 2 + package.json | 1 + packages/cli/src/lib/skills/allowlist.ts | 3 + packages/prisma/package.json | 3 +- scripts/check-skill-packaging.mjs | 142 ++++++++ scripts/set-version.ts | 27 +- scripts/skill-frontmatter.ts | 109 ++++++ scripts/stage-skills.mjs | 84 +++++ skills/README.md | 66 ++++ skills/prisma-platform-core-concepts/SKILL.md | 327 ++++++++++++++++++ 11 files changed, 790 insertions(+), 2 deletions(-) create mode 100644 scripts/check-skill-packaging.mjs create mode 100644 scripts/skill-frontmatter.ts create mode 100644 scripts/stage-skills.mjs create mode 100644 skills/README.md create mode 100644 skills/prisma-platform-core-concepts/SKILL.md diff --git a/.github/workflows/pr-quality.yml b/.github/workflows/pr-quality.yml index 73f257ae..c2fd9efb 100644 --- a/.github/workflows/pr-quality.yml +++ b/.github/workflows/pr-quality.yml @@ -111,6 +111,34 @@ jobs: - name: Check error-reference completeness run: pnpm check:error-reference + skill-packaging: + name: Skill Packaging + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Set up pnpm + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + + - name: Set up Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: .node-version + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + # Packs the `prisma` package the same way publish does and reads the + # skill back out of the tarball: the stamp, the file set, and byte + # equality with the tracked skills/ tree. + - name: Check skill packaging + run: pnpm check:skill-packaging + test: name: Test runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index cd888bb2..81106b38 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ tmp/ # Build and test output artifacts/ dist/ +# Staged by scripts/stage-skills.mjs at prepack; source of truth is /skills +/packages/prisma/skills/ .publish/ coverage/ .vitest/ diff --git a/package.json b/package.json index c9ae9178..0aea0af4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "build:compute": "pnpm --filter @prisma/compute build", "check:grammar": "turbo run check:grammar", "check:error-reference": "node scripts/list-error-codes.mjs --verify docs/reference/error-reference.md", + "check:skill-packaging": "node scripts/check-skill-packaging.mjs", "format": "biome format . --write", "lint": "biome check . --error-on-warnings", "lint:fix": "biome check . --write", diff --git a/packages/cli/src/lib/skills/allowlist.ts b/packages/cli/src/lib/skills/allowlist.ts index 9bb836a0..f5089729 100644 --- a/packages/cli/src/lib/skills/allowlist.ts +++ b/packages/cli/src/lib/skills/allowlist.ts @@ -18,6 +18,9 @@ export const SKILL_SOURCE_PACKAGES: readonly string[] = [ "@prisma/orm-sqlite", "@prisma/orm-mongo", "@prisma/composer", + // The CLI's own package: ships prisma-platform-core-concepts, staged + // into the tarball by scripts/stage-skills.mjs at prepack. + "prisma", ]; /** The directory inside a source package's tarball that holds its skill diff --git a/packages/prisma/package.json b/packages/prisma/package.json index 2cdbd9e7..b13ec089 100644 --- a/packages/prisma/package.json +++ b/packages/prisma/package.json @@ -15,6 +15,7 @@ }, "files": [ "dist", + "skills", "README.md", "LICENSE" ], @@ -43,7 +44,7 @@ "license": "Apache-2.0", "scripts": { "build": "tsdown", - "prepack": "pnpm run build", + "prepack": "pnpm run build && node ../../scripts/stage-skills.mjs", "typecheck": "tsc --noEmit", "test": "vitest run" }, diff --git a/scripts/check-skill-packaging.mjs b/scripts/check-skill-packaging.mjs new file mode 100644 index 00000000..6b82fbea --- /dev/null +++ b/scripts/check-skill-packaging.mjs @@ -0,0 +1,142 @@ +#!/usr/bin/env node +// The `prisma-platform-core-concepts` skill must arrive in the `prisma` +// tarball, carrying the version of the tarball it arrived in. +// +// That claim is only worth as much as the artifact that proves it. The +// manifest can list `"skills"` in `files` while `prepack` failed to stage +// anything; the staged copy can be a leftover from an older version; the +// frontmatter stamp can be missing because someone hand-edited the skill. +// None of those show up in a unit test — they show up in what npm uploads. +// So this check packs the package the same way the publish workflow does and +// reads the skill back out of the tarball. +// +// It also compares the packed skill byte-for-byte against the repo-root +// `skills/` tree, which is the tracked source. If those two ever disagree, +// the tarball is serving different instructions than the repo records. +// +// Usage: node scripts/check-skill-packaging.mjs + +import { execFileSync } from "node:child_process"; +import { + existsSync, + mkdtempSync, + readdirSync, + readFileSync, + rmSync, + statSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { readSkillFrontmatter } from "./skill-frontmatter.ts"; + +const PACKAGE = "prisma"; + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const packageDir = join(repoRoot, "packages/prisma"); +const skillsRoot = join(repoRoot, "skills"); + +const failures = []; +function require_(condition, message) { + if (!condition) failures.push(message); +} + +/** Every file under `dir`, as paths relative to it, sorted. */ +function filesUnder(dir) { + return readdirSync(dir, { recursive: true, encoding: "utf-8" }) + .filter((entry) => statSync(join(dir, entry)).isFile()) + .sort(); +} + +/** The skills the repo-root tree says belong to this package. */ +function ownedSkillNames() { + return readdirSync(skillsRoot, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .filter((entry) => existsSync(join(skillsRoot, entry.name, "SKILL.md"))) + .filter( + (entry) => + readSkillFrontmatter( + readFileSync(join(skillsRoot, entry.name, "SKILL.md"), "utf-8"), + ).library === PACKAGE, + ) + .map((entry) => entry.name); +} + +const expected = ownedSkillNames(); +require_( + expected.length > 0, + `no skill under skills/ declares \`metadata.library: "${PACKAGE}"\` — the frontmatter is what routes a skill into a tarball, so without it nothing ships.`, +); + +// pnpm pack, not npm pack: it runs the same `prepack` and rewrites the same +// specifiers a real publish does. +const work = mkdtempSync(join(tmpdir(), "skill-packaging-")); +try { + execFileSync("pnpm", ["pack", "--pack-destination", work], { + cwd: packageDir, + stdio: ["ignore", "ignore", "inherit"], + }); + const tarball = readdirSync(work).find((f) => f.endsWith(".tgz")); + if (tarball === undefined) + throw new Error(`pnpm pack produced no tarball for ${PACKAGE}`); + execFileSync("tar", ["xzf", tarball], { cwd: work }); + const packedRoot = join(work, "package"); + + const packedVersion = JSON.parse( + readFileSync(join(packedRoot, "package.json"), "utf-8"), + ).version; + + for (const name of expected) { + const packedSkillDir = join(packedRoot, "skills", name); + const packedSkill = join(packedSkillDir, "SKILL.md"); + if (!existsSync(packedSkill)) { + require_( + false, + `the packed tarball has no skills/${name}/SKILL.md — check that "skills" is in the package's \`files\` and that the \`prepack\` script runs scripts/stage-skills.mjs.`, + ); + continue; + } + + const { library, libraryVersion } = readSkillFrontmatter( + readFileSync(packedSkill, "utf-8"), + ); + require_( + library === PACKAGE, + `the packed skills/${name}/SKILL.md declares metadata.library: "${library}", not "${PACKAGE}".`, + ); + require_( + libraryVersion === packedVersion, + `the packed skills/${name}/SKILL.md is stamped metadata.library_version: "${libraryVersion}" but ships in ${PACKAGE}@${packedVersion} — the stamp is what tells a reader which CLI surface the skill describes. Run \`node scripts/set-version.ts ${packedVersion}\`.`, + ); + + // Byte equality with the tracked source: the tarball must serve exactly + // the instructions the repo records. + const sourceDir = join(skillsRoot, name); + const sourceFiles = filesUnder(sourceDir); + const packedFiles = filesUnder(packedSkillDir); + require_( + sourceFiles.join("\n") === packedFiles.join("\n"), + `the packed skills/${name}/ does not have the same files as ${relative(repoRoot, sourceDir)}/ (packed: ${packedFiles.join(", ")}; source: ${sourceFiles.join(", ")}).`, + ); + for (const file of sourceFiles.filter((f) => packedFiles.includes(f))) { + require_( + readFileSync(join(sourceDir, file), "utf-8") === + readFileSync(join(packedSkillDir, file), "utf-8"), + `the packed skills/${name}/${file} differs from the tracked ${relative(repoRoot, join(sourceDir, file))}.`, + ); + } + } +} finally { + rmSync(work, { recursive: true, force: true }); +} + +if (failures.length > 0) { + process.stderr.write( + `\nFAIL — skill packaging check:\n${failures.map((f) => ` - ${f}\n`).join("")}`, + ); + process.exit(1); +} +process.stderr.write( + `\nOK — the ${PACKAGE} tarball carries ${expected.join(", ")}, stamped with its version and identical to skills/.\n`, +); diff --git a/scripts/set-version.ts b/scripts/set-version.ts index 72cd0932..3ac0ab0c 100644 --- a/scripts/set-version.ts +++ b/scripts/set-version.ts @@ -9,6 +9,7 @@ import { participatesInLockstep, rewriteWorkspaceDeps, } from "./set-version-utils.ts"; +import { stampSkillVersion } from "./skill-frontmatter.ts"; // Operator rulings: `@prisma/compute` versions independently pending // extraction to another repo (2026-08-10), and `@prisma/cli-engine` @@ -114,4 +115,28 @@ for (const manifestPath of trackedManifests) { updatedCount++; } -console.log(`\nDone! Updated ${updatedCount} packages.`); +// Skills ship inside the `prisma` tarball and their frontmatter names +// the version of the CLI they describe (`metadata.library_version`). +// The `prisma` package versions in lockstep, so every skill is stamped +// with the same root version; check-skill-packaging.mjs verifies the +// stamp against the packed tarball. +const skillsDir = path.join(rootDir, "skills"); +const skillDirEntries = await fs + .readdir(skillsDir, { withFileTypes: true }) + .catch(() => []); +let stampedSkills = 0; +for (const entry of skillDirEntries) { + if (!entry.isDirectory()) continue; + const skillPath = path.join(skillsDir, entry.name, "SKILL.md"); + // biome-ignore lint/performance/noAwaitInLoops: each skill prints its "Stamped" line as it is rewritten, so a run that fails part way through leaves an accurate record of which files already changed. + const source = await fs.readFile(skillPath, "utf-8").catch(() => undefined); + if (source === undefined) continue; + + await fs.writeFile(skillPath, stampSkillVersion(source, version)); + console.log(`Stamped ${path.relative(rootDir, skillPath)} with ${version}`); + stampedSkills++; +} + +console.log( + `\nDone! Updated ${updatedCount} packages and ${stampedSkills} skills.`, +); diff --git a/scripts/skill-frontmatter.ts b/scripts/skill-frontmatter.ts new file mode 100644 index 00000000..e960a68e --- /dev/null +++ b/scripts/skill-frontmatter.ts @@ -0,0 +1,109 @@ +// Pure helpers for the `metadata.library` / `metadata.library_version` +// frontmatter keys that tie a skill to the npm package it describes. +// +// The skill ships inside the `prisma` tarball, so the version a reader +// sees must be the version they installed. `set-version.ts` stamps +// `library_version` from the same root version it writes into every +// package.json, and `check-skill-packaging.mjs` re-reads it out of the +// packed tarball. Both go through here so there is one definition of +// what the frontmatter looks like. +// +// Under `metadata:` rather than at the top level because the Agent +// Skills spec (agentskills.io) defines the top-level key set and +// reserves `metadata` — a string→string map — for exactly this kind of +// publisher extension. A top-level `library:` would be an undefined key +// that a strict runtime is entitled to reject. + +const FRONTMATTER_BLOCK = /^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/; + +/** The `metadata:` mapping: the key line plus the indented block under it. */ +const METADATA_SECTION = /^metadata:[ \t]*\r?\n((?:[ \t]+\S.*(?:\r?\n|$))*)/m; + +const LEADING_INDENT = /^[ \t]+/; + +function keyPattern(key: string): RegExp { + return new RegExp(`^[ \\t]+${key}:[ \\t]*(.*)$`, "m"); +} + +function unquote(value: string): string { + const trimmed = value.trim(); + const quote = trimmed[0]; + if ( + (quote === '"' || quote === "'") && + trimmed.endsWith(quote) && + trimmed.length >= 2 + ) { + return trimmed.slice(1, -1); + } + return trimmed; +} + +export interface SkillFrontmatter { + /** The npm package the skill ships inside, e.g. `prisma`. */ + library?: string; + /** The version of that package, stamped at release time. */ + libraryVersion?: string; +} + +/** The indented body of the frontmatter's `metadata:` map, if it has one. */ +function metadataBlock(source: string): string | undefined { + const frontmatter = FRONTMATTER_BLOCK.exec(source)?.[1]; + if (frontmatter === undefined) return undefined; + return METADATA_SECTION.exec(frontmatter)?.[1]; +} + +/** Read the version-stamp keys out of a `SKILL.md`. Absent keys stay undefined. */ +export function readSkillFrontmatter(source: string): SkillFrontmatter { + const metadata = metadataBlock(source); + if (metadata === undefined) return {}; + + const library = keyPattern("library").exec(metadata)?.[1]; + const libraryVersion = keyPattern("library_version").exec(metadata)?.[1]; + return { + library: library === undefined ? undefined : unquote(library), + libraryVersion: + libraryVersion === undefined ? undefined : unquote(libraryVersion), + }; +} + +/** + * Rewrite `metadata.library_version` in a `SKILL.md` to `version`. Idempotent. + * + * Both keys must already be present. Adding them is an authoring decision — + * a skill that ships in a tarball declares which package it ships in — and + * silently inserting them here would let a skill with no `library` key be + * stamped with a version that means nothing. + */ +export function stampSkillVersion(source: string, version: string): string { + if (FRONTMATTER_BLOCK.exec(source)?.[1] === undefined) { + throw new Error("SKILL.md has no YAML frontmatter block."); + } + const metadata = metadataBlock(source); + if (metadata === undefined) { + throw new Error( + "SKILL.md frontmatter has no `metadata` map — the version stamp lives there, because the Agent Skills spec reserves `metadata` for publisher keys and defines the top-level ones.", + ); + } + const { library, libraryVersion } = readSkillFrontmatter(source); + if (library === undefined) { + throw new Error( + "SKILL.md frontmatter has no `metadata.library` key — a skill that ships inside a tarball must name the package it ships in.", + ); + } + if (libraryVersion === undefined) { + throw new Error( + "SKILL.md frontmatter has no `metadata.library_version` key — add it (any string) so the release pipeline can stamp it.", + ); + } + + // Function replacements throughout: the skill body and its long + // `description` are prose, and a literal `$&` or `$1` in them would be + // expanded as a capture reference by the string form. The version is + // quoted because `metadata` is a string→string map. + const stamped = metadata.replace( + keyPattern("library_version"), + (line) => + `${LEADING_INDENT.exec(line)?.[0] ?? " "}library_version: "${version}"`, + ); + return source.replace(metadata, () => stamped); +} diff --git a/scripts/stage-skills.mjs b/scripts/stage-skills.mjs new file mode 100644 index 00000000..6137f57f --- /dev/null +++ b/scripts/stage-skills.mjs @@ -0,0 +1,84 @@ +#!/usr/bin/env node +// Stage the agent skills that belong to a package into that package's +// `skills/` directory, so they travel inside its tarball. +// +// Why in the tarball: an agent skill is only useful if it describes the +// version of the package the reader actually installed. Shipping it beside +// the code makes that true by construction — the skill and the CLI surface it +// documents are the same artifact, updated by the same `pnpm add`. This is +// also what lets a consumer's `prisma skills sync` resolve the skill by +// package name instead of scanning node_modules. +// +// Which skills belong to which package is not configured here: each +// `SKILL.md` already declares its package in `metadata.library` frontmatter +// (the same key `set-version.ts` stamps the version onto), so this script +// reads that and copies the matching trees. One source of truth, no table to +// keep in sync. +// +// Run from the package directory, which is where npm/pnpm run `prepack`: +// +// node ../../scripts/stage-skills.mjs +// +// `prepack` rather than the build: `pnpm pack` and `pnpm publish` both run +// it, so the staged copy is produced by the very act of making a tarball and +// can never be a stale build artifact restored from a turbo cache. + +import { + cpSync, + existsSync, + mkdirSync, + readdirSync, + readFileSync, + rmSync, +} from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { readSkillFrontmatter } from "./skill-frontmatter.ts"; + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const packageDir = process.cwd(); + +const packageName = JSON.parse( + readFileSync(join(packageDir, "package.json"), "utf-8"), +).name; +if (typeof packageName !== "string") { + throw new Error(`No package name in ${join(packageDir, "package.json")}.`); +} + +const skillsRoot = join(repoRoot, "skills"); +const owned = readdirSync(skillsRoot, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .filter((entry) => existsSync(join(skillsRoot, entry.name, "SKILL.md"))) + .filter((entry) => { + const source = readFileSync( + join(skillsRoot, entry.name, "SKILL.md"), + "utf-8", + ); + return readSkillFrontmatter(source).library === packageName; + }) + .map((entry) => entry.name); + +const destinationRoot = join(packageDir, "skills"); + +// Rebuilt from scratch every run: a skill that stops belonging to this +// package, or is renamed, must not linger in the next tarball. +rmSync(destinationRoot, { recursive: true, force: true }); + +if (owned.length === 0) { + process.stderr.write( + `No skills declare metadata.library: "${packageName}" — nothing staged.\n`, + ); + process.exit(0); +} + +mkdirSync(destinationRoot, { recursive: true }); +for (const name of owned) { + cpSync(join(skillsRoot, name), join(destinationRoot, name), { + recursive: true, + }); +} + +process.stderr.write( + `Staged ${owned.join(", ")} into ${packageName}'s tarball.\n`, +); diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 00000000..aeeb0d3d --- /dev/null +++ b/skills/README.md @@ -0,0 +1,66 @@ +# Prisma Platform skills + +Agent skills for the [Prisma CLI](https://github.com/prisma/prisma-cli): one +`SKILL.md` that teaches an LLM agent the Prisma Platform's resource model +without re-deriving it from documentation each time. + +## What's in the box + +One skill, `prisma-platform-core-concepts`, covering the platform: the +workspace/project/branch model, preview environments, the two deploy paths +(GitHub integration and `prisma deploy`), services and versions, the Compute +runtime, Prisma Postgres, object storage, environment variables, the local +development stack, and the failure modes. The ORM and Composer have their own +core-concepts skills that ship inside their own packages; this one owns +everything platform-side. + +## Install + +The skill ships inside the `prisma` tarball, so installing the package is +what brings it in. `prisma skills sync` copies it out of `node_modules` into +the skill directories the agent runtimes read (`.claude/skills/`, +`.cursor/skills/`, `.agents/skills/`, `.devin/skills/`): + +```bash +pnpm add -D prisma +pnpm prisma skills sync +``` + +`prisma init` wires a `postinstall` hook so an upgrade brings the matching +skill with it. The version you read is then always the version you +installed: the skill's frontmatter carries `metadata.library: "prisma"` and a +`metadata.library_version` stamped by the release that built the tarball +([`scripts/set-version.ts`](../scripts/set-version.ts); +[`scripts/check-skill-packaging.mjs`](../scripts/check-skill-packaging.mjs) +proves it against the packed artifact). + +## Authoring rules + +For anyone editing the skill: + +1. **Verify every claim while drafting, not in a final pass.** Every command + and flag must exist in `packages/cli/src/` (the `mountedCommands` map in + `cli.ts` is the source of truth for what mounts where). If a search finds + nothing, the surface doesn't ship: name it under *What the platform + doesn't do yet* instead of extrapolating. +2. **The skill must be self-contained.** It gets installed into other repos, + so no link may resolve outside `skills/prisma-platform-core-concepts/`. + Repo docs may be named in prose, never linked relatively. +3. **Teach concepts, not procedures.** Name the moving parts and the command + that reveals each piece of state; reserve numbered steps for + one-safe-path operations. +4. **Leave the `metadata` stamp alone.** `metadata.library` names the npm + package the skill ships inside, and `metadata.library_version` is + rewritten by [`scripts/set-version.ts`](../scripts/set-version.ts) on + every release. Hand-editing the version, or dropping either key, breaks + the release script and + [`scripts/check-skill-packaging.mjs`](../scripts/check-skill-packaging.mjs). + They live under `metadata` because the Agent Skills spec + (agentskills.io) defines the top-level keys and reserves that map + (string → string) for publisher extensions. A new skill needs both keys, + with any placeholder version. +5. **Folder name and frontmatter `name` must match.** The runtimes key on + the frontmatter, humans on the folder. + +Maintainer-facing skills (release process and similar) live in +[`../skills-contrib/`](../skills-contrib/), not here. diff --git a/skills/prisma-platform-core-concepts/SKILL.md b/skills/prisma-platform-core-concepts/SKILL.md new file mode 100644 index 00000000..5c6b8ec8 --- /dev/null +++ b/skills/prisma-platform-core-concepts/SKILL.md @@ -0,0 +1,327 @@ +--- +name: prisma-platform-core-concepts +metadata: + library: "prisma" + library_version: "8.0.0-rc.11" + version: 2026.8.27 +description: >- + Use when hosting, deploying, or operating an app on the Prisma Platform: + projects, branches, preview environments, services and their versions, + Prisma Postgres databases, object-store buckets, environment variables, + custom domains, logs, or the GitHub integration. Triggers on "Prisma + Platform", "Prisma Compute", "Prisma Postgres", "Prisma Storage", + "preview environment", `prisma deploy`, `prisma dev`, `prisma auth`, + `prisma project`, `prisma branch`, `prisma service`, `prisma postgres`, + `prisma bucket`, `prisma git`, promote, rollback. +--- + +# Prisma Platform core concepts + +> **Isolated infrastructure for every branch.** + +The Prisma Platform moves fast, and your training data about it is very +likely outdated. This skill ships inside the installed `prisma` package, so +it describes the exact version this project has: treat it as the source of +truth over anything you remember about Prisma hosting. If +`metadata.library_version` in this file's frontmatter does not match the +project's installed `prisma` package, run `prisma skills sync` and re-read. +Fuller documentation for everything here lives at prisma.io/docs; consult it +when a concept needs more depth than this file carries. + +This file shares the platform's structures, hierarchies, relationships, and +workflows. It is not a CLI reference: commands appear only where a workflow +needs them. To learn a command surface, run `prisma --help`, or +`prisma --help` for any group named below; most commands accept +`--json` for machine-readable output. The data contract, migrations, and +queries belong to the `prisma-orm-core-concepts` skill; declaring services +and modules in code belongs to `prisma-composer-core-concepts`. + +## The stack + +One CLI, `prisma`, fronts a set of products designed to be used together: + +| Product | What it is | Concepts live in | +| --- | --- | --- | +| Prisma ORM (Prisma 8) | Data contract, typed queries, migrations | `prisma-orm-core-concepts` | +| Prisma Composer | The app declaration: services, databases, buckets, wiring | `prisma-composer-core-concepts` | +| Prisma Compute | Hosting: runs your services next to your data | this skill | +| Prisma Postgres | Managed PostgreSQL | this skill | +| Prisma Storage | S3-compatible object storage | this skill | + +**Every deployed app is a Composer app.** Your server code plus a declaration +(`module.ts`) naming its services, databases, and buckets. The declaration is +the source of truth: deploying converges the platform to it, re-deploying +applies only the difference, and removing a resource from the module removes +it from the platform on the next deploy. There is no separate provisioning +step and no connection strings to wire by hand; Composer injects them. + +## The resource model + +1. **Workspace**: the account boundary. Members, billing, and projects live + here; the `auth` group manages which one you act in. +2. **Project**: one product or codebase. Its region is chosen at creation and + is immutable afterwards; every resource in the project inherits it. +3. **Branch**: the isolation boundary, named after a git branch. A branch is + an environment: it owns its own services, its own databases, its own + buckets, and its own environment-variable overrides. +4. Inside a branch: **services** (HTTP apps, deployed as versions), + **databases**, and **buckets**. + +The first branch of a project is the **production** branch. Every branch +after it is a **preview** branch. Production and previews differ in exactly +two ways: which environment-variable class they resolve (see below), and +lifecycle (previews can be reclaimed and torn down; production cannot). + +A CLI invocation resolves its project from a directory binding, created when +a project is created from or linked to the directory and stored in the +gitignored `.prisma/local.json`. Resource commands take flags to target +another project or branch explicitly. + +## Branches are preview environments + +There is no separate "preview environment" object to create or configure. The +branch is the environment, and branches come into being by deploying to them: + +1. **Push a git branch** (with the GitHub integration connected): the + platform creates the branch environment on the fly, builds, and deploys. +2. **Deploy a stage from the CLI**: `prisma deploy module.ts --stage pr-42` + creates a branch named `pr-42` and deploys the identical app graph into + it. A stage name must be a valid git ref name; an invalid name is a hard + error. + +A preview branch that needs a database gets a fresh, empty one, wired in as a +branch-scoped `DATABASE_URL`. Preview work can never touch production data +unless you explicitly point it there. Schema comes from your committed +migrations; data does not follow from production. + +Preview lifecycle rules: + +1. Deleting the upstream git branch tears the whole environment down: + services, versions, and databases, including their data. +2. An idle, unpinned preview branch can be reclaimed automatically. Pinning a + preview exempts it from idle reclamation, but not from teardown when its + git branch is deleted. +3. The production branch is never reclaimed or torn down this way. + +## Two ways to deploy + +**GitHub (recommended).** Install the Prisma GitHub app and connect the +repository: `prisma git connect`, or import the repository in the Console. +From then on, every push builds and deploys on the platform. A push to the +default branch deploys production; a push to any other branch creates or +updates that branch's preview environment. No workflow file is required, and +previews come free with every branch. Opening a pull request does not itself +deploy anything; previews track branch pushes. + +**CLI.** `prisma deploy module.ts` deploys production directly; add +`--stage ` for a preview. Authenticate once with `prisma auth login` +(interactive), or set `PRISMA_SERVICE_TOKEN` (plus `PRISMA_WORKSPACE_ID` +when the token can see more than one workspace) for CI and other headless +runs. The first deploy creates the project, named after your module; the +name must be unique in the workspace, and `--name` deploys under a different +one. + +Both paths converge the same declaration, so they are mutually idempotent: +a repo can be connected to GitHub and still be deployed from the CLI. + +## Services and versions + +A **service** is an HTTP app inside a branch, reachable at a stable URL. +Every deploy of a service creates a new immutable **version**; exactly one +version is live behind the stable URL at a time. Versions can be inspected, +started, stopped, and deleted individually, and a version's logs can be read +or followed; the `service` group covers all of it. + +Two movements between versions matter: + +1. **Promote** takes a version to production by rebuilding it with + production-class environment variables. A version built on a preview + branch never carries preview configuration into production. +2. **Rollback** points production back at a previous version. A stopped + target is started and health-checked before traffic switches, so rollback + is zero-downtime. + +**Custom domains** attach to a service's production branch only. The +workflow: add the domain, create the CNAME record the platform reports, and +wait for DNS verification and certificate provisioning; a verification that +failed on missing DNS is retried after the record exists. + +## The Compute runtime + +Services run on Bun, next to their Prisma Postgres database. There is no +container image to author and no platform emulator to install locally. + +An idle service **sleeps**: the platform snapshots its memory after a short +period of inactivity and resumes it from the snapshot on the next request. +Request handling is unaffected, but background work outside the request +lifecycle (`setTimeout`, `setInterval`, floating promises) can be +interrupted mid-flight. The `@prisma/compute` package provides the two +keep-awake primitives: + +1. `waitUntil(promise, { signal })` keeps the instance awake until the + promise settles. +2. `using guard = new KeepAwakeGuard({ signal })` holds the instance awake + for a scope; call `.release()` in a `finally` block where `using` is + unavailable. + +Pass `AbortSignal.timeout(ms)` as a cost bound: the signal releases the +guard, it does not cancel your work. Do not build schedulers on `setInterval` +inside a service; sleeping makes them silently unreliable. + +## Prisma Postgres + +To your application, a Prisma Postgres database is a regular PostgreSQL +database behind a connection string: any client works, including Prisma ORM, +psql, Kysely, and Drizzle. On top of plain PostgreSQL each database has +built-in connection pooling (no PgBouncer to run), optional query caching, +and automated backups. + +Databases usually come from deploying a module that declares one. They can +also be created directly, in which case the connection URL prints exactly +once. Additional connection strings can be minted and rotated per database; +every secret is shown exactly once at creation, and no later read reveals +it. Backups are automated, listable, and restorable, and per-database usage +metrics are queryable; the `postgres` group covers all of it. + +## Object storage + +Buckets are S3-compatible object stores that live inside the project, +optionally associated with a branch (the project's default branch when +omitted). A bucket cannot be renamed. + +Access keys are bucket-scoped credentials with a role of `read` or +`read_write`. Creating one prints the access key id, secret, endpoint, and +provider bucket name exactly once; a lost secret is revoked and re-created, +never recovered. The `bucket` group manages both buckets and their keys. + +On Compute, Bun's built-in S3 client picks up `S3_ENDPOINT`, `S3_BUCKET`, +`S3_ACCESS_KEY_ID`, and `S3_SECRET_ACCESS_KEY` from the environment, so a +bucket declared in the module needs no client configuration. Locally, a +Composer-declared bucket gets a local stand-in with the same S3 surface; +code written against it runs unchanged on the platform. + +## Environment variables + +Configuration has two independent axes: + +| Axis | Values | Meaning | +| --- | --- | --- | +| Class | `production`, `preview` | Which branches resolve it: production-class values reach the production branch, preview-class values reach every preview | +| Scope | project template, branch override | A template applies to all branches of its class; an override pins a value to one named preview branch | + +Rules that bite: + +1. Branch overrides exist only for the preview class. Production + configuration is always the project template; there is no way to give one + production deploy a special value. +2. Values are write-only. Listing shows names and scopes, never values, and + no API returns them. To change a value you replace it. +3. Variables resolve at deploy time from the branch's class. You never pass + environment variables as part of a deploy. +4. Templates, per-branch overrides, and wholesale dotenv import are all + managed through the `project env` group. +5. `DATABASE_URL` is managed for you when the module declares a database: + previews get their own fresh database's URL, production gets the + production database's. Only set it yourself for databases the platform + does not manage. + +## Local development + +The local stack is real, fast, and needs no cloud credentials. Prefer it +over deploying whenever you want to show the user a change or verify your +own work: + +1. Build the app exactly as you would for deploy, then run + `prisma dev module.ts`. It provisions a local Prisma Postgres database, + applies the schema, creates local stand-ins for declared buckets, starts + the services, and prints a local URL. It watches built output and + restarts a service when its build changes. +2. Exercise the running app directly (for example with `curl` against the + printed URL) instead of reasoning about what the code would do. +3. Stopping `dev` leaves local databases, buckets, and their data in place; + the next run is a warm start. `--fresh` wipes this app's local instances + and data first. +4. Deploying is the same pipeline pointed at the platform: switching from + local to hosted is a configuration change, not a code change. Reach for + `prisma deploy --stage ` only when you need a shareable URL or + platform behaviour (sleeping, real DNS, production-like config). +5. For pure frontend iteration the framework's own dev server also works; + the Composer local stack is what mirrors the deployed wiring. + +Windows is not supported for the local stack yet. + +## The CLI in one map + +Command groups and what each manages; run `prisma --help` for the +commands, before assuming a capability exists or is missing: + +| Group | Manages | +| --- | --- | +| `auth`, `auth workspace` | Login, identity, workspace sessions | +| `project`, `project env` | Projects, directory binding, environment variables | +| `branch` | Listing platform branches | +| `git` | The GitHub repository connection | +| `service`, `service version`, `service domain` | Services, their versions, custom domains | +| `postgres`, `postgres backup`, `postgres connection` | Databases, backups, connection strings | +| `bucket`, `bucket key` | Object storage and its access keys | +| `deploy`, `dev` | Deploying the Composer app; running it locally | +| `contract`, `db`, `migration`, `orm` | The ORM surface (see `prisma-orm-core-concepts`) | +| `init`, `skills` | Repo setup; keeping these agent skills current | +| `feedback` | Sending feedback to the Prisma team | + +Destructive commands (deleting or transferring a project, deleting a +database, restoring a backup, and similar) require re-typing the exact +resource id as confirmation; script them accordingly. + +## Failure modes quick reference + +1. **A secret you need was shown once and is gone.** By design: database + URLs, connection strings, and bucket keys print exactly once at creation. + Rotate or re-create; do not hunt for a read path, none exists. +2. **Deploy refuses the module name.** Module names are workspace-unique. + Deploy under `--name `, or delete the conflicting project. +3. **A preview environment vanished.** Its git branch was deleted, or it + sat idle unpinned and was reclaimed. Push or deploy the branch again to + re-create it; the database starts empty again. +4. **Background work never ran in production.** The service slept. + Wrap the work in `waitUntil` or a `KeepAwakeGuard`, or move it out of the + service. +5. **A custom domain stays unverified.** The CNAME to the reported target + must exist first. Fix DNS, then retry the verification. +6. **Production shows a stale value after an environment-variable change.** + Variables are resolved at deploy time. Re-deploy (or promote) so the new + value is picked up. +7. **A stage name is rejected.** Stage names must be valid git ref names; + rename it. +8. **A resource disappeared from the platform after a deploy.** It was + removed from `module.ts`; the module is the source of truth and deploy + converges to it. Restore the declaration and re-deploy. + +## What the platform doesn't do yet + +Name the gap instead of inventing an API: + +1. **No branch create or delete from the CLI.** The `branch` group only + lists. Branches are created by pushing or by deploying a stage, and + removed by deleting the upstream git branch; there is no CLI teardown + verb for a stage yet. +2. **No build retry.** A failed build is finished; the retry is the next + push or the next deploy. +3. **No reading environment-variable values back**, anywhere. This is a + security stance, not a missing feature, but agents look for it often + enough to name here. +4. **No bucket rename** and no bucket-to-branch re-association after + creation; re-create instead. + +For anything else missing, run the nearest group with `--help` before +concluding it does not exist, and route requests with `prisma feedback` +rather than guessing. + +## Related skills + +Everything inside the app is owned by the siblings: the data contract, +queries, and migrations by `prisma-orm-core-concepts` (and +`prisma-orm-migrations`), and the declaration of services, modules, wiring, +and testing seams by `prisma-composer-core-concepts`. The skills install +together via `prisma skills sync`; when a task crosses from platform +resources into app code, switch skills instead of guessing. From c04e1118536bc1c31c53ddf810ce2cdc4c80c871 Mon Sep 17 00:00:00 2001 From: Tyler Hogarth <5163494+tylerhogarth@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:39:59 +0200 Subject: [PATCH 2/5] Remove the CLI group map from the platform skill Review ruling on #245: the CLI is self-documenting and a command table risks giving the agent context that conflicts with the shipped surface. The skill routes to --help instead. Co-Authored-By: Claude Fable 5 --- skills/prisma-platform-core-concepts/SKILL.md | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/skills/prisma-platform-core-concepts/SKILL.md b/skills/prisma-platform-core-concepts/SKILL.md index 5c6b8ec8..1d210ca0 100644 --- a/skills/prisma-platform-core-concepts/SKILL.md +++ b/skills/prisma-platform-core-concepts/SKILL.md @@ -3,7 +3,7 @@ name: prisma-platform-core-concepts metadata: library: "prisma" library_version: "8.0.0-rc.11" - version: 2026.8.27 + version: 2026.8.28 description: >- Use when hosting, deploying, or operating an app on the Prisma Platform: projects, branches, preview environments, services and their versions, @@ -250,29 +250,6 @@ own work: Windows is not supported for the local stack yet. -## The CLI in one map - -Command groups and what each manages; run `prisma --help` for the -commands, before assuming a capability exists or is missing: - -| Group | Manages | -| --- | --- | -| `auth`, `auth workspace` | Login, identity, workspace sessions | -| `project`, `project env` | Projects, directory binding, environment variables | -| `branch` | Listing platform branches | -| `git` | The GitHub repository connection | -| `service`, `service version`, `service domain` | Services, their versions, custom domains | -| `postgres`, `postgres backup`, `postgres connection` | Databases, backups, connection strings | -| `bucket`, `bucket key` | Object storage and its access keys | -| `deploy`, `dev` | Deploying the Composer app; running it locally | -| `contract`, `db`, `migration`, `orm` | The ORM surface (see `prisma-orm-core-concepts`) | -| `init`, `skills` | Repo setup; keeping these agent skills current | -| `feedback` | Sending feedback to the Prisma team | - -Destructive commands (deleting or transferring a project, deleting a -database, restoring a backup, and similar) require re-typing the exact -resource id as confirmation; script them accordingly. - ## Failure modes quick reference 1. **A secret you need was shown once and is gone.** By design: database From 994b4e28fb716b9a88325b14df97988c5ef23b50 Mon Sep 17 00:00:00 2001 From: Tyler Hogarth <5163494+tylerhogarth@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:56:44 +0200 Subject: [PATCH 3/5] Platform skill: corrections from agent benchmark feedback Four judged benchmark runs against the rc.12 build surfaced gaps that cost agents debug cycles. This applies the ones that belong to this skill: 1. Stamp library_version 8.0.0-rc.12 (the branch predated the bump; the stale stamp made the sync nag unclearable). 2. New Project setup section: the install line for the composer pair, which skill arrives in which package, the composed prisma.config.ts example, and the mandatory prisma-composer.config.ts pointer. 3. Compute runtime: no global Temporal, so ORM DateTime columns need the polyfill or *String column types. 4. Failure modes: COMPOSE.GRAPH_INVALID from non-alphanumeric resource ids, and RUNTIME.TEMPORAL_UNAVAILABLE on first timestamp read. Co-Authored-By: Claude Fable 5 --- skills/prisma-platform-core-concepts/SKILL.md | 56 ++++++++++++++++++- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/skills/prisma-platform-core-concepts/SKILL.md b/skills/prisma-platform-core-concepts/SKILL.md index 1d210ca0..aa0efeb6 100644 --- a/skills/prisma-platform-core-concepts/SKILL.md +++ b/skills/prisma-platform-core-concepts/SKILL.md @@ -2,8 +2,8 @@ name: prisma-platform-core-concepts metadata: library: "prisma" - library_version: "8.0.0-rc.11" - version: 2026.8.28 + library_version: "8.0.0-rc.12" + version: 2026.9.1 description: >- Use when hosting, deploying, or operating an app on the Prisma Platform: projects, branches, preview environments, services and their versions, @@ -12,7 +12,7 @@ description: >- Platform", "Prisma Compute", "Prisma Postgres", "Prisma Storage", "preview environment", `prisma deploy`, `prisma dev`, `prisma auth`, `prisma project`, `prisma branch`, `prisma service`, `prisma postgres`, - `prisma bucket`, `prisma git`, promote, rollback. + `prisma bucket`, `prisma git`, `prisma.config.ts`, promote, rollback. --- # Prisma Platform core concepts @@ -55,6 +55,43 @@ applies only the difference, and removing a resource from the module removes it from the platform on the next deploy. There is no separate provisioning step and no connection strings to wire by hand; Composer injects them. +## Project setup + +The CLI carries every command named here, but an app's own code needs its +dependencies installed. Start from: + +```sh +npm install prisma @prisma/composer @prisma/composer-prisma-cloud +``` + +`@prisma/composer` is what the module declaration imports; +`@prisma/composer-prisma-cloud` is the Prisma Cloud target and brings +`@prisma/orm-postgres` with it. The sibling skills travel inside these +packages (`prisma-composer-core-concepts` in `@prisma/composer`, +`prisma-orm-core-concepts` in `@prisma/orm-postgres`), so they appear after +this install plus `prisma skills sync`, not before. + +Two config files with different owners: + +1. `prisma.config.ts` configures the CLI. Each top-level key is a section + owned by one product, and this CLI recognises exactly three: `orm`, + `composer`, and `skills`. The composed shape: + + ```ts + import { definePrismaConfig } from "prisma/config"; + import { defineConfig } from "@prisma/orm-postgres/config"; + + export default definePrismaConfig({ + orm: defineConfig({ contract: "./src/prisma/contract.prisma" }), + skills: { agents: ["claude"] }, + }); + ``` + +2. `prisma-composer.config.ts` configures Composer itself and is a separate, + mandatory file for `dev` and `deploy`: without it `dev` fails with + `CONFIG.FILE_MISSING`. Its contents belong to + `prisma-composer-core-concepts`. + ## The resource model 1. **Workspace**: the account boundary. Members, billing, and projects live @@ -151,6 +188,11 @@ failed on missing DNS is retried after the record exists. Services run on Bun, next to their Prisma Postgres database. There is no container image to author and no platform emulator to install locally. +The runtime has no global `Temporal` (stock Node 24 lacks one too). Code +that reads an ORM `DateTime` column compiles and deploys, then throws on +the first read. Either `import 'temporal-polyfill/global'` at the service +entry, or use the `*String` column types such as `TimestamptzString`. + An idle service **sleeps**: the platform snapshots its memory after a short period of inactivity and resumes it from the snapshot on the next request. Request handling is unaffected, but background work outside the request @@ -273,6 +315,14 @@ Windows is not supported for the local stack yet. 8. **A resource disappeared from the platform after a deploy.** It was removed from `module.ts`; the module is the source of truth and deploy converges to it. Restore the declaration and re-deploy. +9. **`dev` or `deploy` rejects the app graph (`COMPOSE.GRAPH_INVALID`) over + a name.** Resource ids declared in the module (`provision()` ids, + `compute({ name })`) allow `[A-Za-z0-9]` only. A hyphenated id passes + authoring and typecheck, then fails here. Module and project names do + accept hyphens, so never derive a resource id from them. +10. **A deployed service errors on its first timestamp read + (`RUNTIME.TEMPORAL_UNAVAILABLE`).** The runtime has no global + `Temporal`; see the Compute runtime section for the polyfill. ## What the platform doesn't do yet From 141bf591833c1b9b728efec8ca3aa5aa13c28c63 Mon Sep 17 00:00:00 2001 From: Tyler Hogarth <5163494+tylerhogarth@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:56:44 +0200 Subject: [PATCH 4/5] init e2e: stage skills so the sync outcome is deterministic With prisma allowlisted as a skill source, the init sync outcome depended on whether a local pack had left a staged skills/ directory in packages/prisma: absent in CI (no-skills), present locally (synced). The suite now runs stage-skills.mjs in beforeAll, the same script prepack runs, and asserts the outcome a real install sees: the first init syncs the platform skill into the agent directories, and a rerun reports up-to-date. Co-Authored-By: Claude Fable 5 --- packages/cli/e2e/init.e2e.ts | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/packages/cli/e2e/init.e2e.ts b/packages/cli/e2e/init.e2e.ts index 7886f2ef..bb88382c 100644 --- a/packages/cli/e2e/init.e2e.ts +++ b/packages/cli/e2e/init.e2e.ts @@ -90,6 +90,14 @@ describe("prisma init", () => { "packages/prisma is not built; run `pnpm --filter prisma build` before the e2e suite so the scaffold's prisma/config import can resolve.", ); } + // Stage the skills the published tarball would carry (the same + // script prepack runs). Without this the sync outcome depends on + // whether a local `pnpm pack` happened to leave a staged copy. + await execFileAsync( + process.execPath, + [path.resolve(import.meta.dirname, "../../../scripts/stage-skills.mjs")], + { cwd: PRISMA_PACKAGE_DIR }, + ); workdir = await mkdtemp(path.join(os.tmpdir(), "prisma-e2e-init-")); await writeFile( path.join(workdir, "package.json"), @@ -110,7 +118,7 @@ describe("prisma init", () => { await rm(workdir, { recursive: true, force: true }); }); - it("adds the postinstall hook, scaffolds the config, and finds no skills to sync", async () => { + it("adds the postinstall hook, scaffolds the config, and syncs the platform skill", async () => { const envelope = await runInit(workdir); expect(envelope.ok).toBe(true); @@ -128,10 +136,23 @@ describe("prisma init", () => { "agents", "devin", ]); - // No allowlisted Prisma package is installed here, so the sync has - // nothing to do and says so instead of failing. - expect(envelope.result.skills.outcome).toBe("no-packages"); - expect(envelope.result.skills.sync?.packages).toEqual([]); + // The fixture installs prisma itself, which ships the platform + // skill, so init's sync delivers it into the agent directories. + expect(envelope.result.skills.outcome).toBe("synced"); + expect(envelope.result.skills.sync?.packages).toMatchObject([ + { package: "prisma" }, + ]); + expect( + existsSync( + path.join( + workdir, + ".claude", + "skills", + "prisma-platform-core-concepts", + "SKILL.md", + ), + ), + ).toBe(true); expect(envelope.diagnostics).toEqual([]); const manifest = JSON.parse( @@ -205,7 +226,9 @@ describe("prisma init", () => { expect(envelope.result.postinstall.outcome).toBe("exists"); expect(envelope.result.postinstall.dependency).toBe("declared"); expect(envelope.result.config.outcome).toBe("created"); - expect(envelope.result.skills.outcome).toBe("no-packages"); + // The first run already synced the skill and its stamp still + // matches the installed package, so the rerun writes nothing. + expect(envelope.result.skills.outcome).toBe("up-to-date"); expect(envelope.diagnostics.map((d) => d.code)).not.toContain( "INIT.CONFIG_KEPT", ); From 03fb21af388af6a015f1d637b76a8136878e3a29 Mon Sep 17 00:00:00 2001 From: Tyler Hogarth <5163494+tylerhogarth@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:05:18 +0200 Subject: [PATCH 5/5] Platform skill: use the documented Temporal polyfill entry point The ORM's error reference for RUNTIME.TEMPORAL_UNAVAILABLE documents import 'temporal-polyfill/full/global' (full calendar support), not the bare /global entry the benchmark agents happened to use. Name the dependency explicitly too. Co-Authored-By: Claude Fable 5 --- skills/prisma-platform-core-concepts/SKILL.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/skills/prisma-platform-core-concepts/SKILL.md b/skills/prisma-platform-core-concepts/SKILL.md index aa0efeb6..0adcffd1 100644 --- a/skills/prisma-platform-core-concepts/SKILL.md +++ b/skills/prisma-platform-core-concepts/SKILL.md @@ -190,8 +190,9 @@ container image to author and no platform emulator to install locally. The runtime has no global `Temporal` (stock Node 24 lacks one too). Code that reads an ORM `DateTime` column compiles and deploys, then throws on -the first read. Either `import 'temporal-polyfill/global'` at the service -entry, or use the `*String` column types such as `TimestamptzString`. +the first read. Either add the `temporal-polyfill` dependency and +`import 'temporal-polyfill/full/global'` at the service entry, or use the +`*String` column types such as `TimestamptzString`. An idle service **sleeps**: the platform snapshots its memory after a short period of inactivity and resumes it from the snapshot on the next request.