@rehearsal-db/core 1.0.2 → 1.1.0

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/CHANGELOG.md CHANGED
@@ -1,10 +1,35 @@
1
1
  # Changelog
2
2
 
3
- All notable Rehearsal package changes will be documented here. The format follows Keep
4
- a Changelog, and versions will follow Semantic Versioning after the package exists.
3
+ All notable Rehearsal package changes are documented here. The format follows Keep
4
+ a Changelog, and published versions follow Semantic Versioning.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [1.1.0] - 2026-10-21
9
+
10
+ ### Added
11
+
12
+ - Optional read-only runtime privilege expectations for reviewed schemas, tables,
13
+ sequences and exact function signatures. Required and forbidden effective access is
14
+ separate from RLS, includes inherited and column-only permissions, and never repairs
15
+ grants automatically. Existing policies without this field keep their behavior.
16
+ - Document explicit least-privilege Data API grants, forward-only correction and
17
+ positive/negative API proofs for Supabase's October 30 opt-in table-exposure change.
18
+
19
+ ### Fixed
20
+
21
+ - Record the Supabase provider in newly written runtime receipts, so inventory can
22
+ distinguish recorded format/provider compatibility from unknown legacy provenance.
23
+ Older receipts remain unknown rather than being silently rewritten or reverified.
24
+ - Bound newly generated Supabase project IDs to the provider's 40-character limit
25
+ with a deterministic suffix. Preserve valid existing identities; reject invalid
26
+ configured IDs without renaming or adopting resources.
27
+ - Align OS setup helpers with core's Node.js 22, 24 and 26 support. macOS/Linux
28
+ helpers accept an explicit `REHEARSAL_NODE_PATH`, and missing-Node guidance points
29
+ to version-manager selection or the official installer without installing an OS tool.
30
+ - Update compatible older CLI installations to the helper's exact current target
31
+ instead of treating every 1.0.1-or-newer installation as already current.
32
+
8
33
  ## [1.0.2] - 2026-10-20
9
34
 
10
35
  ### Fixed
@@ -815,7 +840,8 @@ not unattended cleanup, and container age is not proof of user inactivity.
815
840
  publication uses short-lived trusted OIDC, and every release tag must already exist on
816
841
  protected `main`.
817
842
 
818
- [Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.2...HEAD
843
+ [Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.1.0...HEAD
844
+ [1.1.0]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.2...v1.1.0
819
845
  [1.0.2]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.1...v1.0.2
820
846
  [1.0.1]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.0...v1.0.1
821
847
  [1.0.0]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.10...v1.0.0
package/COMPATIBILITY.md CHANGED
@@ -26,12 +26,18 @@ The first `1.0.0` release requires external beta evidence, a support policy and
26
26
  settled public API described in [the stability policy](docs/stability.md). Supporting
27
27
  additional database families, package managers or operating systems is not implied.
28
28
 
29
- Rehearsal supports maintained even-numbered Node.js release lines that pass its CI
30
- matrix. Rehearsal 1.0 supports Node.js 22, 24, and 26; Node.js 24 LTS is the recommended
31
- default and the release-build runtime. End-of-life and odd-numbered Node.js releases are
32
- not supported. A future release may remove a Node.js line after its upstream end-of-life
29
+ Rehearsal supports the maintained Node.js release lines verified by its existing CI
30
+ matrix: Node.js 22, 24, and 26. Node.js 24 LTS is the recommended default and the
31
+ release-build runtime. End-of-life and unverified future release lines are not
32
+ supported. A future release may remove a Node.js line after its upstream end-of-life
33
33
  date, with that change called out in the changelog.
34
34
 
35
+ Core 1.1.0's OS helpers accept the same three lines. A terminal or desktop launcher
36
+ that cannot find Node does not mean an installed version is unsupported. Select it
37
+ through the project's existing version manager or use the helper's explicit executable
38
+ path; see [OS setup](installers/README.md). Helpers never install Node or switch a
39
+ project's runtime automatically.
40
+
35
41
  ## Operating systems and execution location
36
42
 
37
43
  Core 1.0.1's platform boundary distinguishes the computer running VS Code from the
package/README.md CHANGED
@@ -47,7 +47,7 @@ it through a reviewed privacy policy, and retire the access again. It is never p
47
47
  production deployment tool.
48
48
 
49
49
  The CLI is free and MIT-licensed, with stable releases on
50
- [npm](https://www.npmjs.com/package/@rehearsal-db/core). This guide targets **1.0.2**;
50
+ [npm](https://www.npmjs.com/package/@rehearsal-db/core). This guide targets **1.1.0**;
51
51
  install only a version actually published there. Prefer a visual workflow?
52
52
  [Install the free VS Code Starter preview](docs/vscode.md). Its included features are
53
53
  free indefinitely for personal or commercial development—no account, card, or trial.
@@ -71,7 +71,7 @@ your migration is intended to change.
71
71
 
72
72
  Prefer OS-specific files? Use the [Windows / WSL 2](installers/windows/README.md),
73
73
  [macOS](installers/macos/README.md) or [Linux](installers/linux/README.md) setup folder.
74
- These readable helpers require existing Node.js 24 or 26 and check prerequisites by
74
+ These readable helpers accept existing Node.js 22, 24 or 26 and check prerequisites by
75
75
  default; installation is an explicit option. They do not install OS tools or start a
76
76
  database. See [their exact behavior and release availability](installers/README.md).
77
77
 
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rehearsal-db/core",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "description": "Catch PostgreSQL and Supabase migrations that pass on empty databases but fail on real data—before deploy.",
6
6
  "repository": {
@@ -126,6 +126,7 @@ export interface RehearsalConfig {
126
126
  protected?: boolean;
127
127
  target?: "supabase" | "postgresql";
128
128
  applicationUrl?: string;
129
+ /** Exact lowercase/digit/hyphen ownership ID: 2–40 Supabase, 2–63 PostgreSQL. */
129
130
  projectId?: string;
130
131
  apiPort?: number;
131
132
  databasePort: number;
@@ -9,6 +9,7 @@ import { normalizeResourcePolicy, normalizeRequiredServices, } from "./configura
9
9
  import { normalizeConfigurationProfiles } from "./configuration_profiles.mjs";
10
10
  import { normalizeApplicationUrls } from "./configuration_application_urls.mjs";
11
11
  import { normalizeApplicationActivity } from "./configuration_activity.mjs";
12
+ import { assertRuntimeProjectId } from "./runtime_project_identity.mjs";
12
13
  import { assertBoolean, assertCommand, assertDatabaseIdentifier, assertEnvironmentKey, assertEnvironmentReference, assertHttpStatus, assertIdentifier, assertKnownKeys, assertLoopbackUrl, assertNonEmptyString, assertPlainObject, assertPort, assertPostgresImage, assertPositiveInteger, assertRelativePath, assertStringArray, } from "./configuration_validation.mjs";
13
14
  const LOOPBACK_HOSTS = new Set(REHEARSAL_DEFAULTS.safety.allowedHosts);
14
15
  const SUPPORTED_SUPABASE_OAUTH_PROVIDERS = new Set(["github", "google"]);
@@ -351,7 +352,7 @@ export const normalizeRehearsalConfig = (input) => {
351
352
  protected: assertBoolean(runtime.protected ?? false, "config.runtime.protected"),
352
353
  target: runtimeTarget,
353
354
  applicationUrl: applicationUrls.applicationUrl,
354
- projectId: assertNonEmptyString(runtime.projectId ?? REHEARSAL_DEFAULTS.runtime.projectId, "config.runtime.projectId"),
355
+ projectId: assertRuntimeProjectId(runtime.projectId ?? REHEARSAL_DEFAULTS.runtime.projectId, runtimeTarget),
355
356
  ports: Object.freeze(ports),
356
357
  }),
357
358
  safety: Object.freeze({
@@ -1,4 +1,5 @@
1
1
  /** Render reviewed starter configurations without reading or writing a project. */
2
+ import { createRuntimeProjectId } from "./runtime_project_identity.mjs";
2
3
  export const renderDetectedConfig = (detected, { applicationUrl = "http://localhost:5175", ports = { api: 58321, database: 58322, studio: 58323 }, } = {}) => `// @ts-check
3
4
  /**
4
5
  * Rehearsal configuration for ${detected.projectName}.
@@ -132,7 +133,7 @@ export default defineRehearsalConfig({
132
133
  protected: false, // Pin data against reset, run, migrate, discard, and runtime cleanup.
133
134
  target: "supabase",
134
135
  applicationUrl: ${JSON.stringify(applicationUrl)},
135
- projectId: "${detected.projectName}-rehearsal",
136
+ projectId: ${JSON.stringify(createRuntimeProjectId(detected.projectName, "supabase", detected.projectIdSeed))},
136
137
  apiPort: ${ports.api},
137
138
  databasePort: ${ports.database},
138
139
  studioPort: ${ports.studio},
@@ -266,7 +267,7 @@ export default defineRehearsalConfig({
266
267
  protected: false, // Pin data against reset, run, migrate, discard, and runtime cleanup.
267
268
  target: "postgresql",
268
269
  applicationUrl: ${JSON.stringify(applicationUrl)},
269
- projectId: "${detected.projectName}-rehearsal",
270
+ projectId: ${JSON.stringify(createRuntimeProjectId(detected.projectName, "postgresql", detected.projectIdSeed))},
270
271
  databasePort: ${databasePort},
271
272
  },
272
273
 
@@ -3,7 +3,6 @@ import { basename, isAbsolute, join, relative, resolve } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import { findRehearsalConfigPath, } from "./configuration_discovery.mjs";
5
5
  import { normalizeRehearsalConfig } from "./configuration_normalization.mjs";
6
- import { assertIdentifier } from "./configuration_validation.mjs";
7
6
  export const loadRehearsalConfig = async ({ projectRoot = process.cwd(), configPath, } = {}) => {
8
7
  const root = resolve(projectRoot);
9
8
  const path = await findRehearsalConfigPath({
@@ -74,7 +73,6 @@ export const loadRehearsalConfig = async ({ projectRoot = process.cwd(), configP
74
73
  throw new Error("config.preparation.privacyKey must resolve to a secret file inside config.baseline.artifactDirectory.");
75
74
  }
76
75
  }
77
- assertIdentifier(config.runtime.projectId, "config.runtime.projectId");
78
76
  return Object.freeze({
79
77
  config,
80
78
  configPath: path,
@@ -1,6 +1,8 @@
1
1
  /** Inspect project-local files used to render a conservative starter config. */
2
2
  export interface DetectedProject {
3
3
  projectName: string;
4
+ /** Untruncated normalized name keeps shortened new runtime identities distinct. */
5
+ projectIdSeed?: string;
4
6
  packageManager: "npm" | "pnpm" | "yarn";
5
7
  hasSupabaseConfig: boolean;
6
8
  hasMigrations: boolean;
@@ -31,6 +31,7 @@ export const inspectDetectedProject = async ({ projectRoot = process.cwd(), } =
31
31
  "rehearsal-project";
32
32
  return {
33
33
  projectName,
34
+ projectIdSeed: normalizedProjectName || projectName,
34
35
  packageManager,
35
36
  hasSupabaseConfig: await hasPath("supabase/config.toml"),
36
37
  hasMigrations: await hasPath("supabase/migrations"),
@@ -0,0 +1,5 @@
1
+ /** Preserve exact configured runtime ownership and bound newly generated identities. */
2
+ import type { RehearsalRuntimeTargetId } from "../targets/target.mjs";
3
+ export declare const assertRuntimeProjectId: (value: unknown, target: RehearsalRuntimeTargetId) => string;
4
+ /** Only new setup identities are shortened; valid existing generated IDs stay stable. */
5
+ export declare const createRuntimeProjectId: (projectName: string, target: RehearsalRuntimeTargetId, identitySeed?: string) => string;
@@ -0,0 +1,41 @@
1
+ /** Preserve exact configured runtime ownership and bound newly generated identities. */
2
+ import { createHash } from "node:crypto";
3
+ import { RehearsalError } from "../shared/diagnostics.mjs";
4
+ import { assertIdentifier, assertNonEmptyString, } from "./configuration_validation.mjs";
5
+ const maximumProjectIdLength = (target) => target === "supabase" ? 40 : 63;
6
+ export const assertRuntimeProjectId = (value, target) => {
7
+ const projectId = assertNonEmptyString(value, "config.runtime.projectId");
8
+ const maximumLength = maximumProjectIdLength(target);
9
+ if (projectId.length < 2 || projectId.length > maximumLength) {
10
+ const provider = target === "supabase" ? "Supabase" : "PostgreSQL";
11
+ throw new RehearsalError({
12
+ category: "configuration_invalid",
13
+ code: "CONFIGURATION_INVALID",
14
+ message: `config.runtime.projectId must contain 2–${maximumLength} characters for ${provider}.`,
15
+ expected: `2–${maximumLength} lowercase letters, digits, or hyphens`,
16
+ actual: { length: projectId.length },
17
+ context: { field: "runtime.projectId", target, maximumLength },
18
+ refused: "No runtime was started or renamed. Rehearsal never truncates a configured project ID or adopts resources by prefix.",
19
+ suggestions: [
20
+ "Choose a distinct, explicitly reviewed ID for a new disposable runtime. Review existing resources before changing their configured identity.",
21
+ ],
22
+ });
23
+ }
24
+ return assertIdentifier(projectId, "config.runtime.projectId");
25
+ };
26
+ /** Only new setup identities are shortened; valid existing generated IDs stay stable. */
27
+ export const createRuntimeProjectId = (projectName, target, identitySeed = projectName) => {
28
+ const candidate = `${projectName}-rehearsal`;
29
+ const maximumLength = maximumProjectIdLength(target);
30
+ if (candidate.length <= maximumLength)
31
+ return assertRuntimeProjectId(candidate, target);
32
+ const digest = createHash("sha256")
33
+ .update(`${identitySeed}-rehearsal`)
34
+ .digest("hex")
35
+ .slice(0, 12);
36
+ const suffix = `-${digest}-rehearsal`;
37
+ const prefix = projectName
38
+ .slice(0, maximumLength - suffix.length)
39
+ .replace(/-+$/u, "");
40
+ return assertRuntimeProjectId(`${prefix}${suffix}`, target);
41
+ };
@@ -8,6 +8,7 @@ import { access, mkdir, open, readFile, rename, rm, writeFile, } from "node:fs/p
8
8
  import { basename, dirname, join, relative, resolve } from "node:path";
9
9
  import { findRehearsalConfigPath, inspectDetectedProject, loadRehearsalConfig, renderDetectedConfig, renderDetectedPostgresqlConfig, } from "./configuration.mjs";
10
10
  import { resolveRuntimeTarget } from "../targets/target.mjs";
11
+ import { createRuntimeProjectId } from "./runtime_project_identity.mjs";
11
12
  export { assertSupportedRehearsalNodeRuntime, inspectRehearsalNodeRuntime, } from "../shared/node_runtime.mjs";
12
13
  import { assertSupportedRehearsalNodeRuntime } from "../shared/node_runtime.mjs";
13
14
  const CONFIG_PATH = "rehearsal.config.mjs";
@@ -257,7 +258,8 @@ export const planRehearsalSetup = async ({ projectRoot = process.cwd(), configPa
257
258
  ...(isPortAvailable ? { isAvailable: isPortAvailable } : {}),
258
259
  });
259
260
  const applicationUrl = existing?.config.runtime.applicationUrl ?? "http://localhost:5175";
260
- const projectId = existing?.config.runtime.projectId ?? `${detected.projectName}-rehearsal`;
261
+ const projectId = existing?.config.runtime.projectId ??
262
+ createRuntimeProjectId(detected.projectName, runtimeTarget, detected.projectIdSeed);
261
263
  const sourceSupabaseConfig = runtimeTarget === "supabase"
262
264
  ? await readOptionalFile(join(root, "supabase/config.toml"))
263
265
  : null;
@@ -1,4 +1,5 @@
1
1
  /** Build narrowly validated local restore prerequisites and structural checks. */
2
+ import { type RuntimePrivilege } from "./privileges.mjs";
2
3
  type PolicyObject = Record<string, unknown>;
3
4
  interface RuntimeRelation {
4
5
  readonly schema: string;
@@ -55,6 +56,7 @@ export interface RuntimePolicy {
55
56
  readonly triggers: readonly RuntimeTrigger[];
56
57
  readonly localRows: readonly RuntimeLocalRow[];
57
58
  readonly expectations: readonly RuntimeExpectation[];
59
+ readonly privileges?: readonly RuntimePrivilege[];
58
60
  }
59
61
  export declare const IDENTITY_CLAIM_RECEIPT_SQL = "create schema if not exists rehearsal_internal;\ncreate table if not exists rehearsal_internal.identity_claims (\n identity_name text primary key,\n placeholder_user_id uuid not null unique,\n local_user_id uuid not null,\n claimed_at timestamptz not null default now()\n);";
60
62
  export declare const validateRuntimePolicy: (input: unknown) => RuntimePolicy;
@@ -1,4 +1,5 @@
1
1
  /** Build narrowly validated local restore prerequisites and structural checks. */
2
+ import { buildRuntimePrivilegeVerificationChecks, validateRuntimePrivileges, } from "./privileges.mjs";
2
3
  const IDENTIFIER = /^[a-z][a-z0-9_]{0,62}$/u;
3
4
  const IDENTITY_NAME = /^[a-z0-9][a-z0-9-]{1,62}$/u;
4
5
  const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/iu;
@@ -58,7 +59,14 @@ create table if not exists rehearsal_internal.identity_claims (
58
59
  );`;
59
60
  export const validateRuntimePolicy = (input) => {
60
61
  const policy = object(input, "runtimePolicy");
61
- keys(policy, ["policyVersion", "prerequisites", "triggers", "localRows", "expectations"], "runtimePolicy");
62
+ keys(policy, [
63
+ "policyVersion",
64
+ "prerequisites",
65
+ "triggers",
66
+ "localRows",
67
+ "expectations",
68
+ "privileges",
69
+ ], "runtimePolicy");
62
70
  if (policy.policyVersion !== 1)
63
71
  throw new Error("Runtime policy must use policyVersion 1.");
64
72
  const prerequisites = object(policy.prerequisites ?? {}, "runtimePolicy.prerequisites");
@@ -216,6 +224,9 @@ export const validateRuntimePolicy = (input) => {
216
224
  triggers: Object.freeze(triggers),
217
225
  localRows: Object.freeze(localRows),
218
226
  expectations: Object.freeze(expectations),
227
+ ...(Object.hasOwn(policy, "privileges")
228
+ ? { privileges: validateRuntimePrivileges(policy.privileges) }
229
+ : {}),
219
230
  });
220
231
  };
221
232
  export const buildRuntimePrerequisiteSql = (input) => {
@@ -360,6 +371,7 @@ export const buildRuntimeVerificationSql = (input) => {
360
371
  }
361
372
  checks.push(`if exists (select 1 from pg_constraint where not convalidated) then
362
373
  raise exception 'Rehearsal found an unvalidated structural constraint'; end if;`);
374
+ checks.push(...buildRuntimePrivilegeVerificationChecks(policy.privileges ?? []));
363
375
  return `do $rehearsal_verify$
364
376
  begin
365
377
  ${checks.join("\n ")}
@@ -0,0 +1,25 @@
1
+ /** Validate opt-in effective privileges and build catalog-only verification checks. */
2
+ export interface RuntimePrivilegeArgumentType {
3
+ readonly schema: "pg_catalog";
4
+ readonly name: string;
5
+ }
6
+ interface RuntimePrivilegeBase {
7
+ readonly schema: string;
8
+ readonly role: string;
9
+ readonly required: readonly string[];
10
+ readonly forbidden: readonly string[];
11
+ }
12
+ export type RuntimePrivilege = RuntimePrivilegeBase & ({
13
+ readonly kind: "schema";
14
+ } | {
15
+ readonly kind: "table" | "sequence";
16
+ readonly name: string;
17
+ } | {
18
+ readonly kind: "function";
19
+ readonly name: string;
20
+ readonly argumentTypes: readonly RuntimePrivilegeArgumentType[];
21
+ });
22
+ export declare const validateRuntimePrivileges: (input: unknown) => readonly RuntimePrivilege[];
23
+ /** Blocks are embedded inside the policy verifier; no ACL or object writes are emitted. */
24
+ export declare const buildRuntimePrivilegeVerificationChecks: (entries: readonly RuntimePrivilege[]) => readonly string[];
25
+ export {};
@@ -0,0 +1,198 @@
1
+ /** Validate opt-in effective privileges and build catalog-only verification checks. */
2
+ const IDENTIFIER = /^[a-z][a-z0-9_]{0,62}$/u;
3
+ const TYPE_IDENTIFIER = /^[a-z_][a-z0-9_]{0,62}$/u;
4
+ const PRIVILEGES = {
5
+ schema: ["CREATE", "USAGE"],
6
+ table: [
7
+ "SELECT",
8
+ "INSERT",
9
+ "UPDATE",
10
+ "DELETE",
11
+ "TRUNCATE",
12
+ "REFERENCES",
13
+ "TRIGGER",
14
+ "MAINTAIN",
15
+ ],
16
+ sequence: ["USAGE", "SELECT", "UPDATE"],
17
+ function: ["EXECUTE"],
18
+ };
19
+ const COLUMN_PRIVILEGES = new Set(["SELECT", "INSERT", "UPDATE", "REFERENCES"]);
20
+ const object = (value, label) => {
21
+ if (value === null ||
22
+ typeof value !== "object" ||
23
+ Array.isArray(value) ||
24
+ Object.getPrototypeOf(value) !== Object.prototype) {
25
+ throw new Error(`${label} must be an object.`);
26
+ }
27
+ return value;
28
+ };
29
+ const keys = (value, allowed, label) => {
30
+ if (Object.keys(value).some((key) => !allowed.includes(key)))
31
+ throw new Error(`${label} contains unsupported fields.`);
32
+ };
33
+ const identifier = (value, label) => {
34
+ if (typeof value !== "string" || !IDENTIFIER.test(value))
35
+ throw new Error(`${label} is not a safe PostgreSQL identifier.`);
36
+ return value;
37
+ };
38
+ const permissions = (value, allowed, label) => {
39
+ if (!Array.isArray(value) ||
40
+ value.length > allowed.length ||
41
+ value.includes(undefined) ||
42
+ value.some((entry) => typeof entry !== "string" || !allowed.includes(entry)) ||
43
+ new Set(value).size !== value.length) {
44
+ throw new Error(`${label} must contain distinct supported privileges.`);
45
+ }
46
+ return Object.freeze([...value]);
47
+ };
48
+ export const validateRuntimePrivileges = (input) => {
49
+ if (!Array.isArray(input) ||
50
+ input.length > 1_024 ||
51
+ input.includes(undefined))
52
+ throw new Error("runtimePolicy.privileges must be an array of at most 1024 declarations.");
53
+ const observed = new Map();
54
+ return Object.freeze(input.map((value, index) => {
55
+ const label = `runtimePolicy.privileges[${index}]`;
56
+ const entry = object(value, label);
57
+ if (typeof entry.kind !== "string" ||
58
+ !Object.hasOwn(PRIVILEGES, entry.kind)) {
59
+ throw new Error(`${label}.kind is unsupported.`);
60
+ }
61
+ const kind = entry.kind;
62
+ keys(entry, [
63
+ "kind",
64
+ "schema",
65
+ "role",
66
+ "required",
67
+ "forbidden",
68
+ ...(kind === "schema" ? [] : ["name"]),
69
+ ...(kind === "function" ? ["argumentTypes"] : []),
70
+ ], label);
71
+ const schema = identifier(entry.schema, `${label}.schema`);
72
+ const role = identifier(entry.role, `${label}.role`);
73
+ const allowed = PRIVILEGES[kind];
74
+ const required = permissions(Object.hasOwn(entry, "required") ? entry.required : [], allowed, `${label}.required`);
75
+ const forbidden = permissions(Object.hasOwn(entry, "forbidden") ? entry.forbidden : [], allowed, `${label}.forbidden`);
76
+ if (required.length + forbidden.length === 0)
77
+ throw new Error(`${label} must declare a required or forbidden privilege.`);
78
+ const base = { schema, role, required, forbidden };
79
+ let normalized;
80
+ if (kind === "schema")
81
+ normalized = Object.freeze({ ...base, kind });
82
+ else {
83
+ const name = identifier(entry.name, `${label}.name`);
84
+ if (kind !== "function")
85
+ normalized = Object.freeze({ ...base, kind, name });
86
+ else {
87
+ if (!Array.isArray(entry.argumentTypes) ||
88
+ entry.argumentTypes.length > 32 ||
89
+ entry.argumentTypes.includes(undefined))
90
+ throw new Error(`${label}.argumentTypes must be an array of at most 32 types.`);
91
+ const argumentTypes = entry.argumentTypes.map((value, argumentIndex) => {
92
+ const typeLabel = `${label}.argumentTypes[${argumentIndex}]`;
93
+ const type = object(value, typeLabel);
94
+ keys(type, ["schema", "name"], typeLabel);
95
+ if (type.schema !== "pg_catalog" ||
96
+ typeof type.name !== "string" ||
97
+ !TYPE_IDENTIFIER.test(type.name)) {
98
+ throw new Error(`${typeLabel} must name a safe pg_catalog type.`);
99
+ }
100
+ return Object.freeze({
101
+ schema: "pg_catalog",
102
+ name: type.name,
103
+ });
104
+ });
105
+ normalized = Object.freeze({
106
+ ...base,
107
+ kind,
108
+ name,
109
+ argumentTypes: Object.freeze(argumentTypes),
110
+ });
111
+ }
112
+ }
113
+ const identity = JSON.stringify([
114
+ kind,
115
+ schema,
116
+ normalized.kind === "schema" ? null : normalized.name,
117
+ role,
118
+ normalized.kind === "function" ? normalized.argumentTypes : null,
119
+ ]);
120
+ const earlier = observed.get(identity) ?? {
121
+ required: new Set(),
122
+ forbidden: new Set(),
123
+ };
124
+ if (required.some((privilege) => forbidden.includes(privilege) || earlier.forbidden.has(privilege)) ||
125
+ forbidden.some((privilege) => earlier.required.has(privilege))) {
126
+ throw new Error(`${label} has overlapping required and forbidden privileges.`);
127
+ }
128
+ required.forEach((privilege) => earlier.required.add(privilege));
129
+ forbidden.forEach((privilege) => earlier.forbidden.add(privilege));
130
+ observed.set(identity, earlier);
131
+ return normalized;
132
+ }));
133
+ };
134
+ const literal = (value) => `'${value.replaceAll("'", "''")}'`;
135
+ const typeLookup = (type) => `select t.oid from pg_catalog.pg_type t
136
+ join pg_catalog.pg_namespace n on n.oid = t.typnamespace
137
+ where n.nspname = 'pg_catalog' and t.typname = ${literal(type.name)}`;
138
+ const objectLookup = (entry) => {
139
+ if (entry.kind === "schema")
140
+ return `select n.oid into rehearsal_object from pg_catalog.pg_namespace n
141
+ where n.nspname = ${literal(entry.schema)};`;
142
+ if (entry.kind === "function")
143
+ return `select p.oid into rehearsal_object from pg_catalog.pg_proc p
144
+ join pg_catalog.pg_namespace n on n.oid = p.pronamespace
145
+ where n.nspname = ${literal(entry.schema)} and p.proname = ${literal(entry.name)}
146
+ and p.prokind = 'f' and p.pronargs = ${entry.argumentTypes.length}
147
+ ${entry.argumentTypes.map((type, index) => `and p.proargtypes[${index}] = (${typeLookup(type)})`).join("\n ")};`;
148
+ return `select c.oid into rehearsal_object from pg_catalog.pg_class c
149
+ join pg_catalog.pg_namespace n on n.oid = c.relnamespace
150
+ where n.nspname = ${literal(entry.schema)} and c.relname = ${literal(entry.name)}
151
+ and c.relkind ${entry.kind === "sequence" ? "= 'S'" : "in ('r', 'p', 'v', 'm', 'f')"};`;
152
+ };
153
+ const permissionCheck = (entry, privilege, forbidden) => {
154
+ const functionName = `pg_catalog.has_${entry.kind}_privilege`;
155
+ const tableCheck = `${functionName}(rehearsal_role, rehearsal_object, ${literal(privilege)})`;
156
+ const expression = entry.kind === "table" && forbidden && COLUMN_PRIVILEGES.has(privilege)
157
+ ? `(${tableCheck} or pg_catalog.has_any_column_privilege(rehearsal_role, rehearsal_object, ${literal(privilege)}))`
158
+ : tableCheck;
159
+ const evaluation = privilege === "MAINTAIN"
160
+ ? `if pg_catalog.current_setting('server_version_num')::integer < 170000 then
161
+ rehearsal_allowed := false;
162
+ else
163
+ rehearsal_allowed := ${expression};
164
+ end if;`
165
+ : `rehearsal_allowed := ${expression};`;
166
+ return `${evaluation}
167
+ if rehearsal_allowed is null then
168
+ raise exception 'Rehearsal privilege object is unavailable';
169
+ end if;
170
+ if rehearsal_allowed is ${forbidden ? "true" : "false"} then
171
+ raise exception 'Rehearsal ${forbidden ? "forbidden privilege is present" : "required privilege is missing"}';
172
+ end if;`;
173
+ };
174
+ /** Blocks are embedded inside the policy verifier; no ACL or object writes are emitted. */
175
+ export const buildRuntimePrivilegeVerificationChecks = (entries) => entries.map((entry) => `declare
176
+ rehearsal_role pg_catalog.oid;
177
+ rehearsal_object pg_catalog.oid;
178
+ rehearsal_allowed boolean;
179
+ begin
180
+ select r.oid into rehearsal_role from pg_catalog.pg_roles r
181
+ where r.rolname = ${literal(entry.role)};
182
+ if rehearsal_role is null then
183
+ raise exception 'Rehearsal privilege role is missing';
184
+ end if;
185
+ ${entry.kind === "function"
186
+ ? entry.argumentTypes
187
+ .map((type) => `if not exists (${typeLookup(type)}) then
188
+ raise exception 'Rehearsal privilege argument type is missing';
189
+ end if;`)
190
+ .join("\n ")
191
+ : ""}
192
+ ${objectLookup(entry)}
193
+ if rehearsal_object is null then
194
+ raise exception 'Rehearsal privilege object is missing';
195
+ end if;
196
+ ${entry.required.map((privilege) => permissionCheck(entry, privilege, false)).join("\n ")}
197
+ ${entry.forbidden.map((privilege) => permissionCheck(entry, privilege, true)).join("\n ")}
198
+ end;`);
@@ -426,6 +426,7 @@ const resetRuntime = async () => {
426
426
  path: runtimeMarkerPath,
427
427
  marker: {
428
428
  formatVersion: active.baseline.formatVersion,
429
+ target: "supabase",
429
430
  generationId: active.baseline.generationId,
430
431
  dataSha256: readBaselineDataSha256(active.baseline),
431
432
  schemaSha256: active.baseline.files?.["production-schema.sql"]?.sha256 ?? null,
package/docs/README.md CHANGED
@@ -41,7 +41,7 @@ accomplish and follow that path.
41
41
  ## Follow the project
42
42
 
43
43
  - [Roadmap](roadmap.md)
44
- - [Stability and the first stable release](stability.md)
44
+ - [Stability and release verification](stability.md)
45
45
  - [Repository architecture](architecture.md)
46
46
  - [Release process](releasing.md)
47
47
 
@@ -229,6 +229,10 @@ approval sequence.
229
229
 
230
230
  `runtimePolicy` replaces common restore adapters with reviewed schemas, allowlisted
231
231
  extensions, managed triggers, local-only singleton rows, and structural expectations.
232
+ The [runtime policy guide](runtime-policies.md#explicit-data-api-grants) also owns the
233
+ least-privilege Data API migration checklist and core 1.1.0's optional read-only
234
+ required/forbidden privilege declarations. Earlier releases do not accept that new
235
+ declaration. Rehearsal never infers or automatically grants API access.
232
236
  `identityPolicy` declares a hashed verified-email or provider-subject matcher, copied
233
237
  placeholder, safe signup defaults, required role/reference transfers, Storage path
234
238
  rewrites, and token checks. Provider credentials stay in the local Supabase runtime
@@ -300,8 +304,10 @@ Rehearsal stacks.
300
304
  `keep-until-stop` preserves today's interactive behavior, while `stop-after-run` stops
301
305
  the targets after proofs, proof failures, or interruption. `lifecycle.open` uses
302
306
  `keep-until-stop` or `stop-on-application-exit` after the app closes. Every mode stops
303
- only Rehearsal's configured targets, never the shared engine. Ordinary stopping keeps
304
- database and Storage volumes and all sandbox edits; it does not reset or discard them.
307
+ only Rehearsal's configured targets; lifecycle selection does not enable engine shutdown.
308
+ The separately opted-in `stopOwnedColimaWhenIdle` policy above is the only guarded Colima
309
+ shutdown path. Ordinary stopping keeps database and Storage volumes and all sandbox
310
+ edits; it does not reset or discard them.
305
311
  See [Container runtime lifecycle](runtime-lifecycle.md).
306
312
 
307
313
  ### Foreground application idle shutdown
@@ -637,8 +643,17 @@ The VS Code Local Resources manager reviews an exact diff before changing either
637
643
  See [Local Resources](runtime-lifecycle.md#local-resources-manager) for image and volume rules.
638
644
 
639
645
  Use unique, non-privileged ports that do not overlap. `projectId` accepts lowercase
640
- letters, numbers, and hyphens. It labels the local Docker resources so cleanup targets
641
- only this project.
646
+ letters, numbers, and hyphens: 2–40 characters for Supabase, or 2–63 for PostgreSQL.
647
+ It labels the local Docker resources so cleanup targets only this exact project.
648
+ Overlong configured IDs are refused before a provider starts; Rehearsal never truncates
649
+ them or adopts resources using a shortened prefix. Review existing resources before
650
+ changing a configured ID, and use a distinct reviewed identity for a new disposable
651
+ runtime rather than assuming that a renamed config owns old containers or volumes.
652
+
653
+ For new setup only, the generated `<project-name>-rehearsal` ID stays unchanged when it
654
+ fits the selected provider. Longer new IDs use a readable prefix and a deterministic
655
+ hash suffix within that provider's limit. Setup uses the same exact ID in the generated
656
+ Rehearsal config and Supabase TOML. Existing configuration files are never rewritten.
642
657
 
643
658
  Application and readiness URLs must use a different port from the configured database,
644
659
  API, and Studio services. Editing a URL does not rewrite your framework's development
@@ -12,17 +12,17 @@ Separate setup folders are available for
12
12
  [macOS](https://github.com/Ddupasquier/rehearsal-db/tree/main/installers/macos) and
13
13
  [Linux](https://github.com/Ddupasquier/rehearsal-db/tree/main/installers/linux).
14
14
  They are readable helpers, not native executables or bundled Node/Docker installers.
15
- Existing Node.js 24 or 26 is required. The default only checks an existing project;
15
+ Existing Node.js 22, 24 or 26 is required. The default only checks an existing project;
16
16
  installation needs an explicit option and does not start a database. Review the
17
17
  [shared behavior and release availability](https://github.com/Ddupasquier/rehearsal-db/blob/main/installers/README.md)
18
- before choosing an install option. An exact core 1.0.2 install requires that version to
18
+ before choosing an install option. An exact core 1.1.0 install requires that version to
19
19
  be published; a checked-in helper is not proof of publication.
20
20
 
21
21
  ### Windows: use a WSL 2 Linux workspace
22
22
 
23
23
  The Windows compatibility route is Linux execution inside **WSL 2**, not a native
24
- PowerShell/Command Prompt database runtime. Core 1.0.1 and extension 0.1.21 make that
25
- boundary explicit; native Windows version/help/discovery do not authorize runtime work.
24
+ PowerShell/Command Prompt database runtime. Since core 1.0.1 and extension 0.1.21, that
25
+ boundary is explicit; native Windows version/help/discovery do not authorize runtime work.
26
26
 
27
27
  1. Follow Microsoft's [WSL installation guide](https://learn.microsoft.com/en-us/windows/wsl/install)
28
28
  to prepare a WSL 2 Linux distribution, such as Ubuntu 24.04. Do not use WSL 1.
package/docs/releasing.md CHANGED
@@ -138,7 +138,9 @@ release-source migration.
138
138
  4. When an unfamiliar developer is available, have them follow clean-project onboarding.
139
139
  Correct and retest the first confusing, missing, or wrong instruction. Record when
140
140
  this human observation was unavailable; automated checks must not impersonate it.
141
- 5. Change `private` to `false` only in the reviewed release change.
141
+ 5. Confirm package metadata keeps `private: false` for this public npm package. Any
142
+ metadata change belongs in the reviewed release change; do not toggle visibility
143
+ or package privacy as an incidental publishing step.
142
144
  6. Record the exact tarball filename, SHA-1, SHA-256, allowlisted files, unpacked size,
143
145
  executable, declared runtime dependency inventory, and dependency audit result.
144
146
  7. Review the release change and its exact artifact evidence.
@@ -195,7 +197,7 @@ This is mutual exclusion, not a guaranteed ordered queue: reconcile a superseded
195
197
  pending workflow before deliberately requesting another run.
196
198
 
197
199
  The objective promotion checklist and compatibility boundary live in
198
- [Stability and the first stable release](stability.md).
200
+ [Stability and release verification](stability.md).
199
201
 
200
202
  The trusted publisher allows `npm dist-tag` only so this workflow can maintain those two
201
203
  tags without a stored npm token. A manual workflow run from `main` can repair the correct
package/docs/roadmap.md CHANGED
@@ -1,9 +1,16 @@
1
1
  # Next steps
2
2
 
3
- Rehearsal core **1.0.0 is available on
3
+ Rehearsal core **1.1.0 is available on
4
4
  [npm](https://www.npmjs.com/package/@rehearsal-db/core)** for guided Supabase and
5
- ordinary PostgreSQL migration rehearsals. The [free VS Code Starter preview](vscode.md)
6
- adds a visual workbench. Neither requires a purchase or account.
5
+ ordinary PostgreSQL migration rehearsals. The [free VS Code Starter 0.1.23 preview](vscode.md)
6
+ adds a visual workbench and is distributed through Marketplace and reviewed GitHub
7
+ VSIX releases. Neither requires a purchase or account. Core is MIT-licensed; all
8
+ features included in Starter are free indefinitely for personal or commercial development.
9
+
10
+ Stable core, the editor preview and a future paid Pro offer have separate completion
11
+ boundaries. A downloadable preview does not imply that every native accessibility,
12
+ provider or real-user acceptance observation has been completed. Publication does not
13
+ authorize a new database operation or migrate a running legacy extension.
7
14
 
8
15
  The standalone workflow keeps the rehearsal engine in the package while application
9
16
  meaning and privacy decisions remain project-owned. The sequence below explains its
@@ -30,10 +37,18 @@ Track the full breakdown in
30
37
  For questions or feature requests, use
31
38
  [GitHub issues](https://github.com/Ddupasquier/rehearsal-db/issues).
32
39
 
33
- ## Acceptance still required
40
+ ## Verification and supported-journey work
41
+
42
+ The [release verification policy](stability.md) owns promotion requirements. The
43
+ [Environment Workbench epic](https://github.com/Ddupasquier/rehearsal-db/issues/130)
44
+ and its children track remaining editor acceptance; the issue tracker owns current
45
+ status and pickup order, not this page. Supported-journey compatibility work,
46
+ including the [Supabase grant-preservation review](https://github.com/Ddupasquier/rehearsal-db/issues/199),
47
+ is distinct from adding new database families or paid features.
34
48
 
35
- These are verification requirements, not a live checklist of unfinished releases or
36
- consumer migrations. Record the observed results for each exact candidate separately.
49
+ For each exact candidate, retain the checks already proved and complete the missing
50
+ observations. The following describe evidence boundaries, not a claim that a shipped
51
+ release or consumer upgrade remains unfinished:
37
52
 
38
53
  - Install the exact packed candidate in clean Supabase and PostgreSQL consumers.
39
54
  - Exercise two unrelated synthetic schemas, bounded large data, interruption, drift,
@@ -48,6 +63,12 @@ consumer migrations. Record the observed results for each exact candidate separa
48
63
 
49
64
  ## Later database targets
50
65
 
51
- After the PostgreSQL family is stable in real projects, evaluate individual PostgreSQL
52
- services. MySQL, MongoDB, and unrelated database families require different migration,
53
- restore, and safety behavior and remain out of scope.
66
+ Additional database, runtime, identity-provider and migration-tool integrations require
67
+ their own reviewed capability contracts and independent acceptance. Research or a roadmap
68
+ ticket is not a support claim. MySQL, MongoDB, SQLite and other database families are not
69
+ supported by this PostgreSQL/Supabase release. They remain separate future work rather
70
+ than prerequisites for the current local workflow.
71
+
72
+ The complete CLI and its database/safety capabilities stay free. Optional paid Pro
73
+ workflows, licensing and commercial readiness remain separate owner decisions; this
74
+ roadmap does not approve a paywall, price, billing service or new distribution operation.
@@ -27,8 +27,8 @@ then repeat the command.
27
27
 
28
28
  Setting `autoStartColima: true` is an explicit convenience opt-in. If Docker is
29
29
  unavailable and the `colima` command exists, Rehearsal runs plain `colima start` and
30
- reports it. Rehearsal does not resize Colima and does not stop the shared engine
31
- automatically. Starting Docker can also wake unrelated containers that use Docker's
30
+ reports it. Rehearsal does not resize Colima. Engine shutdown remains off unless the
31
+ separate ownership-guarded policy below is explicitly enabled. Starting Docker can also wake unrelated containers that use Docker's
32
32
  `always` or `unless-stopped` restart policy. Rehearsal changes only the selected
33
33
  Supabase stack to Docker's `no` restart policy after it starts successfully.
34
34
 
@@ -84,8 +84,9 @@ lifecycle: {
84
84
  `run` accepts `keep-until-stop` or `stop-after-run`. `open` accepts
85
85
  `keep-until-stop` or `stop-on-application-exit`. The bounded choices stop every
86
86
  configured Rehearsal target in reverse dependency order after the command finishes,
87
- fails after startup, times out, or is interrupted. They never stop the shared Docker
88
- engine.
87
+ fails after startup, times out, or is interrupted. Lifecycle choices govern targets,
88
+ not engine ownership; they do not enable Colima shutdown. The separate
89
+ `stopOwnedColimaWhenIdle` policy still requires its own opt-in and ownership checks.
89
90
 
90
91
  Stopping is non-destructive: database and Storage volumes, applied migrations, and
91
92
  sandbox edits remain available to `start` or `open`. Only the existing explicit `reset`
@@ -66,6 +66,151 @@ identity policy. Verification accepts the configured placeholder before associat
66
66
  the exact recorded local identity afterward. Rehearsal stores that mapping only in its
67
67
  local internal schema; it does not duplicate the old role or weaken the row check.
68
68
 
69
+ ### Explicit Data API grants
70
+
71
+ Supabase's [official announcement](https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically)
72
+ sets October 30, 2026 for changing existing projects to opt-in exposure of new
73
+ `public` tables. Existing table grants remain; this is not evidence that a particular
74
+ project's setting has already changed. Direct PostgreSQL access is a separate contract.
75
+
76
+ [Grants and RLS](https://supabase.com/docs/guides/api/securing-your-api) answer different
77
+ questions: a grant allows an API role to reach an object; an RLS policy decides which
78
+ rows that role can use. Passing an administrator SQL query or finding an RLS policy
79
+ does not prove that an application's Data API request will work safely.
80
+
81
+ For every new or recreated API table:
82
+
83
+ 1. Review the exposed schemas and the exact operations needed by `anon`,
84
+ `authenticated`, and `service_role`. An internal table does not need API access.
85
+ 2. Put explicit, least-privilege grants and RLS in that table's new migration. Remove
86
+ unwanted inherited defaults for the new object; do not blindly grant anonymous CRUD.
87
+ Check its sequences and callable functions separately, including `PUBLIC EXECUTE`.
88
+ 3. Preserve already-applied migration files. Correct historical objects through a
89
+ separately reviewed forward migration, not by rewriting the baseline's prefix.
90
+ 4. Test an empty setup and the disposable restore/reset/replay path with both permissive
91
+ and revoked automatic defaults. A schema dump alone is not proof of effective ACLs:
92
+ [Supabase's dump guidance](https://supabase.com/docs/reference/cli/supabase-db-dump#supabase-db-dump)
93
+ warns that target default privileges can broaden restored access.
94
+ 5. Independently check SQL grants, RLS and actual allowed/denied Data API requests with
95
+ the application's roles. A successful `service_role` request does not prove ordinary
96
+ authenticated access, and an empty RLS-filtered response is not a grant refusal.
97
+
98
+ For example, a **new read-only authenticated API table**, created by its migration owner,
99
+ can deliberately declare its own boundary:
100
+
101
+ ```sql
102
+ create table public.api_notes (
103
+ id bigint generated by default as identity primary key,
104
+ owner_id uuid not null,
105
+ body text not null
106
+ );
107
+ alter table public.api_notes enable row level security;
108
+ revoke all on table public.api_notes from public, anon, authenticated, service_role;
109
+ revoke all on sequence public.api_notes_id_seq from public, anon, authenticated, service_role;
110
+ grant select on table public.api_notes to authenticated;
111
+ create policy read_own_notes on public.api_notes
112
+ for select to authenticated using ((select auth.uid()) = owner_id);
113
+ ```
114
+
115
+ This example deliberately permits no client inserts, sequence use or service-role
116
+ access. Choose a different reviewed boundary when the application needs those operations.
117
+ Apply such SQL only through your own authorized migration process; Rehearsal does not
118
+ change hosted grants or RLS on your behalf.
119
+
120
+ ### Read-only privilege expectations (core 1.1.0 and newer)
121
+
122
+ Core 1.1.0 adds the following optional declaration. Earlier releases, including
123
+ 1.0.2, do not accept `privileges`; install a published release containing this contract
124
+ before adding it. A project-owned application proof remains necessary for API/RLS behavior.
125
+
126
+ Add `privileges` to the same runtime-policy JSON to check required and forbidden
127
+ **effective** privileges after restore and migration. These checks issue no `GRANT`,
128
+ `REVOKE`, role switch or permission repair. They do not infer grants from RLS.
129
+
130
+ ```json
131
+ {
132
+ "policyVersion": 1,
133
+ "privileges": [
134
+ {
135
+ "kind": "schema",
136
+ "schema": "public",
137
+ "role": "authenticated",
138
+ "required": ["USAGE"],
139
+ "forbidden": ["CREATE"]
140
+ },
141
+ {
142
+ "kind": "table",
143
+ "schema": "public",
144
+ "name": "api_notes",
145
+ "role": "authenticated",
146
+ "required": ["SELECT"],
147
+ "forbidden": [
148
+ "INSERT",
149
+ "UPDATE",
150
+ "DELETE",
151
+ "TRUNCATE",
152
+ "REFERENCES",
153
+ "TRIGGER",
154
+ "MAINTAIN"
155
+ ]
156
+ },
157
+ {
158
+ "kind": "table",
159
+ "schema": "public",
160
+ "name": "api_notes",
161
+ "role": "anon",
162
+ "forbidden": [
163
+ "SELECT",
164
+ "INSERT",
165
+ "UPDATE",
166
+ "DELETE",
167
+ "TRUNCATE",
168
+ "REFERENCES",
169
+ "TRIGGER",
170
+ "MAINTAIN"
171
+ ]
172
+ }
173
+ ]
174
+ }
175
+ ```
176
+
177
+ Existing policy fields and structural/RLS expectations remain independent and compatible.
178
+ Omitting `privileges` preserves the previous behavior; an empty array adds no checks.
179
+ Each nonempty declaration needs at least one required or forbidden privilege. Omitted
180
+ lists mean empty, but explicit `null`, overlapping lists, unknown fields and unsupported
181
+ identifiers or privileges are rejected. At most 1,024 declarations are accepted.
182
+
183
+ | Kind | Object fields | Accepted privileges |
184
+ | ---------- | --------------------------------- | --------------------------------------------------------------------------------------- |
185
+ | `schema` | `schema`; no `name` | `USAGE`, `CREATE` |
186
+ | `table` | `schema`, `name` | `SELECT`, `INSERT`, `UPDATE`, `DELETE`, `TRUNCATE`, `REFERENCES`, `TRIGGER`, `MAINTAIN` |
187
+ | `sequence` | `schema`, `name` | `USAGE`, `SELECT`, `UPDATE` |
188
+ | `function` | `schema`, `name`, `argumentTypes` | `EXECUTE` |
189
+
190
+ Use exact uppercase privilege names and reviewed existing role/object names. Roles must
191
+ exist in `pg_roles`; declare an actual application role to check access inherited from
192
+ `PUBLIC`, not a standalone `PUBLIC` pseudo-role. Function checks cover ordinary functions,
193
+ not procedures, aggregates or window routines. A function declaration must include its
194
+ exact input types (including `INOUT`, excluding output-only arguments) as structured
195
+ catalog names, for example
196
+ `"argumentTypes": [{ "schema": "pg_catalog", "name": "uuid" }]`; `[]` selects a
197
+ zero-argument function. At most 32 arguments are supported. SQL signature strings and
198
+ non-`pg_catalog` argument types are not accepted; use a project-owned proof for unsupported
199
+ signatures rather than changing a function to fit the check.
200
+
201
+ Effective checks include inherited role permissions and grants through `PUBLIC`.
202
+ Required table access means a full-table privilege. Forbidden `SELECT`, `INSERT`,
203
+ `UPDATE` and `REFERENCES` also detect a privilege granted on only one column.
204
+ `MAINTAIN` is checked on PostgreSQL 17 and newer; below 17 it is unavailable, so a required
205
+ check fails and a forbidden check passes. Missing roles, objects, types or exact function
206
+ signatures fail rather than being interpreted as a successful denial.
207
+
208
+ These declarations do not inventory undeclared objects, prove RLS expressions or grant
209
+ options, change Data API exposure settings, or make a live HTTP request. Keep your
210
+ application's positive and negative API proof alongside them, including fresh setup and
211
+ replay coverage. A failed check needs a fresh reviewed migration; it is never an
212
+ automatic privilege escalation or a reason to use hosted administrator credentials.
213
+
69
214
  ## Identity policy
70
215
 
71
216
  The identity policy links one verified local Supabase identity to a copied pseudonymous
package/docs/stability.md CHANGED
@@ -1,11 +1,15 @@
1
- # Stability and the first stable release
1
+ # Stability and release verification
2
2
 
3
- Rehearsal core **1.0.0 is published** on
3
+ Rehearsal core **1.1.0 is published** on
4
4
  [npm](https://www.npmjs.com/package/@rehearsal-db/core). This page defines its
5
5
  compatibility promise and the verification policy for releases; it is not an
6
6
  unfinished launch checklist. Stable core does not require every planned database,
7
7
  editor interface, or paid feature.
8
8
 
9
+ The compatibility promise began with **1.0.0**. The separate
10
+ [VS Code Starter 0.1.23 preview](vscode.md) has its own editor acceptance and does
11
+ not turn a core release into a completed accessibility or provider walkthrough.
12
+
9
13
  ## What becomes a compatibility promise
10
14
 
11
15
  Starting with `1.0.0`, Rehearsal treats these documented surfaces as public:
@@ -65,7 +69,7 @@ ready. The sequence below is policy, not a claim that its checks have passed.
65
69
  corrected candidate. Reuse sufficient unchanged evidence with its original scope;
66
70
  do not relabel old or unavailable manual checks as new results. No minimum number
67
71
  of calendar days replaces these checks.
68
- 6. Publish `1.0.0` only when no blocking issue remains and the exact tarball has passed
72
+ 6. Publish the selected stable version only when no blocking issue remains and the exact tarball has passed
69
73
  the required release checks and consumer acceptance. The protected GitHub npm
70
74
  review is the single human authorization for its displayed exact version and
71
75
  checksum; no separate chat approval or pasted phrase is required.
@@ -94,7 +98,7 @@ baseline. Automatic compatible updates do not cross the 0.x-to-1.x version bound
94
98
  review the changelog and deliberately select the stable version:
95
99
 
96
100
  ```bash
97
- npm install --save-dev --save-exact @rehearsal-db/core@1.0.0
101
+ npm install --save-dev --save-exact @rehearsal-db/core@1.1.0
98
102
  npx --no-install rehearsal --version
99
103
  npx --no-install rehearsal doctor
100
104
  ```
package/docs/vscode.md CHANGED
@@ -4,9 +4,9 @@ The Rehearsal extension turns the local CLI into a project-aware database-change
4
4
  workbench. It is for developers who want the safety of exact migration plans and disposable
5
5
  local runtimes without memorizing every command.
6
6
 
7
- This guide targets **Free Starter 0.1.22**, named **Rehearsal DB** on
7
+ This guide targets **Free Starter 0.1.23**, named **Rehearsal DB** on
8
8
  [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=rehearsal-db.rehearsal-db),
9
- with a [reviewed GitHub VSIX](https://github.com/Ddupasquier/rehearsal-vscode/releases/tag/v0.1.22)
9
+ with a [reviewed GitHub VSIX](https://github.com/Ddupasquier/rehearsal-vscode/releases/tag/v0.1.23)
10
10
  for direct installation. The canonical extension ID is **`rehearsal-db.rehearsal-db`**,
11
11
  published by **`rehearsal-db`**. The workbench and commands retain the **Rehearsal** brand.
12
12
  All included features are free indefinitely for personal or commercial development.
@@ -44,8 +44,8 @@ boundaries, baselines, migration digests, runtime isolation, locks, rollback, an
44
44
  Use desktop VS Code 1.101 or newer on macOS or Linux and a supported Node.js release
45
45
  (22, 24, or 26). You do not need the extension source repository or Test Lab.
46
46
  For a Windows desktop, use the [WSL 2 Linux-workspace setup](getting-started.md#windows-use-a-wsl-2-linux-workspace).
47
- With editor 0.1.21, native Windows inspection and foreground actions refuse before launching a child
48
- process and directs you to Remote-WSL. Install the extension into the Linux extension
47
+ Since editor 0.1.21, native Windows inspection and foreground actions refuse before launching a child
48
+ process and direct you to Remote-WSL. Install the extension into the Linux extension
49
49
  host and keep the project on its Linux filesystem. WSL compatibility remains a preview;
50
50
  real WSL core fixtures do not certify a native Remote-WSL editor walkthrough.
51
51
 
@@ -66,7 +66,7 @@ real WSL core fixtures do not certify a native Remote-WSL editor walkthrough.
66
66
  4. Follow **Next best step**. Review the detected project and active configuration;
67
67
  setup, source access, migration execution and cleanup keep their own confirmations.
68
68
 
69
- The release pairing described here is core **1.0.2** with Starter **0.1.22**.
69
+ The release pairing described here is core **1.0.2** with Starter **0.1.23**.
70
70
  Install it once both versions appear in their official release channels.
71
71
  The extension's minimum core is `0.1.0-rc.4`. A project that is not ready should show
72
72
  setup guidance, not require you to build the extension. Opening the dashboard does not
@@ -92,10 +92,10 @@ distinction between desktop OS, extension host, architecture and actual database
92
92
  ### Direct GitHub installation
93
93
 
94
94
  The verified GitHub VSIX is an alternative to Marketplace. Open the
95
- [Starter 0.1.22 release](https://github.com/Ddupasquier/rehearsal-vscode/releases/tag/v0.1.22).
96
- Under **Assets**, download `rehearsal-db-0.1.22.vsix` and `SHA256SUMS`. In a terminal,
97
- run `shasum -a 256 /path/to/rehearsal-db-0.1.22.vsix` on macOS or
98
- `sha256sum /path/to/rehearsal-db-0.1.22.vsix` on Linux. Compare the result with that
95
+ [Starter 0.1.23 release](https://github.com/Ddupasquier/rehearsal-vscode/releases/tag/v0.1.23).
96
+ Under **Assets**, download `rehearsal-db-0.1.23.vsix` and `SHA256SUMS`. In a terminal,
97
+ run `shasum -a 256 /path/to/rehearsal-db-0.1.23.vsix` on macOS or
98
+ `sha256sum /path/to/rehearsal-db-0.1.23.vsix` on Linux. Compare the result with that
99
99
  release's `SHA256SUMS`; stop if they differ. Do not substitute a local rebuild.
100
100
  Then open **Extensions → … → Install from VSIX…** and choose the verified file.
101
101
  Save your work and reload only if VS Code asks.
@@ -147,6 +147,11 @@ reviewed change. Wait for **Saved**, then choose **Return to project**. Concurre
147
147
  refuse safely instead of overwriting someone else's work; a saved confirmation is tied
148
148
  to the exact bytes read back from disk, not merely a completed write request.
149
149
 
150
+ Invalid structured fields show supported values or numeric limits before review.
151
+ For example, retained environment count must be a whole number from 1 to 1000.
152
+ If a review becomes stale, close it, refresh Rehearsal, and review the current source;
153
+ saved and unsaved edits are not reverted. A refusal does not retry or reset a database.
154
+
150
155
  Commands reuse the same Rehearsal terminal for a project only after its previous command
151
156
  has a confirmed exit and its directory still matches. A running sandbox or manually
152
157
  entered foreground command is never interrupted to make room for another action. If
@@ -222,7 +227,7 @@ route; do not delete its coordination lock while a package manager may still be
222
227
 
223
228
  Manual updating stays available: download and verify the next reviewed VSIX, then repeat
224
229
  **Install from VSIX…**. See the release's
225
- [installation guide](https://github.com/Ddupasquier/rehearsal-vscode/blob/v0.1.22/README.md#install-and-open-your-project).
230
+ [installation guide](https://github.com/Ddupasquier/rehearsal-vscode/blob/v0.1.23/README.md#install-and-open-your-project).
226
231
  An ordinary core-package update is detected from its manifest, lockfile and installed
227
232
  metadata without reloading VS Code. The header shows loaded extension and core versions
228
233
  separately; installed → newest appears only after a successful release check. Unknown or
@@ -239,8 +244,8 @@ external links may use their own services.
239
244
 
240
245
  ## License, support and future Pro
241
246
 
242
- Starter 0.1.22 carries the same
243
- [free Starter license](https://github.com/Ddupasquier/rehearsal-vscode/blob/v0.1.22/LICENSE).
247
+ Starter 0.1.23 carries the same
248
+ [free Starter license](https://github.com/Ddupasquier/rehearsal-vscode/blob/v0.1.23/LICENSE).
244
249
  Its current review, configuration, comparison and reporting tools are included; they
245
250
  are not a timed evaluation or a paid feature gate. Free use does not mean the extension
246
251
  is MIT-licensed: its bundled terms govern copying and redistribution. Core remains MIT.
@@ -249,17 +254,17 @@ Future optional Pro editor workflows must be separately identified. They cannot
249
254
  retroactively remove Starter rights already granted for a received version. Pro pricing,
250
255
  billing and entitlements are not implemented; no payment is needed to use Starter.
251
256
  Database and safety capabilities stay in the free CLI. The
252
- [edition guide](https://github.com/Ddupasquier/rehearsal-vscode/blob/v0.1.22/docs/commercial-readiness.md)
257
+ [edition guide](https://github.com/Ddupasquier/rehearsal-vscode/blob/v0.1.23/docs/commercial-readiness.md)
253
258
  owns the detailed boundary.
254
259
 
255
260
  Preview describes stability, not a trial countdown. Support is best effort, with no
256
261
  service-level agreement, database hosting, backups or production support. Before use,
257
- read the release's [privacy statement](https://github.com/Ddupasquier/rehearsal-vscode/blob/v0.1.22/PRIVACY.md)
258
- and [support policy](https://github.com/Ddupasquier/rehearsal-vscode/blob/v0.1.22/SUPPORT.md).
262
+ read the release's [privacy statement](https://github.com/Ddupasquier/rehearsal-vscode/blob/v0.1.23/PRIVACY.md)
263
+ and [support policy](https://github.com/Ddupasquier/rehearsal-vscode/blob/v0.1.23/SUPPORT.md).
259
264
  Report editor problems in the
260
265
  [Rehearsal issue tracker](https://github.com/Ddupasquier/rehearsal-db/issues), as
261
266
  described in the support policy.
262
- Use its [private security process](https://github.com/Ddupasquier/rehearsal-vscode/blob/v0.1.22/SECURITY.md)
267
+ Use its [private security process](https://github.com/Ddupasquier/rehearsal-vscode/blob/v0.1.23/SECURITY.md)
263
268
  for vulnerabilities; never post credentials, private rows or baseline files publicly.
264
269
 
265
270
  Historical VSIX files retain their bundled terms. Choose a reviewed release carrying
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rehearsal-db/core",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "description": "Catch PostgreSQL and Supabase migrations that pass on empty databases but fail on real data—before deploy.",
6
6
  "repository": {