@12-apps/prisma 6.6.0 → 6.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/prisma",
3
- "version": "6.6.0",
3
+ "version": "6.7.0",
4
4
  "sideEffects": false,
5
5
  "description": "Prisma host: the multi-file schema folder, the plugin migration seam, and the shared PrismaClient singleton with its audit / append-only extensions",
6
6
  "main": "dist/index.js",
@@ -68,7 +68,7 @@
68
68
  },
69
69
  "devDependencies": {
70
70
  "@12-apps/audit": "^5.7.1",
71
- "@12-apps/auth": "^2.19.0",
71
+ "@12-apps/auth": "^2.21.0",
72
72
  "@12-apps/discounts": "^1.13.0",
73
73
  "@12-apps/entitlements": "^3.10.0",
74
74
  "@12-apps/entity-lifecycle": "^4.13.0",
@@ -76,14 +76,14 @@
76
76
  "@12-apps/feature-flags": "^2.4.1",
77
77
  "@12-apps/i18n": "^1.3.0",
78
78
  "@12-apps/jobs": "^4.5.0",
79
- "@12-apps/mcp": "^3.16.0",
80
- "@12-apps/notifications": "^4.9.0",
79
+ "@12-apps/mcp": "^3.17.0",
80
+ "@12-apps/notifications": "^4.11.0",
81
81
  "@12-apps/onboarding": "^2.7.0",
82
- "@12-apps/payments-backend": "^4.26.0",
82
+ "@12-apps/payments-backend": "^4.27.1",
83
83
  "@12-apps/product-research": "^2.10.0",
84
84
  "@12-apps/rbac": "^4.14.0",
85
85
  "@12-apps/realtime": "^2.6.0",
86
- "@12-apps/report-builder": "^5.14.0",
86
+ "@12-apps/report-builder": "^5.14.1",
87
87
  "@12-apps/shift": "^3.4.0",
88
88
  "@12-apps/typescript-config": "^1.21.0",
89
89
  "@types/node": "^22.10.6",
@@ -0,0 +1,37 @@
1
+ -- @12-apps/mcp: the sealed successor that makes refresh rotation RETRYABLE.
2
+ --
3
+ -- `oauth_refresh_tokens.grace_seal` holds a token's own plaintext, encrypted
4
+ -- (AES-256-GCM) under a key derived by HKDF from the plaintext of the token it
5
+ -- was rotated FROM, with its grace deadline sealed inside the same blob.
6
+ --
7
+ -- WHY THE COLUMN EXISTS. Rotation-on-use with replay revocation cannot tell a
8
+ -- thief from a client that used one token twice for an innocent reason, and
9
+ -- there are two routine innocent reasons: a response lost to a proxy timeout,
10
+ -- and two of the client's own sessions refreshing at the same moment. Both were
11
+ -- punished as theft — the whole lineage revoked, including the successor just
12
+ -- handed to whoever won — which killed a live connection and sent a human back
13
+ -- through the full authorization flow. With this column, re-presenting a
14
+ -- just-consumed token inside the window returns THAT SAME successor instead, so
15
+ -- one successor is still all that ever exists and no second family is created.
16
+ --
17
+ -- WHY IT IS NOT A PLAINTEXT COLUMN. The key is never stored: it is derived from
18
+ -- the parent, which is itself only ever stored hashed. A dump of this table
19
+ -- therefore yields ciphertext and nothing that opens it, so the package's
20
+ -- "hashed, never plaintext" invariant is unchanged. The only party that can open
21
+ -- a seal is one presenting the parent — which is the party being served, and
22
+ -- which already held the token that mints the successor.
23
+ --
24
+ -- The seal is CLEARED whenever a token is consumed or revoked, and that is the
25
+ -- bound on what it costs: a seal opens only under the plaintext it was rotated
26
+ -- from, so spent seals left in place would chain — one historical plaintext plus
27
+ -- a copy of this table would walk forward to the live token offline, with no
28
+ -- server call and therefore no replay detection.
29
+ --
30
+ -- Nullable, but NOT optional: the package writes this field on every rotation,
31
+ -- so a deployment that raises the package version without applying this
32
+ -- migration gets a runtime failure on every refresh, not a quietly disabled
33
+ -- window. Apply it in the same change as the version raise. Guarded with
34
+ -- IF NOT EXISTS like every other statement this package ships, so a host that
35
+ -- already added the column adopts the migration as a no-op.
36
+ ALTER TABLE "oauth_refresh_tokens"
37
+ ADD COLUMN IF NOT EXISTS "grace_seal" TEXT;
@@ -38,6 +38,7 @@
38
38
  "20260821140000_discounts_package_owned",
39
39
  "20260824120000_payments_pending_verification",
40
40
  "20260826170000_locale_preferences",
41
- "20260830120000_discount_weekly_schedule"
41
+ "20260830120000_discount_weekly_schedule",
42
+ "20260910120000_add_refresh_grace_seal"
42
43
  ]
43
44
  }
@@ -71,6 +71,16 @@ model OAuthRefreshToken {
71
71
  revokedAt DateTime? @map("revoked_at")
72
72
  createdAt DateTime @default(now()) @map("created_at")
73
73
 
74
+ // This token's own plaintext, SEALED under a key derived from the plaintext of
75
+ // the token it was rotated from, with its grace deadline inside the sealed
76
+ // blob. It is what lets a rotation be RETRIED: re-presenting the consumed
77
+ // parent within the window returns this same successor instead of revoking the
78
+ // lineage, so a lost response or two concurrent refreshes no longer end the
79
+ // connection. Only a caller holding the parent can open it, so the "never store
80
+ // a token in the clear" rule is intact — a dump of this table yields ciphertext
81
+ // and no key. Null on a root token, and whenever the window is turned off.
82
+ graceSeal String? @map("grace_seal")
83
+
74
84
  // `tokenHash` is already indexed by its `@unique` constraint — the
75
85
  // lookup-on-presentation path — so no separate `@@index([tokenHash])` is added
76
86
  // (it would be redundant). The composite index serves per-user/per-client