@12-apps/prisma 6.5.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.5.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,22 +68,22 @@
68
68
  },
69
69
  "devDependencies": {
70
70
  "@12-apps/audit": "^5.7.1",
71
- "@12-apps/auth": "^2.19.0",
72
- "@12-apps/discounts": "^1.12.0",
71
+ "@12-apps/auth": "^2.21.0",
72
+ "@12-apps/discounts": "^1.13.0",
73
73
  "@12-apps/entitlements": "^3.10.0",
74
74
  "@12-apps/entity-lifecycle": "^4.13.0",
75
75
  "@12-apps/eslint-config": "^1.22.0",
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.8.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.24.0",
82
+ "@12-apps/payments-backend": "^4.27.1",
83
83
  "@12-apps/product-research": "^2.10.0",
84
- "@12-apps/rbac": "^4.13.0",
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,40 @@
1
+ -- @12-apps/discounts: the WEEKLY schedule (FUT-996).
2
+ --
3
+ -- "Toda sexta, das 16:00 às 20:00" — a recurring window INSIDE the campaign
4
+ -- `[starts_at, ends_at)`, which until now was the only scheduling a rule had.
5
+ --
6
+ -- ── PURELY ADDITIVE, AND THAT IS THE POINT ────────────────────────────────
7
+ -- One nullable column, no backfill, no CHECK to widen, nothing dropped. NULL
8
+ -- means "always, within the campaign window", which is exactly what every
9
+ -- existing row already means — so this migration cannot change the price of
10
+ -- anything, and the release carrying it needs none of the expand/contract
11
+ -- ceremony a destructive DDL would (see the host's CLAUDE.md on why a
12
+ -- `DROP COLUMN` takes production down for the length of a healthcheck).
13
+ --
14
+ -- Replay-safe like its predecessor: `IF NOT EXISTS` throughout.
15
+ --
16
+ -- ── WHY JSON RATHER THAN A FOURTH TABLE ───────────────────────────────────
17
+ -- The recurrence is not SQL-queryable in any useful way. The admin grid's
18
+ -- vigência filter already punts on comparing two nullable timestamps against
19
+ -- `now()` and rides as a separate parameter the store resolves, so a schedule
20
+ -- table would buy queryability that nothing asks for — while adding a third
21
+ -- `include` to `loadDiscountRules`, which is on the storefront's hottest read
22
+ -- (a whole menu page's badge preview walks it once per request).
23
+ --
24
+ -- The trade is stated plainly: the database cannot check this column's SHAPE.
25
+ -- `assertSchedule` in `src/server/validate.ts` is the only gate in front of it,
26
+ -- and the evaluator is deliberately tolerant of a row that got past it — a
27
+ -- malformed window covers NOTHING rather than everything, so the failure mode
28
+ -- of a bad blob is a promotion that does not fire, never a cart priced at zero.
29
+ --
30
+ -- Shape, for a reader with only the database in front of them:
31
+ --
32
+ -- {"windows":[{"days":[4],"from":"16:00","to":"20:00"}]}
33
+ --
34
+ -- * `days` — Monday-first, 0..6. NOT `Date#getDay()`'s Sunday-first axis.
35
+ -- * `from`/`to` — "HH:MM", 24-hour, in the STORE's timezone, half-open
36
+ -- [from, to) exactly as [starts_at, ends_at) is.
37
+ -- * `to` < `from` means the window runs past midnight (a bar shutting at
38
+ -- 02:00), which is the most common happy hour there is.
39
+
40
+ ALTER TABLE "discounts" ADD COLUMN IF NOT EXISTS "schedule" JSONB;
@@ -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;
@@ -37,6 +37,8 @@
37
37
  "20260821120000_shift_kind_host_vocabulary",
38
38
  "20260821140000_discounts_package_owned",
39
39
  "20260824120000_payments_pending_verification",
40
- "20260826170000_locale_preferences"
40
+ "20260826170000_locale_preferences",
41
+ "20260830120000_discount_weekly_schedule",
42
+ "20260910120000_add_refresh_grace_seal"
41
43
  ]
42
44
  }
@@ -79,6 +79,29 @@ model Discount {
79
79
  // either side = open-ended.
80
80
  startsAt DateTime? @map("starts_at")
81
81
  endsAt DateTime? @map("ends_at")
82
+ // The WEEKLY schedule INSIDE that window (FUT-996) — "toda sexta, das 16:00
83
+ // às 20:00", "segunda e terça à tarde". NULL = always, within
84
+ // [starts_at, ends_at), which is what every row predating the feature means.
85
+ //
86
+ // {"windows":[{"days":[4],"from":"16:00","to":"20:00"}]}
87
+ //
88
+ // `days` is Monday-first 0..6 — the axis every hours surface uses, and NOT
89
+ // `Date#getDay()`'s Sunday-first one. `from`/`to` are "HH:MM" in the STORE's
90
+ // timezone, half-open [from, to) exactly as the campaign window is, and a
91
+ // `to` earlier than `from` runs past midnight rather than being invalid.
92
+ //
93
+ // JSON rather than a fourth table because the recurrence is not usefully
94
+ // SQL-queryable (the grid's vigência filter already cannot express the
95
+ // campaign window either), and a fourth table would put a third `include` on
96
+ // the storefront's hottest read. The cost is that the DATABASE cannot check
97
+ // this shape: `assertSchedule` is the only gate, and the evaluator treats a
98
+ // malformed window as covering NOTHING, so a bad blob is a promotion that
99
+ // does not fire rather than a cart priced wrong.
100
+ //
101
+ // A recurring window is a WALL-CLOCK fact, so it is only meaningful against
102
+ // the store's own timezone. The engine never reads one: a host resolves the
103
+ // instant into a weekday and a minute-of-day and passes those in.
104
+ schedule Json? @map("schedule")
82
105
  // Minimum PRE-DISCOUNT cart subtotal, in cents. Always compared against the
83
106
  // untouched subtotal, never a running total — which is what makes the result
84
107
  // independent of the order the discounts are applied in.
@@ -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