You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Merge origin/main into claude/issue-13871-generate-codegen-ghost-field-types
Keeps the gate-family derivation and the verification on a tree that is at
origin/main, per scripts/pm/dispatch-gates.mjs's stale-tree refusal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
Restore the `field` key and the curated conflict sentence on the 409 body for an insert refused by a unique constraint
6
+
7
+
Since the ObjectQL engine began answering a driver's unique violation with its own `DUPLICATE_RECORD` envelope (`status: 409`, `object`, `field`, the driver error on `cause`), `POST /api/v1/data/:object` let that envelope leave `classifyDataError` through the generic declared-status passthrough: still 409, but with `code: 'DUPLICATE_RECORD'`, no `field`, and the engine's own sentence in `error`. The `isUniqueViolationError` arm that names the conflicting column (`field: 'email'`, "A record with this email already exists") was no longer reached for an insert conflict. Measured with the real engine and real drivers: on `driver-sqlite-wasm` the `field` key disappeared from every conflict on a single-column index; on `driver-memory` the sentence changed.
8
+
9
+
A dedicated arm for the engine's envelope now sits with the other structured 409s (`DELETE_RESTRICTED`, `CONCURRENT_UPDATE`), ahead of the passthrough:
10
+
11
+
-`code` stays `UNIQUE_VIOLATION` — the code every client branching on this conflict already reads.
12
+
-`field` is restored whenever the dialect determinably named the column (SQLite, Postgres); composite keys and index-naming dialects (MySQL) carry no `field` key, exactly as before.
13
+
-`error` is the curated end-user sentence again; the engine's own sentence rides on `developerMessage`, the same split the `DELETE_RESTRICTED` body uses.
14
+
- The body still quotes nothing the driver said — no offending value, no statement, no index name — including on `driver-memory`, whose raw refusal used to echo the offending values as JSON through the passthrough.
15
+
16
+
`patch`: a restoration of the shipped body's keys and wording; the wire `code` and the status are unchanged. The arm fires for the engine's envelope only; a plugin or sandbox body that throws the registered `DUPLICATE_RECORD` itself keeps the answer it gets today.
SCIM provisioning multi-writes now run inside one engine transaction, as the adapter's `#3653` scoping note already declared. On `@better-auth/scim` 1.7.2 the SCIM request scope was stamped with `AsyncLocalStorage.enterWith` inside the `verifyBearerToken` callback and was not observed at write time (measured: zero `engine.transaction` calls across `POST /scim/v2/Users` and `PATCH /scim/v2/Users/{id}`), so `sys_user`, `sys_scim_subject` and `sys_scim_user` landed as separate autocommits, and a refused deactivation left the SCIM resource reporting `active: false` for an account that was still enabled. `AuthManager.handleRequest` now opens the scope with `run(...)` around every request under `/scim/v2` — exactly as narrow as before; non-SCIM better-auth flows keep their sequential posture. A refused last-administrator deactivation now rolls the vendor's own `scimUser.active = false` write back, so the SCIM resource keeps reading `active: true`. The pin the #14360 suite held on that residual (`scim-deactivation-reconcile-user.test.ts`, face (c)) is flipped from `false` to `true` deliberately with this change, and a new runtime pin (`scim-transaction-scope.test.ts`) observes each SCIM mutation calling `engine.transaction`.
Copy file name to clipboardExpand all lines: .github/workflows/cut-rc.yml
+6-6Lines changed: 6 additions & 6 deletions
Original file line number
Diff line number
Diff line change
@@ -866,11 +866,11 @@ jobs:
866
866
echo "@objectstack/cli@$(jq -r '.version' packages/cli/package.json) is on main at ${LOCAL}."
867
867
868
868
# Pre-publish gate #2035, kept in step with release.yml's publish job rather
869
-
# than dropped for speed. In pre mode (which the guard step proved is active,
870
-
#so ALWAYS on this lane) the #3600 amendment makes it advisory: a major
871
-
#train exists to ship deliberate surface removals, and a migrated hotcrm
872
-
#cannot exist until the rc artifacts it would migrate against are published.
873
-
#It reports; it cannot block.
869
+
# than dropped for speed. This step is advisory by construction — it has no
870
+
#BLOCKING switch — because this lane only ever runs in pre mode (the guard
871
+
#step above proved it active). It reports; it cannot block. For why the
872
+
#equivalent gate in release.yml is currently advisory too, and what re-arms
873
+
#it, see that step's own comment.
874
874
- name: Downstream backward-compat smoke (live hotcrm, advisory in pre mode)
875
875
if: ${{ !inputs.dry_run }}
876
876
env:
@@ -879,7 +879,7 @@ jobs:
879
879
if bash scripts/downstream-smoke.sh; then
880
880
echo "::notice::hotcrm@${HOTCRM_REF} is still compatible with the pre-release train."
881
881
else
882
-
echo "::warning::hotcrm@${HOTCRM_REF} is incompatible with the pre-release train — expected for this window's deliberate removals. Ship a migrated hotcrm releaseand bump HOTCRM_REF in release.yml before 'changeset pre exit' re-arms that gate."
882
+
echo "::warning::hotcrm@${HOTCRM_REF} is incompatible with the pre-release train — expected for this window's deliberate removals. Ship a migrated hotcrm release, bump it, and set BLOCKING=1 in .github/workflows/release.yml to re-arm this gate."
Copy file name to clipboardExpand all lines: content/docs/references/api/events.mdx
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -57,6 +57,7 @@ const result = BulkDataEventSchema.parse(data);
57
57
|**type**|`Enum<'data.record.created' \| 'data.record.updated' \| 'data.record.deleted'>`| ✅ | Event type |
58
58
|**object**|`string`| ✅ | Object name |
59
59
|**recordId**|`string`| ✅ | Record ID |
60
+
|**organizationId**|`string`| optional | Organization the record belongs to (its organization_id), so a tenant-scoped consumer can discriminate the event's tenant without reading the record body. Absent when the record belongs to no organization: every event on a single-posture deployment (no organization wall, nothing stamps the column), and a row that carries no organization under a walled posture (environment-wide, or an object outside the wall) — read absence as "not behind any organization wall", never as "unknown". Present = exactly that organization; never fabricated, and the empty string is refused. |
0 commit comments