Tags · prisma/orm · GitHub
Skip to content

Tags: prisma/orm

Tags

v8.0.0-rc.8-dev.13

Toggle v8.0.0-rc.8-dev.13's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(target-postgres): avoid uncast array_position for pg.enum ORDER BY (

#30191)

## Summary

`ORDER BY` on a column typed `pg.enum(...)` rendered as
`array_position(ARRAY[...]::text[], "col")` with no cast on the column
argument. Against a native Postgres enum column that is rejected
outright:

```
42883: function array_position(text[], "TicketStatus") does not exist
```

So ordering by any enum-restricted column failed at runtime on rc.8.
`db.sql` failed identically — it is the SQL renderer, not the ORM
surface, so dropping to the SQL builder was not a workaround.

Fixes #30163

## The fix

Gate the `array_position` declaration-order rewrite off for native
enums, rather than casting the column argument to `text`.

A native Postgres enum already sorts by declaration order under a plain
`ORDER BY` — Postgres orders enum values by `pg_enum.enumsortorder` — so
the rewrite is redundant there. It exists for value-sets backed by
`text`/`varchar` columns with a CHECK constraint, which would otherwise
sort alphabetically. Those are unaffected: they carry `pg/text@1`, the
gate is inert for them, and the existing declaration-order suite passes
7/7 unchanged.

The alternative was casting the column inside `array_position`. That
also works — it preserves declaration order, it does not sort
alphabetically — but it keeps a per-row function call that defeats a
plain index on the column, and it leaves the renderer unable to
distinguish "needs sort-order emulation" from "the database already
sorts this correctly". Gating also agrees with `renderWhere`, which
renders comparisons on the raw column, so a keyset/cursor predicate
already compares by enum ordering where `array_position` did not.

## Why it is safe

Contract declaration order and `pg_enum.enumsortorder` are kept
identical by the migration planner: it can only append a value (`ALTER
TYPE … ADD VALUE`, no `BEFORE`/`AFTER`) and refuses any other member
change — rename, removal, or reorder — via
`nativeEnumMemberChangeRefusal`. If that refusal is ever relaxed to
permit reordering, this gate has to be revisited.

The gate keys on `codecId`, not `nativeType`. A hand-authored contract
carrying a `pg/text@1` codec over a column whose adopted physical type
happens to be a native enum would not be caught — reachable only by
hand-adopting an existing enum type as text, not by anything
`pg.enum(...)` authoring produces.

## Interaction with #30099

#30099 ("enum ORDER BY / DISTINCT ON loses declaration order behind a
derived table") touches the same function. **This PR should land first**
— it is ~10 lines against a hard runtime error on a published release,
while #30099 is larger and still in review.

#30099 deletes `TableSourceCoordinate` / `collectTableSources` and both
resolver functions here, replacing them with
`resolveColumnValueSetFromSource(source, column, contract)` returning `{
found, values }`. On rebase, drop both call sites of
`sortsByDeclarationOrderNatively` and call it once instead, in that PR's
`table-source` branch, immediately after `storageColumn` is resolved:

```ts
if (sortsByDeclarationOrderNatively(storageColumn)) return { found: true, values: undefined };
```

`found: true`, not `false` — the column exists, it is simply not
rewritten, and the identifier resolver's ambiguity counter depends on
that distinction. That single site also covers #30099's new
derived-table recursion, which this PR's two call sites do not reach.
Re-inserting the gate at the two old call sites instead would pass the
tests here but leave a native-enum column behind a
`distinct()`/`groupBy()` wrap as a new, untested 42883.

## Testing


`test/integration/test/ports/prisma/functional/issues-30163-enum-order-by`
— an ORM-level port test. The harness pushes the contract through the
plan → apply path (no hand-written DDL), then seeds rows and queries
through the public facade:

```ts
await db.public.Ticket.orderBy([(t) => t.status.asc(), (t) => t.id.asc()])
  .select('id', 'status')
  .all();
```

Declaration order is `open, closed`, so alphabetical ordering is
distinguishable from a correct sort. Ascending and descending are both
asserted on the whole result shape. Against the renderer on `main` both
cases fail with `42883`; with the fix both pass.

The existing text-backed value-set suite
(`order-by-enum.integration.test.ts`) passes 7/7 unchanged, and the
postgres adapter suite is green at 866 passed / 3 expected-fail.

## Release note

`docs/releases/v8.0.0-rc.9.md` does not exist yet; the entry follows
once it does, matching the precedent set by #30099 for rc.5.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01UcqoY3CKfnubdZt5YQk2Rq


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
- Fixed PostgreSQL native enum sorting so ascending and descending order
follow the enum’s declared value order.
- Prevented runtime errors when ordering columns backed by native
PostgreSQL enums.
- Improved deterministic results when multiple records share the same
enum value.
- Corrected `distinctOn` behavior to return one record for each enum
value.
- Ensured native enum queries behave consistently across supported
ordering scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Co-authored-by: Steven McClankerton <tatarintsev@prisma.io>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

v8.0.0-rc.8-dev.12

Toggle v8.0.0-rc.8-dev.12's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(schema-ir): match int8 decimal-text defaults against their safe-i…

…nteger number (#30194)

## Summary

A literal `@default` on an `int8` column made `db migrate` fail its own
post-apply verification with `MIGRATION.SCHEMA_VERIFY_FAILED`. An `int4`
column with the same default verified fine.

Fixes #30174

## The true root cause

`parsePostgresDefault`'s `isBigInt` branch
(`packages/3-targets/3-targets/postgres/src/core/default-normalizer.ts:225-228`)
returns decimal text for *every* `int8` default, regardless of DDL
spelling. So even a bare `DEFAULT 0` introspects back as `"0"` (string)
against the contract's `0` (number) for `pg/int8number@1`. It is a
string/number type asymmetry, not a syntax one.

Postgres does **not** re-derive a quoted cast for small `bigint`
defaults: `bigint DEFAULT 0` comes back as `0` — bare within `int4`
range, quoted+cast only beyond it. Verified on PostgreSQL 17.11 and
PGlite 18.3, byte-identical:

```
bigint DEFAULT 0                              -> 0
bigint DEFAULT 9223372036854775807            -> '9223372036854775807'::bigint
```

The `'0'::int8` our own DDL renders is ours, not Postgres's:
`pgRenderDdlColumnDefault` runs the value through `codec.encode`,
`pgInt8NumberEncode` returns a string, and `pgInlineLiteral` quotes and
casts any string wire.

## Why the fix cannot live on the render path or in
`postgresResolveDefault`

`postgresResolveDefault`'s output becomes `resolvedDefault`
(`packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts:127-129`),
which `columnLike()`
(`packages/3-targets/3-targets/postgres/src/core/migrations/column-ddl-rendering.ts:56`)
maps into the `StorageColumn.default` slot that reaches
`codec.decodeJson`
(`packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts:1770`).
*"One differ drives both verify and plan"*
(`packages/3-targets/3-targets/postgres/src/core/migrations/diff-database-schema.ts:214`).

The two codecs bound to the same `resolvedNativeType` are mutually
incompatible: `PgInt8NumberCodec.decodeJson` requires a JSON number
(`codecs.ts:723-725` — its own doc comment calls this "the deliberate
exception to the decimal-text rule ... and the codec's purpose"), while
`PgInt8Codec.decodeJson` requires a decimal string
(`codecs.ts:660-668`). Introspection has no codec identity to consult,
so it cannot pick a shape per column, and any normalization performed on
the *shared* `resolvedDefault` before it reaches DDL rendering corrupts
one codec or the other. Confirmed with three live-database probes:

```
PROBE A  int8number + decimal-TEXT resolvedDefault (proposed normalize-to-string fix) -> pg/int8number@1 must be a number
PROBE B  int8@1     + NUMBER       resolvedDefault (the inverse: normalize-to-number)  -> pg/int8@1 must be a decimal string
PROBE C  int8number + NUMBER       resolvedDefault (this PR's fix: unchanged)          -> ok, stage execute, no failure
```

## What the fix is

One spelling-gated branch in the module-private `normalizeLiteralValue`
(`packages/2-sql/1-core/schema-ir/src/ir/resolved-default-equality.ts`),
beside the existing temporal-normalization branch: a safe-integer number
is compared against the decimal text it denotes, only under an
`int8`/`bigint` native type. It **normalizes rather than widens**:
`literalValuesEqual` is byte-for-byte unchanged, and
`resolvedDefaultsEqual`'s signature is unchanged. The normalized value
lives for the one comparison call and never reaches `resolvedDefault` or
the DDL renderer, so it cannot corrupt the codec-typed value
`pgRenderDdlColumnDefault` depends on.

## Soundness

`String(n)` is injective over safe integers, so no two distinct values
collide. `pgInt8NumberGuard`
(`packages/3-targets/3-targets/postgres/src/core/codec-helpers.ts:158-169`)
already rejects everything outside `Number.isSafeInteger` at both encode
and decode, so the gate this fix adds is exactly coextensive with the
codec's own domain and can produce no reachable false negative.

## On the reporter's claim

He is right that `BigInt` and `BigIntNumber` produce byte-identical DDL.
What's wrong is only the inference that the codec family causes it:
`pg/int8@1` carries `"value": "0"` and round-trips text→text;
`pg/int8number@1` carries `"value": 0` and is the sole reproducer.

## The layering residue, stated honestly

`int8` is a Postgres alias for the standard SQL `bigint`, and this
shared SQL-core package now names it. `isTemporalNativeType` directly
above already matches `timestamptz` the same way, and `bigint` itself is
standard SQL accepted by MySQL/MSSQL/SQLite. The clean fix is a
comparison-only target hook — see follow-up below.

## Testing

- `schema-ir`: 264 passed
- `family-sql` (`packages/2-sql/9-family`): 340 passed
- `adapter-postgres`: 867 passed, 3 expected-fail, 0 failed
- End-to-end on real PostgreSQL 17.11: applied and verified the
reporter's model plus a `Number.MAX_SAFE_INTEGER`-adjacent `int8`
default with no precision loss
- Unit coverage: the safe-integer match in both operand orders, a
negative integer, a genuine mismatch, no effect without an
`int8`/`bigint` native type, a rounded-number-vs-exact-text rejection,
an outside-safe-integer-range rejection (even when the text is exact),
and two huge decimal-text strings compared by identity

## Follow-ups (not opened as issues; not fixed in this PR)

- **Comparison-only normalization seam.** `DefaultResolver`
(`packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts:62-71`)
cannot carry this normalization because its output is dual-purpose
(verify *and* plan/DDL). The proper home is a new comparison-only target
hook alongside `DefaultResolver` / `NativeTypeExpander` /
`DefaultRenderer`, so this equality file needs no `int8`/`bigint`
spelling at all. That's an architecture change (Ask First) beyond this
bug fix.
- **`int8`-family list-default crash.** `BigInt[] @default([1,2])` ->
`CLI.UNEXPECTED: pg/int8@1 database JSON value must be a decimal
string`; `BigIntNumber[] @default([1,2])` -> `... must be a number`.
Cause: `pgRenderDdlColumnDefault` (`control-adapter.ts:1770`) hands the
whole array to `codec.decodeJson` instead of decoding per element.
Pre-existing, out of scope, and the same codec-shape constraint this fix
navigates — the reporter's money model has 22 defaulted `int8` columns.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01UcqoY3CKfnubdZt5YQk2Rq

Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Co-authored-by: Steven McClankerton <tatarintsev@prisma.io>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

v8.0.0-rc.8-dev.11

Toggle v8.0.0-rc.8-dev.11's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(cli): use "Prisma ORM" for user-visible CLI strings (#30192)

## Summary

User-visible CLI surfaces still said "Prisma Next" after the rename.
This changes them to **"Prisma ORM"**, which is version-independent —
these strings do not need revisiting at each major release.

Fixes #30062

## Surfaces changed

- `orm init` command summary and its bug-report string
- `lsp` command summary and description
- The scaffolded quick-reference templates (postgres + mongo)
- The scaffolded `README.md` templates (postgres + mongo)
- The telemetry first-run consent notice — the first branded sentence a
new user sees
- The three config-validation diagnostics (`No Prisma ORM configuration
was loaded`, etc.)
- The two install-fallback warnings printed during `orm init`
- The remaining internal doc comments in `cli/src`, so the source uses
one name throughout

After this change there is no occurrence of "Prisma Next" anywhere in
`packages/1-framework/3-tooling/cli/src`.

## Deliberately unchanged

- `QUICK_REFERENCE_FILE = 'prisma-next.md'` and every reference to it
*by filename*. This is covered by the standing allowance at
`scripts/lint-legacy-name.mjs:60`, which names the file specifically and
tracks its removal to **ROADMAP § 3**. The filename/greeting mismatch is
a known, tracked interim state.
- `TELEMETRY_DOCS_URL` and the `prisma-next.dev` URLs — live
infrastructure, not brand strings.
- `PRISMA_NEXT_DISABLE_TELEMETRY` — renaming it would break existing
opt-outs.
- The `// use prisma-next` schema directive — a language construct, same
allowance.
- Legacy skill slugs in `skill-sources.ts`.

The issue's fourth complaint (skills labeled "Prisma Next agent-skill")
is **moot**: `orm init` no longer installs skills at all
(`commands/init/skill-sources.ts:1-5`).

## Follow-ups, not in this PR

- Runtime error strings in the target packs still say "Prisma Next":
`packages/3-targets/3-targets/postgres/src/core/migrations/runner.ts:322`,
`issue-planner.ts:516`,
`packages/3-targets/3-targets/sqlite/src/core/migrations/runner.ts:335`.
A different surface, not raised by the issue.
- Package READMEs and `package.json` descriptions across
`packages/3-targets/**` and `packages/3-extensions/**` — explicitly
deferred by the issue ("internal package names, repo docs … can lag").

## Testing

Verified against the built bin rather than the diff: all help screens
render "Prisma ORM" and contain no "Prisma Next"; a real `orm init`
scaffold produces 11 "Prisma ORM" occurrences across `README.md` and
`prisma-next.md` and zero legacy ones.

- CLI package suite: 115 files, 1436 tests, green
- Init-related integration tests (`cli.init-templates`,
`cli.init-facade-imports`, `cli.init-skill-distribution`,
`cli.config-section-requirements`, `cli.bin-smoke`): 5 files, 37 tests,
green
- `node scripts/lint-legacy-name.mjs`: clean
- `pnpm lint` and `pnpm typecheck`: clean

Per review feedback, the two rename-assertion tests added earlier were
removed rather than adjusted — they only asserted the absence of strings
this PR deletes, which is not coverage worth carrying. The existing
template snapshots already pin the scaffolded output.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01UcqoY3CKfnubdZt5YQk2Rq


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Updated CLI help text, diagnostics, telemetry notices, and generated
MongoDB/PostgreSQL templates to consistently use “Prisma ORM” branding.
* Refreshed related setup guidance, sample descriptions, headings, and
references across generated documentation.
* Clarified terminology in control API and ORM configuration
documentation.
* **Tests**
* Updated diagnostic expectations to reflect the “Prisma ORM”
terminology.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Co-authored-by: Steven McClankerton <tatarintsev@prisma.io>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

v8.0.0-rc.8-dev.10

Toggle v8.0.0-rc.8-dev.10's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
ci: fix the prisma-cli publish notification (event type, repo name) (#…

…30179)

## Linked issue

n/a — small change; fallout from the repository rename (prisma/prisma →
prisma/orm).

## Summary

The publish workflow's "Notify prisma-cli" step never reached its
consumer. It sent `event_type: family-published`, but prisma-cli's
`update-product-versions.yml` triggers on `product-published`, so the
auto-repin only ever ran from its daily scheduled backstop. The payload
also still named this repo `prisma/prisma`. The step now sends
`product-published` with `repo: prisma/orm`.

## Testing performed

- Verified the receiving side: `prisma/prisma-cli`
`.github/workflows/update-product-versions.yml` triggers on
`repository_dispatch: types: [product-published]` and reads nothing from
the payload, so the `repo` field is informational.
- The diff changes only characters inside an existing quoted scalar; no
YAML structure changed.

## Skill update

n/a — internal only (CI workflow; no user-facing surface).

## Checklist

- [x] All commits are signed off (`git commit -s`) per the
[DCO](../CONTRIBUTING.md#developer-certificate-of-origin-dco). The DCO
status check will block merge if any commit is missing a
`Signed-off-by:` trailer.
- [x] I read [CONTRIBUTING.md](../CONTRIBUTING.md) and the change is
scoped to one logical concern.
- [x] Tests are updated (n/a — CI workflow change, no test surface).
- [ ] The PR title is in `TML-NNNN: <sentence-case title>` form — no
Linear ticket exists for this rename fallout; the title follows the
repo's convention for such chores (cf. #30145).
- [x] The **Skill update** section above is filled in.

## Notes for the reviewer

- The event-type mismatch predates the rename. prisma-cli's consumer
workflow was created on 2026-08-17 listening for `product-published`
(prisma/prisma-cli#192); this sender, added on 2026-08-13, was never
updated. The daily cron masked the dead letter — a lost dispatch delays
the repin by at most a day, which is why nothing visibly broke.
- Companion PRs update the other side of the rename: `prisma/orm` `v7`
branch (`github.repository` guards), `prisma/engines-wrapper` (workflow
dispatch target), and `prisma/prisma-engines` (Makefile clone URL).


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated the publication notification event type to
`product-published`.
* Repository and version information in the notification remain
unchanged.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: Oleksii Orlenko <robot@aqrln.net>

v8.0.0-rc.8-dev.9

Toggle v8.0.0-rc.8-dev.9's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
TML-3199: Docs hygiene — dead links, stale API instructions, and the …

…payload label (#30016)

# Docs hygiene: dead links, stale API instructions, and the payload
label

Main-based cleanup (not part of the raw-SQL stack, though its items were
surfaced by that campaign's reviews).

## What changed

- **ADR-INDEX's dead ADR 035 link** — a pure capitalization mismatch
(`Dual authoring conflict resolution` vs the file's `Dual Authoring
Conflict Resolution`); corrected to the file's actual name.
- **`error-reference.md`: "Meta:" → "Payload:" (260 entries) + one
preamble sentence.** The old label was wrong for half the file:
`structuredError()` writes `error.meta` but `runtimeError()` writes
`error.details`, and a scripted classification showed a per-code label
is ill-defined — of the codes classifiable at all, eight are raised
through *both* constructors. The neutral label plus a preamble stating
the rule (meta from structuredError, details from runtimeError, some
codes both ways) is accurate today and stays accurate as raise sites
move. No tooling reads the label (verified against
`list-error-codes.mjs`).
- **Stale `validateContract<Contract>(contractJson)` instruction removed
from four surfaces** (`AGENTS.md` § Key Patterns, Testing Guide ×3, the
Runtime subsystem doc, and the `typed-contract-in-tests` rulecard) — no
such export exists, and the stale pattern had already generated a false
review finding. Replacements verified against current code: the client
factory hydrates (`postgres<Contract>({ contractJson, url })`), and
tests use `validateSqlContractFully<Contract>(contractJson)` (the idiom
with 174 current usages). The `validateContract` in
`family-instance-domain-actions` is deliberately untouched — that one is
the real ADR 204 control-plane primitive, a different thing sharing the
name.
- **ADR 012's refs clause** now states ADR 205's own conclusion: the
unindexed-predicate lint and refs-based budget heuristic ran off the
removed sidecar and no longer run for any plan. (The previous wording
invited a hunt for a `meta.refs` field that no longer exists.)
- **One ticketed item needed nothing**: the four "dead" source links in
the Runtime & Middleware doc were already fixed upstream — verified
resolving, left alone.

## Known merge note

This PR and the raw-SQL stack (#29997) both edit the tail of the same
ADR 012 update note, for different reasons. The conflict is one line but
**semantic**: whichever lands second must carry both intents (the stack
scopes the wire-level-rows claim as historical; this states the refs
heuristics gone). Taking either side wholesale silently drops the other.

Out of scope, ticketed: 68 further dead links across `docs/` + the
missing link checker, two orphaned error-reference entries the
one-directional checker cannot see, and ADR 205's own upstream ambiguity
(all on TML-3211).

Refs: TML-3199

https://claude.ai/code/session_01NnNjsNcPMtbJZhnZz5Zzbe


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Updated contract hydration and validation guidance to reflect the
current workflow.
* Refreshed testing and runtime examples for standalone contract usage.
* Corrected architecture decision record titles, links, and descriptions
of removed raw-plan metadata.
* Clarified that contract data can be passed directly through runtime
setup.

* **Tests**
* Updated typed contract fixture guidance to use full SQL contract
validation for parsed contract data.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Oleksii Orlenko <robot@aqrln.net>

v8.0.0-rc.8-dev.8

Toggle v8.0.0-rc.8-dev.8's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(skills): jj-aware artifact guard and v2-named schema helpers (TML…

…-3223) (#30089)

## Linked issue

Refs TML-3223 part 2 — the skills audit's script findings.

## Summary

Two script-level findings from the audit, independent of the docs PR
(#30088).

**The artifact guard could not run in the repo that ships it.**
`guard-review-artifacts-ignored.mjs` shells out to `git rev-parse`, so
in a non-colocated Jujutsu workspace it failed before checking anything
— and the review-fetch and review-triage phases both gate on it, so both
were blocked here. Git stays the primary path, unchanged and exact. When
git cannot answer, the guard walks up to the `.jj` directory and accepts
an artifact directory under that workspace root's `wip/` tree, which
`.gitignore` covers wholesale; anything outside it is refused.

Demonstrated in this workspace, both states:

```
$ node …/guard-review-artifacts-ignored.mjs --dir wip/reviews/x
ok: review artifacts are under the ignored wip/ tree: wip/reviews/x     # exit 0

$ node …/guard-review-artifacts-ignored.mjs --dir docs
error: without git, review artifacts must live under the ignored wip/ tree: …/wip   # exit 1
```

**The schema helpers were named for the version they no longer
enforce.** `normalizeReviewStateV1` / `assertReviewStateV1` check
`version: 2`. Renamed to `…V2` across the definition and its six call
sites; `rg ReviewStateV1` returns nothing.

Both SKILL.md files that describe the guard as a git check now describe
both paths.

## Testing performed

`pnpm test:scripts` (green — 0 failures), `pnpm lint:skills` (green),
plus the guard demonstration above.

## Skill update

This PR is skill maintenance: one guard script, one schema module and
its consumers, and the two SKILL.md sentences that describe them.

## Notes for the reviewer

The rename is pure — no behavior change, no signature change. The
guard's git path is byte-identical; everything new sits behind the
branch git previously threw from.

https://claude.ai/code/session_01NnNjsNcPMtbJZhnZz5Zzbe


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added safe review-artifact validation for Jujutsu workspaces without a
Git directory.
* Artifacts can be stored under the workspace’s ignored `wip/`
directory, while unsafe paths and symlink escapes are rejected.

* **Improvements**
* Updated review-state processing to use the current schema across
workflows.
* Preserved existing Git-based safety checks and command-line behavior.

* **Tests**
* Added coverage for valid, missing, boundary-violating, and unsafe
workspace artifact paths.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Oleksii Orlenko <robot@aqrln.net>