rename: OAS becomes Kiosk, everywhere

officer kiosk is the product name; OAS was a placeholder that mimicked expo's
application services and is gone.

the rename is total because nothing has shipped: appName, permission key, all
four table prefixes, every index and check constraint name, the exported drizzle
symbols, and the secret-store purpose. no rows exist anywhere, so there is no
migration and no compatibility window - which is exactly why doing it now cost
nothing and doing it after the first publish would have cost a manual migration
on every install.

the one remaining mention of OAS is dated history in the manifest, naming what
this was ported from.
This commit is contained in:
Claude Opus 5
2026-08-18 01:42:37 +00:00
parent ed9bf828f8
commit 5374d40553
2 changed files with 30 additions and 27 deletions
+21 -21
View File
@@ -13,7 +13,7 @@ import {
import { users } from 'officerdb/auth/schema';
/*
Every table here is prefixed `oas_`, and that is load-bearing rather than tidy: `purge` finds a
Every table here is prefixed `kiosk_`, and that is load-bearing rather than tidy: `purge` finds a
plugin's tables by that prefix, so an unprefixed one is never cleaned up and shows forever in the
orphan list.
@@ -21,7 +21,7 @@ import { users } from 'officerdb/auth/schema';
There are no migrations. Install runs a generate-and-create against this schema and swallows "already
exists", so a column added in a later version is SILENTLY ABSENT on every machine that installed an
earlier one. Email survived that because its table had never held a row; `oas_builds` holds real rows
earlier one. Email survived that because its table had never held a row; `kiosk_builds` holds real rows
from the first publish. So the columns below are deliberately wider than today's needs, and the legal
values are declared as CHECK constraints rather than left as free text — the house rule from
`databases/CLAUDE.md`, which exists because rows for deleted providers kept their credentials long
@@ -43,11 +43,11 @@ const oneOf = (column: unknown, values: readonly string[]) =>
/**
* A published build. One row per app, per platform, per version.
*
* The artefact itself is NOT here — it is a ~20MB file under the OAS artefact root, and this row points
* The artefact itself is NOT here — it is a ~20MB file under the Kiosk artefact root, and this row points
* at it. See `artefactPath`.
*/
export const oasBuilds = pgTable(
'oas_builds',
export const kioskBuilds = pgTable(
'kiosk_builds',
{
id: serial('id').primaryKey(),
/** The directory name in the monorepo — `chat`. Not what the person sees. */
@@ -83,7 +83,7 @@ export const oasBuilds = pgTable(
/** How many devices the signing profile covers. Null for anything not ad-hoc signed. */
deviceCount: integer('device_count'),
sizeBytes: bigint('size_bytes', { mode: 'number' }).notNull(),
/** Relative to the OAS artefact root, never absolute — the root moves between machines. */
/** Relative to the Kiosk artefact root, never absolute — the root moves between machines. */
artefactPath: text('artefact_path').notNull(),
/** Survives retention. Replaces the `.keep` file the standalone service used. */
pinned: boolean('pinned').notNull().default(false),
@@ -91,9 +91,9 @@ export const oasBuilds = pgTable(
publishedAt: timestamp('published_at', { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
uniqueIndex('uq_oas_builds_app_platform_version').on(table.appName, table.platform, table.version),
check('ck_oas_builds_platform', oneOf(table.platform, PLATFORMS)),
check('ck_oas_builds_signing', sql`${table.signing} IS NULL OR ${oneOf(table.signing, SIGNINGS)}`),
uniqueIndex('uq_kiosk_builds_app_platform_version').on(table.appName, table.platform, table.version),
check('ck_kiosk_builds_platform', oneOf(table.platform, PLATFORMS)),
check('ck_kiosk_builds_signing', sql`${table.signing} IS NULL OR ${oneOf(table.signing, SIGNINGS)}`),
],
);
@@ -109,8 +109,8 @@ export const oasBuilds = pgTable(
* back in order to build an install URL, which a hash cannot do — the platform's own `api_keys` needs
* no such thing because nothing ever re-displays them.
*/
export const oasUserKeys = pgTable(
'oas_user_keys',
export const kioskUserKeys = pgTable(
'kiosk_user_keys',
{
id: serial('id').primaryKey(),
userId: integer('user_id')
@@ -118,14 +118,14 @@ export const oasUserKeys = pgTable(
.references(() => users.id, { onDelete: 'cascade' }),
/** SHA-256 of the key. Never the key itself. */
keyHash: text('key_hash').notNull(),
/** `encryptSecret('oas', key)` — readable back, unlike the hash. */
/** `encryptSecret('kiosk', key)` — readable back, unlike the hash. */
keyEncrypted: text('key_encrypted').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
rotatedAt: timestamp('rotated_at', { withTimezone: true }),
/** Debounced to once a minute on write, the way `api_keys` does it. */
lastUsedAt: timestamp('last_used_at', { withTimezone: true }),
},
(table) => [uniqueIndex('uq_oas_user_keys_user').on(table.userId)],
(table) => [uniqueIndex('uq_kiosk_user_keys_user').on(table.userId)],
);
/**
@@ -136,8 +136,8 @@ export const oasUserKeys = pgTable(
* them together, which is why a name cannot end up on the wrong device — asking afterwards is how you
* get three devices called "iOS Device".
*/
export const oasDevices = pgTable(
'oas_devices',
export const kioskDevices = pgTable(
'kiosk_devices',
{
id: serial('id').primaryKey(),
userId: integer('user_id')
@@ -162,9 +162,9 @@ export const oasDevices = pgTable(
(table) => [
// Partial: many devices may sit at `udid IS NULL` while pending, but a captured identifier is one
// physical device and must not be claimable twice.
uniqueIndex('uq_oas_devices_udid').on(table.udid).where(sql`${table.udid} IS NOT NULL`),
check('ck_oas_devices_status', oneOf(table.status, DEVICE_STATUSES)),
check('ck_oas_devices_kind', sql`${table.kind} IS NULL OR ${oneOf(table.kind, DEVICE_KINDS)}`),
uniqueIndex('uq_kiosk_devices_udid').on(table.udid).where(sql`${table.udid} IS NOT NULL`),
check('ck_kiosk_devices_status', oneOf(table.status, DEVICE_STATUSES)),
check('ck_kiosk_devices_kind', sql`${table.kind} IS NULL OR ${oneOf(table.kind, DEVICE_KINDS)}`),
],
);
@@ -175,14 +175,14 @@ export const oasDevices = pgTable(
* handed to the OS and the server never learns how it went. So this is best-effort by construction, and
* an absent row means "not reported", never "not installed".
*/
export const oasInstalls = pgTable('oas_installs', {
export const kioskInstalls = pgTable('kiosk_installs', {
id: serial('id').primaryKey(),
userId: integer('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
deviceId: integer('device_id').references(() => oasDevices.id, { onDelete: 'set null' }),
deviceId: integer('device_id').references(() => kioskDevices.id, { onDelete: 'set null' }),
buildId: integer('build_id')
.notNull()
.references(() => oasBuilds.id, { onDelete: 'cascade' }),
.references(() => kioskBuilds.id, { onDelete: 'cascade' }),
installedAt: timestamp('installed_at', { withTimezone: true }).notNull().defaultNow(),
});