Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .changeset/identity-api-key-schema-retired.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
"@objectstack/spec": minor
---

feat(spec): retire `ApiKeySchema` — the identity module no longer publishes a second, fictional declaration of `sys_api_key` (#8715, ADR-0049)

<!-- adr-0087: registered identity-api-key-schema-retired -->

**BREAKING** public-surface removal, landing after the v17.0.0 cut (the
lockstep launch-window convention ships it as `minor`; the migration
prescription is registered under protocol major 18, where `os migrate meta`
users will look — the #8586 precedent).

`ApiKeySchema` (and its `ApiKey` / `ApiKeyParsed` types) documented
better-auth's `apiKey` **plugin** schema — a plugin this platform does not
load: `start` and `lastRefetchAt` name columns that do not exist; `enabled`
inverts the real `revoked` column's polarity; `rateLimitEnabled` /
`rateLimitTimeWindow` / `rateLimitMax` / `remaining` advertise a per-key
rate-limit capability nothing implements; `permissions` and `metadata` have no
columns; `organizationId` is camelCase fiction next to the real snake_case
`active_organization_id`. Zero consumers anywhere in the monorepo outside its
own unit test — one table had two declarations, and the published one was
fiction (maintainer-ruled DELETE, 2026-08-15).

**What breaks:** `import { ApiKeySchema, ApiKey, ApiKeyParsed }` from
`@objectstack/spec` or `@objectstack/spec/identity` is TS2305 after upgrade.
The generated reference page's `ApiKey` section and the 19
`identity/ApiKey:*` authorable-surface keys disappear with the schema.

**What stays:** everything real. The single declaration of `sys_api_key` is
the ObjectSchema in `@objectstack/platform-objects`
(`identity/sys-api-key.object.ts`) — columns `name, prefix, user_id,
active_organization_id, scopes, expires_at, last_used_at, revoked, key, id,
created_at, updated_at`; rows are minted by `POST /api/v1/keys` and verified
by `core/src/security/api-key.ts`, keyed by the `osk_` prefix. Neither ever
read the deleted schema, so runtime behaviour is byte-identical.
`UserSchema` / `AccountSchema` / `VerificationTokenSchema` and the
organization module survive unchanged.

The retirement kit:

- schema deleted in place, with the in-module explanatory block naming the
live declaration (`packages/spec/src/identity/identity.zod.ts`)
- ADR-0087 registration: retired-def entry `identity/ApiKey` + D3 semantic
entry `identity-api-key-schema-retired`, both under protocol 18 (route 3 —
no carrier key and no authored document, so no tombstone and no D2
conversion; the registry entries ARE the declaration)
- pin tests: `identity/api-key-retirement.test.ts` (zero holders on every
public entry, survivors stand) and platform-objects'
`sys-api-key-single-declaration.test.ts` (the real column set, spec's
runtime namespace lost the name)
- generated baselines regenerated: authorable surface (−19 keys), JSON-schema
manifest (−1 def), api-surface / export-origins (−3 names), reference docs
- `cloud/developer-portal.zod.ts` prose corrected: marketplace API keys point
at the `sys_api_key` object and `POST /api/v1/keys`, not at
`Identity.ApiKeySchema` (the marketplace-key plan is ruled not live)

## FROM → TO

```ts
// before — type-checked green against a schema no runtime ever read
import { ApiKeySchema, type ApiKey } from '@objectstack/spec/identity';
const key: ApiKey = { id, name, userId, enabled: true, rateLimitMax: 100, /* … */ };

// after — read the real table: the sys_api_key ObjectSchema in
// @objectstack/platform-objects (snake_case, `revoked` not `enabled`);
// mint via POST /api/v1/keys, verify via core/src/security/api-key.ts.
import { SysApiKey } from '@objectstack/platform-objects';
```
10 changes: 8 additions & 2 deletions content/docs/references/cloud/developer-portal.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@ Identity module (`@objectstack/spec` Identity namespace), which follows the
better-auth specification. This module only defines marketplace-specific
extensions on top of the shared identity layer:

- **User & Session** → `Identity.UserSchema`, `Identity.SessionSchema`
- **User** → `Identity.UserSchema`; the session wire shape is `Session` from
`@objectstack/spec/api` (the identity module deliberately declares no bare
`SessionSchema` — #4641)
- **Organization & Members** → `Identity.OrganizationSchema`, `Identity.MemberSchema`
- **API Keys** → `Identity.ApiKeySchema` (with marketplace scopes)
- **API Keys** → the `sys_api_key` ObjectSchema in
`@objectstack/platform-objects` (`identity/sys-api-key.object.ts`), minted via
`POST /api/v1/keys`; its `scopes` column is the marketplace-scope carrier.
There is no `Identity.ApiKeySchema` — #8715 retired it (it documented
better-auth's apiKey plugin, which this platform does not load)

## Key Concepts
- **Publisher Profile**: Links an Identity Organization to a marketplace publisher
Expand Down
33 changes: 2 additions & 31 deletions content/docs/references/identity/identity.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ defines "how to login".
## TypeScript Usage

```typescript
import { AccountSchema, ApiKeySchema, UserSchema, VerificationTokenSchema } from '@objectstack/spec/identity';
import type { Account, ApiKey, User, VerificationToken } from '@objectstack/spec/identity';
import { AccountSchema, UserSchema, VerificationTokenSchema } from '@objectstack/spec/identity';
import type { Account, User, VerificationToken } from '@objectstack/spec/identity';

// Validate data
const result = AccountSchema.parse(data);
Expand Down Expand Up @@ -51,35 +51,6 @@ const result = AccountSchema.parse(data);
| **updatedAt** | `string` | ✅ | Last update timestamp |


---

## ApiKey

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **id** | `string` | ✅ | API key identifier |
| **name** | `string` | ✅ | API key display name |
| **start** | `string` | optional | Key prefix for identification |
| **prefix** | `string` | optional | Custom key prefix |
| **userId** | `string` | ✅ | Owner user ID |
| **organizationId** | `string` | optional | Scoped organization ID |
| **expiresAt** | `string` | optional | Expiration timestamp |
| **createdAt** | `string` | ✅ | Creation timestamp |
| **updatedAt** | `string` | ✅ | Last update timestamp |
| **lastUsedAt** | `string` | optional | Last used timestamp |
| **lastRefetchAt** | `string` | optional | Last refetch timestamp |
| **enabled** | `boolean` | ✅ | Whether the key is active |
| **rateLimitEnabled** | `boolean` | optional | Whether rate limiting is enabled |
| **rateLimitTimeWindow** | `integer` | optional | Rate limit window (ms) |
| **rateLimitMax** | `integer` | optional | Max requests per window |
| **remaining** | `integer` | optional | Remaining requests |
| **permissions** | `Record<string, boolean>` | optional | Granular permission flags |
| **scopes** | `string[]` | optional | High-level access scopes |
| **metadata** | `Record<string, any>` | optional | Custom metadata |


---

## User
Expand Down
12 changes: 6 additions & 6 deletions content/docs/references/index.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Protocol Reference
description: Every schema published by @objectstack/spec — 1583 schemas across 14 protocol modules
description: Every schema published by @objectstack/spec — 1582 schemas across 14 protocol modules
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand All @@ -24,7 +24,7 @@ counts are sums of the rows they head. Regenerate with
| [Automation Protocol](/docs/references/automation) | 13 | 68 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
| [Cloud Protocol](/docs/references/cloud) | 11 | 94 | Environments, packages and versions, marketplace, developer portal, tenancy. |
| [Data Protocol](/docs/references/data) | 29 | 163 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
| [Identity Protocol](/docs/references/identity) | 5 | 28 | Users and accounts, organizations, positions, API keys, SCIM provisioning. |
| [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. |
| [Integration Protocol](/docs/references/integration) | 1 | 27 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. |
| [Kernel Protocol](/docs/references/kernel) | 31 | 176 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. |
| [QA Protocol](/docs/references/qa) | 1 | 8 | Declarative test suites — scenarios, steps, actions and assertions. |
Expand All @@ -33,7 +33,7 @@ counts are sums of the rows they head. Regenerate with
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
| [System Protocol](/docs/references/system) | 36 | 287 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
| [UI Protocol](/docs/references/ui) | 16 | 160 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
| **Total** | **198** | **1583** | 14 protocol modules |
| **Total** | **198** | **1582** | 14 protocol modules |

---

Expand Down Expand Up @@ -186,14 +186,14 @@ Objects, fields, queries, filters, datasources and drivers — the ObjectQL laye

## Identity Protocol

**Source:** `packages/spec/src/identity/` · **Import:** `@objectstack/spec/identity` · **5 pages, 28 schemas**
**Source:** `packages/spec/src/identity/` · **Import:** `@objectstack/spec/identity` · **5 pages, 27 schemas**

Users and accounts, organizations, positions, API keys, SCIM provisioning.
Users and accounts, organizations, positions, SCIM provisioning.

| File | Schemas |
| :--- | :--- |
| [`eval-user.zod.ts`](/docs/references/identity/eval-user) | `EvalUser` |
| [`identity.zod.ts`](/docs/references/identity/identity) | `Account`, `ApiKey`, `User`, `VerificationToken` |
| [`identity.zod.ts`](/docs/references/identity/identity) | `Account`, `User`, `VerificationToken` |
| [`organization.zod.ts`](/docs/references/identity/organization) | `Invitation`, `InvitationStatus`, `Member`, `Organization` |
| [`position.zod.ts`](/docs/references/identity/position) | `Position` |
| [`scim.zod.ts`](/docs/references/identity/scim) | `SCIMAddress`, `SCIMBulkOperation`, `SCIMBulkRequest`, `SCIMBulkResponse`, `SCIMBulkResponseOperation`, `SCIMEmail`, `SCIMEnterpriseUser`, `SCIMError`, `SCIMGroup`, `SCIMGroupReference`, `SCIMListResponse`, `SCIMMemberReference`, `SCIMMeta`, `SCIMName`, `SCIMPatchOperation`, `SCIMPatchRequest`, `SCIMPhoneNumber`, `SCIMUser` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ directory rather than per file.
| `ai/` | 77 |
| `api/` | 399 |
| `cloud/` | 83 |
| `identity/` | 33 |
| `identity/` | 32 |
| `integration/` | 10 |
| `kernel/` | 296 |
| `qa/` | 6 |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

import { describe, it, expect } from 'vitest';
import { SysApiKey } from './sys-api-key.object.js';
import * as specIdentity from '@objectstack/spec/identity';

// ─── [#8715] `sys_api_key` has exactly ONE declaration — this object ────────
//
// `@objectstack/spec/identity` used to publish an `ApiKeySchema` that
// documented better-auth's `apiKey` PLUGIN shape — a plugin this platform
// does not load — so the one table had two declarations and the published one
// was fiction (`enabled` vs the real `revoked`, four rate-limit keys with no
// implementation, `start`/`lastRefetchAt`/`permissions`/`metadata` columns
// that do not exist). ADR-0049 enforce-or-remove; maintainer ruling
// 2026-08-15, disposition B: the schema is DELETED and this ObjectSchema is
// the single declaration.
//
// The spec half of the pin (zero holders on every public entry) lives in
// `packages/spec/src/identity/api-key-retirement.test.ts`; this half pins the
// consumer side — spec's runtime namespace really lost the name — and the
// real column set, so a drifted re-declaration cannot come back quietly on
// either side.
describe('[#8715] sys_api_key single-declaration pin', () => {
it('this object declares exactly the real column set', () => {
const declared = Object.keys((SysApiKey as { fields: Record<string, unknown> }).fields).sort();
expect(declared).toEqual([
'active_organization_id',
'created_at',
'expires_at',
'id',
'key',
'last_used_at',
'name',
'prefix',
'revoked',
'scopes',
'updated_at',
'user_id',
]);
// The polarity the deleted schema inverted: the kill switch is `revoked`,
// and there is no `enabled` column (asserted by the exact set above).
expect(declared).toContain('revoked');
});

it('@objectstack/spec/identity no longer exports the fictional schema', () => {
// Value export only, deliberately: `ApiKey` / `ApiKeyParsed` were
// type-only and have no runtime footprint a namespace check could see —
// asserting them here would be vacuous (green before the retirement too).
// The types are covered by the spec-side pin, which reads the built
// export-origins artifact and enumerates types as well as consts.
expect('ApiKeySchema' in specIdentity, 'spec/identity must not export ApiKeySchema (#8715)').toBe(false);
// Anti-vacuity: the namespace import is real and the survivors stand.
expect('UserSchema' in specIdentity).toBe(true);
});
});
3 changes: 0 additions & 3 deletions packages/spec/api-surface/identity.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
"AUTH_ERROR_CODES (const)",
"Account (type)",
"AccountSchema (const)",
"ApiKey (type)",
"ApiKeyParsed (type)",
"ApiKeySchema (const)",
"AuthError (interface)",
"AuthHeaders (interface)",
"AuthResponse (interface)",
Expand Down
1 change: 0 additions & 1 deletion packages/spec/authorable-defaults/identity.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
"description": "Ratchet of the DEFAULT VALUE of every authorable key in one category that has one (#4666) — what a metadata author gets when they omit the key, which for AI-authored metadata is most of the time. Sharded by category like authorable-surface/; the gate reads the whole authorable-defaults/ directory as ONE set. Each line is \"<def>:<key> = <canonical JSON>\". Additions (a NEW key that ships with a default) are auto-recorded — commit the change. CHANGING, ADDING or REMOVING the default of a key that already existed is NOT auto-recorded: it silently alters the behaviour of already-deployed metadata, so it fails check:authorable-surface until it is declared in DEFAULT_CHANGES_BY_MAJOR (scripts/lib/default-changes.ts). Constraints are deliberately NOT recorded here — a tightened bound REJECTS a document loudly, which is a different and self-announcing class (maintainer ruling on #4666, direction B). See #4666, #4661.",
"category": "identity",
"defaults": [
"identity/ApiKey:enabled = true",
"identity/EvalUser:positions = []",
"identity/Invitation:status = \"pending\"",
"identity/Position:delegatable = false",
Expand Down
19 changes: 0 additions & 19 deletions packages/spec/authorable-surface/identity.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,6 @@
"identity/Account:type",
"identity/Account:updatedAt",
"identity/Account:userId",
"identity/ApiKey:createdAt",
"identity/ApiKey:enabled",
"identity/ApiKey:expiresAt",
"identity/ApiKey:id",
"identity/ApiKey:lastRefetchAt",
"identity/ApiKey:lastUsedAt",
"identity/ApiKey:metadata",
"identity/ApiKey:name",
"identity/ApiKey:organizationId",
"identity/ApiKey:permissions",
"identity/ApiKey:prefix",
"identity/ApiKey:rateLimitEnabled",
"identity/ApiKey:rateLimitMax",
"identity/ApiKey:rateLimitTimeWindow",
"identity/ApiKey:remaining",
"identity/ApiKey:scopes",
"identity/ApiKey:start",
"identity/ApiKey:updatedAt",
"identity/ApiKey:userId",
"identity/EvalUser:email",
"identity/EvalUser:id",
"identity/EvalUser:isPlatformAdmin",
Expand Down
3 changes: 0 additions & 3 deletions packages/spec/export-origins/identity.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
"AUTH_ERROR_CODES": "src/identity/protocol.ts#AUTH_ERROR_CODES (const)",
"Account": "src/identity/identity.zod.ts#Account (type)",
"AccountSchema": "src/identity/identity.zod.ts#AccountSchema (const)",
"ApiKey": "src/identity/identity.zod.ts#ApiKey (type)",
"ApiKeyParsed": "src/identity/identity.zod.ts#ApiKeyParsed (type)",
"ApiKeySchema": "src/identity/identity.zod.ts#ApiKeySchema (const)",
"AuthError": "src/identity/protocol.ts#AuthError (interface)",
"AuthHeaders": "src/identity/protocol.ts#AuthHeaders (interface)",
"AuthResponse": "src/identity/protocol.ts#AuthResponse (interface)",
Expand Down
1 change: 0 additions & 1 deletion packages/spec/json-schema.manifest/identity.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"category": "identity",
"schemas": [
"identity/Account",
"identity/ApiKey",
"identity/EvalUser",
"identity/Invitation",
"identity/InvitationStatus",
Expand Down
4 changes: 3 additions & 1 deletion packages/spec/scripts/build-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,9 @@ const CATEGORY_BLURBS: Record<string, string> = {
automation: 'Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records.',
cloud: 'Environments, packages and versions, marketplace, developer portal, tenancy.',
data: 'Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer.',
identity: 'Users and accounts, organizations, positions, API keys, SCIM provisioning.',
// "API keys" deliberately absent since #8715: the sys_api_key table is
// declared by @objectstack/platform-objects, not by a spec identity schema.
identity: 'Users and accounts, organizations, positions, SCIM provisioning.',
integration: 'The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances.',
kernel: 'Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry.',
qa: 'Declarative test suites — scenarios, steps, actions and assertions.',
Expand Down
10 changes: 8 additions & 2 deletions packages/spec/src/cloud/developer-portal.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ import { PublisherVerificationSchema } from './marketplace.zod';
* better-auth specification. This module only defines marketplace-specific
* extensions on top of the shared identity layer:
*
* - **User & Session** → `Identity.UserSchema`, `Identity.SessionSchema`
* - **User** → `Identity.UserSchema`; the session wire shape is `Session` from
* `@objectstack/spec/api` (the identity module deliberately declares no bare
* `SessionSchema` — #4641)
* - **Organization & Members** → `Identity.OrganizationSchema`, `Identity.MemberSchema`
* - **API Keys** → `Identity.ApiKeySchema` (with marketplace scopes)
* - **API Keys** → the `sys_api_key` ObjectSchema in
* `@objectstack/platform-objects` (`identity/sys-api-key.object.ts`), minted via
* `POST /api/v1/keys`; its `scopes` column is the marketplace-scope carrier.
* There is no `Identity.ApiKeySchema` — #8715 retired it (it documented
* better-auth's apiKey plugin, which this platform does not load)
*
* ## Key Concepts
* - **Publisher Profile**: Links an Identity Organization to a marketplace publisher
Expand Down
Loading
Loading