Skip to content

Commit decfe87

Browse files
committed
wip: changeset for the identity wire relay
Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2b0f81d commit decfe87

1 file changed

Lines changed: 31 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/client": minor
4+
"@objectstack/plugin-auth": minor
5+
---
6+
7+
The identity read routes now serve what `@objectstack/spec/identity` declares: `metadata` arrives DECODED on every organization route that reads the row back, and `updatedAt` is declared optional on `Organization` / `Member` / `Invitation` — the shape better-auth's own serializer documents (#18728).
8+
9+
Clause-②: yes (widening) — `updatedAt` moves from required to optional on three published schemas, so the set a consumer may hand to `OrganizationSchema` / `MemberSchema` / `InvitationSchema` grows by exactly one shape: the key being absent. Nothing previously admitted is refused, nothing is renamed, and no producer is required to write it. Contract-review tier.
10+
11+
Three published schemas could not parse a served response. `OrganizationSchema` declared `updatedAt` required and `metadata` an object; the four organization read routes (`setActive`, `get`, `delete`, `list`) carried no `updatedAt` at all and served `metadata` as the stored JSON text. `@objectstack/client` had recorded that as three 「not relayed」 notes rather than as a defect, and with zero in-repo consumers nothing went red — the audience was entirely external. Maintainer ruling C (batch #158 item 4) fixed the producer and made the one remaining key conditional on a measurement, which is what decided each half:
12+
13+
- **`metadata` is decoded at the producer, unconditionally** — it is our column. plugin-auth's data adapter decodes `sys_organization.metadata` out of its stored JSON text on its READ verbs, so all four routes serve the object the spec declares, and an unset column is OMITTED rather than sent as `null`. ⛔ The write verbs are deliberately untouched: better-auth's own organization adapter decodes the `create` / `update` echoes itself and discriminates on the value still being a string, so decoding there would fold the create echo's `metadata` to `undefined`. Both directions are pinned.
14+
- **`updatedAt` aligns to the documented wire** — ruling C's own fallback A, and its two conditions were measured against the installed better-auth 1.7.3 rather than assumed. The routes are better-auth's endpoints mounted through a single catch-all, each answering `ctx.json(...)` with no ObjectStack post-processing; and the vendor's `organization`, `member` and `invitation` models declare no `updatedAt` field, while its adapter factory's output transform iterates the declared fields only, so an undeclared column is dropped before any route sees it. Control, in the same file: the vendor's `team` and `organizationRole` models DO declare `updatedAt`, so the absence is a reading. For `member` and `invitation` there is additionally no column to serve — `sys_member` and `sys_invitation` are `managedBy: 'better-auth'`, the one disposition under which the platform injects no audit family, and neither declares `updated_at` itself.
15+
- **`@objectstack/client` relays the schemas.** `OrganizationWire` is the spec's `Organization`, `OrganizationMemberWire` is `Member`, and `OrganizationInvitationWire` is `Invitation` with `status` narrowed per route plus the three members the platform adds on top (`teamId` and the two ADR-0105 D8 placement fields, which the non-strict schema strips). The three 「not relayed」 notes are gone.
16+
- **The negative controls are the point.** "The client relays the spec schemas" and "the client stopped validating" look identical from a green positive test, so every accepted body is paired with a refused one — a required field genuinely missing, `metadata` still arriving as the stored JSON TEXT, and a `createdAt` or `updatedAt` present but not a datetime. `.optional()` widened the accept set by absence ONLY; a value that is there is still held to `z.string().datetime()`.
17+
18+
⚠️ **BREAKING** for a consumer that reads the organization wire's `metadata`, even though the level ships as `minor`: the repo's launch-window convention refuses `major` (`check-changeset-no-major`) and breaking-ness is carried by this banner plus the ADR-0087 disposition rather than by the level.
19+
20+
**FROM → TO**: `organization.metadata` was the stored JSON text and is now the decoded object.
21+
22+
```ts
23+
// before — the caller decoded it
24+
const meta = JSON.parse(org.metadata ?? '{}');
25+
// after — the producer decoded it; the key is ABSENT when unset
26+
const meta = org.metadata ?? {};
27+
```
28+
29+
`updatedAt` needs no migration in either direction: it was never on this family's wire, so no caller can have been reading a value, and the declaration now says so.
30+
31+
<!-- adr-0087: not-required (no-migration-prescription) Nothing authorable moves. No authored metadata property, object definition or accepted request shape changes its spelling, type or legality in a direction an author must act on: the only `packages/spec` edit makes `updatedAt` OPTIONAL on three identity wire schemas, which is pure widening — every document that parsed before still parses, byte for byte, so `objectstack migrate meta` has nothing to visit, `spec-changes.json` has nothing to project and the upgrade guide has no row to gain. These three schemas are not metadata types either: they are not in `DEFAULT_METADATA_TYPE_REGISTRY`, carry no authorable surface, and had zero in-repo consumers before this change. The other categories are closed on facts: all three packages publish to npm, declare no `private` and ship `dist` in `files[]` (not `unpublished`); no ADR-0087 id is minted in this diff (not `registered`) and none pre-dates the base that would cover it (not `already-registered`); and exported declarations DO change — `OrganizationWire` and `OrganizationMemberWire` become type aliases of the spec declarations and `OrganizationInvitationWire` becomes an intersection over one — so neither `runtime-interface-only` nor `type-surface-only` applies. plugin-auth's new module is package-internal and is NOT re-exported from its entry, so it adds no published declaration. The BREAKING banner above is carried rather than dropped, because the served wire shape of `@objectstack/plugin-auth` changes and `@objectstack/client`'s declaration of it changes with it. -->

0 commit comments

Comments
 (0)