@ttt-productions/ttt-core 0.79.52 → 0.79.53

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.
@@ -1 +1 @@
1
- {"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../src/doc-schemas/system.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;iBAuB1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAIxD,eAAO,MAAM,eAAe;;;iBAG1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAIxD,eAAO,MAAM,mBAAmB;;;;;iBAQ9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAOhE,eAAO,MAAM,uBAAuB;;;iBAGlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAKxE,eAAO,MAAM,2BAA2B;;;iBAGtC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAIhF,eAAO,MAAM,mBAAmB;;;;;;iBAG9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
1
+ {"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../src/doc-schemas/system.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;iBAuB1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAIxD,eAAO,MAAM,eAAe;;;iBAG1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAIxD,eAAO,MAAM,mBAAmB;;;;;iBAQ9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAOhE,eAAO,MAAM,uBAAuB;;;iBAGlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAKxE,eAAO,MAAM,2BAA2B;;;iBAGtC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAIhF,eAAO,MAAM,mBAAmB;;;;;;iBAG9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
@@ -5,8 +5,9 @@ import { MAX_ANNOUNCEMENT_MESSAGE_LENGTH, MAX_APP_VERSION_LENGTH, MAX_MAINTENANC
5
5
  import { PublicDocumentVersionBlockSchema } from './public-documents.js';
6
6
  // Its write rule is `mergeAppConfigUpdate` (../utils/app-config.ts): an update over the doc as
7
7
  // read, any missing field from DEFAULT_APP_CONFIG, the whole doc validated against this schema.
8
- // Every text lever carries the same cap as its UpdateAppConfigInputSchema field, so no writer
9
- // can store a value the update input would refuse.
8
+ // Its read rule is `readAppConfigLever` (same file): a lever this schema's field refuses reads as
9
+ // its DEFAULT_APP_CONFIG value. Every text lever carries the same cap as its
10
+ // UpdateAppConfigInputSchema field, so no writer can store a value the update input would refuse.
10
11
  export const AppConfigSchema = z.object({
11
12
  // '' (DEFAULT_APP_CONFIG) is valid here — no version published; only the update input
12
13
  // requires a non-empty value.
@@ -1 +1 @@
1
- {"version":3,"file":"system.js","sourceRoot":"","sources":["../../src/doc-schemas/system.ts"],"names":[],"mappings":"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","sourcesContent":["// System Firestore document SCHEMAS — the `_appConfig/app` runtime-config singleton.\n// Type inferred via z.infer. (Field docs live in ../types/system.ts.)\n\nimport { z } from 'zod';\nimport {\n MAX_ANNOUNCEMENT_MESSAGE_LENGTH,\n MAX_APP_VERSION_LENGTH,\n MAX_MAINTENANCE_MESSAGE_LENGTH,\n} from '../constants/business-admin.js';\nimport { PublicDocumentVersionBlockSchema } from './public-documents.js';\n\n// Its write rule is `mergeAppConfigUpdate` (../utils/app-config.ts): an update over the doc as\n// read, any missing field from DEFAULT_APP_CONFIG, the whole doc validated against this schema.\n// Every text lever carries the same cap as its UpdateAppConfigInputSchema field, so no writer\n// can store a value the update input would refuse.\nexport const AppConfigSchema = z.object({\n // '' (DEFAULT_APP_CONFIG) is valid here — no version published; only the update input\n // requires a non-empty value.\n appVersion: z.string().max(MAX_APP_VERSION_LENGTH),\n maintenanceMode: z.boolean(),\n maintenanceMessage: z.string().max(MAX_MAINTENANCE_MESSAGE_LENGTH).optional(),\n registrationEnabled: z.boolean(),\n // Announcement banner (the third operational lever, alongside maintenanceMode and\n // registrationEnabled). OPTIONAL, and EMPTY means no banner — there is no separate\n // on/off boolean, so clearing the text IS turning the banner off. Non-empty renders a\n // banner on the landing page and login screen. Set via updateAppConfig (audited).\n announcementMessage: z.string().max(MAX_ANNOUNCEMENT_MESSAGE_LENGTH).optional(),\n // Runtime abuse throttle (incident lever — NOT the charter/full mode, which is\n // a ttt-core code constant). Scales every rate limiter's maxRequests DOWN:\n // 0 < m <= 1, tighten-only. Absent = 1 (no throttle). Set via updateAppConfig\n // (audited); read by the backend limiter wrapper with a short in-memory cache.\n rateLimitMultiplier: z.number().gt(0).max(1).optional(),\n // Public-document version block — per document its current version and the latest version\n // that required acceptance, plus the one required-acceptance level. Written ONLY by the\n // release publish transaction (never updateAppConfig); every session already reads this doc\n // live, so a required release reaches open sessions without a new listener. Absent until the\n // first release: every document is then version 0 and nothing is required.\n publicDocumentVersions: PublicDocumentVersionBlockSchema.optional(),\n});\nexport type AppConfig = z.infer<typeof AppConfigSchema>;\n\n// _systemData/adminList — the admin / jrAdmin uid lists; source of truth synced to custom claims\n// by setAdminStatusClaim. (functions/src/auth/runUpdateAdminList.ts)\nexport const AdminListSchema = z.object({\n admins: z.array(z.string()),\n jrAdmins: z.array(z.string()),\n});\nexport type AdminList = z.infer<typeof AdminListSchema>;\n\n// _systemData/profanityList — the moderation word list read by the runtime text filter.\n// (functions/src/utility/seedProfanityList.ts / curateProfanityList.ts)\nexport const ProfanityListSchema = z.object({\n words: z.array(z.string()),\n updatedAt: z.number(),\n wordCount: z.number(),\n // Monotonic version for the chat-realtime word-list sync (Contract B). Bumped by each\n // curation; the chat Worker KV snapshot + the channel-DO lazy version check key on it.\n // Absent ⇒ 0.\n wordListVersion: z.number().optional(),\n});\nexport type ProfanityList = z.infer<typeof ProfanityListSchema>;\n\n// _systemData/reservedUsernames — curated brand/impersonation terms blocked at registration\n// by exact normalized (UPPERCASE) match. Distinct from the per-user `reservedDisplayNames`\n// uniqueness collection. Seeded by functions/src/utility/seedReservedUsernames.ts; consulted by\n// registerUser + checkDisplayNameAvailable. Names are stored UPPERCASE to match the\n// reservedDisplayName case-folding convention.\nexport const ReservedUsernamesSchema = z.object({\n names: z.array(z.string()),\n updatedAt: z.number(),\n});\nexport type ReservedUsernames = z.infer<typeof ReservedUsernamesSchema>;\n\n// _systemData/blockedFranchiseNames — curated famous-franchise / IP names (UPPERCASE), consulted\n// when creating/publishing a Work or Realm title and by the admin trademark-assist button. Distinct\n// from reservedUsernames (registration). Seeded by an admin seed callable. (legal-convo [BUILD].)\nexport const BlockedFranchiseNamesSchema = z.object({\n names: z.array(z.string()),\n updatedAt: z.number(),\n});\nexport type BlockedFranchiseNames = z.infer<typeof BlockedFranchiseNamesSchema>;\n\n// _systemData/appMode — the charter→full mode marker that recordAppModeFlip writes.\n// `current` mirrors the deployed `APP_MODE` constant after a flip is recorded.\nexport const AppModeMarkerSchema = z.object({\n current: z.enum(['charter', 'full']),\n updatedAt: z.number(),\n});\nexport type AppModeMarker = z.infer<typeof AppModeMarkerSchema>;\n"]}
1
+ {"version":3,"file":"system.js","sourceRoot":"","sources":["../../src/doc-schemas/system.ts"],"names":[],"mappings":"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","sourcesContent":["// System Firestore document SCHEMAS — the `_appConfig/app` runtime-config singleton.\n// Type inferred via z.infer. (Field docs live in ../types/system.ts.)\n\nimport { z } from 'zod';\nimport {\n MAX_ANNOUNCEMENT_MESSAGE_LENGTH,\n MAX_APP_VERSION_LENGTH,\n MAX_MAINTENANCE_MESSAGE_LENGTH,\n} from '../constants/business-admin.js';\nimport { PublicDocumentVersionBlockSchema } from './public-documents.js';\n\n// Its write rule is `mergeAppConfigUpdate` (../utils/app-config.ts): an update over the doc as\n// read, any missing field from DEFAULT_APP_CONFIG, the whole doc validated against this schema.\n// Its read rule is `readAppConfigLever` (same file): a lever this schema's field refuses reads as\n// its DEFAULT_APP_CONFIG value. Every text lever carries the same cap as its\n// UpdateAppConfigInputSchema field, so no writer can store a value the update input would refuse.\nexport const AppConfigSchema = z.object({\n // '' (DEFAULT_APP_CONFIG) is valid here — no version published; only the update input\n // requires a non-empty value.\n appVersion: z.string().max(MAX_APP_VERSION_LENGTH),\n maintenanceMode: z.boolean(),\n maintenanceMessage: z.string().max(MAX_MAINTENANCE_MESSAGE_LENGTH).optional(),\n registrationEnabled: z.boolean(),\n // Announcement banner (the third operational lever, alongside maintenanceMode and\n // registrationEnabled). OPTIONAL, and EMPTY means no banner — there is no separate\n // on/off boolean, so clearing the text IS turning the banner off. Non-empty renders a\n // banner on the landing page and login screen. Set via updateAppConfig (audited).\n announcementMessage: z.string().max(MAX_ANNOUNCEMENT_MESSAGE_LENGTH).optional(),\n // Runtime abuse throttle (incident lever — NOT the charter/full mode, which is\n // a ttt-core code constant). Scales every rate limiter's maxRequests DOWN:\n // 0 < m <= 1, tighten-only. Absent = 1 (no throttle). Set via updateAppConfig\n // (audited); read by the backend limiter wrapper with a short in-memory cache.\n rateLimitMultiplier: z.number().gt(0).max(1).optional(),\n // Public-document version block — per document its current version and the latest version\n // that required acceptance, plus the one required-acceptance level. Written ONLY by the\n // release publish transaction (never updateAppConfig); every session already reads this doc\n // live, so a required release reaches open sessions without a new listener. Absent until the\n // first release: every document is then version 0 and nothing is required.\n publicDocumentVersions: PublicDocumentVersionBlockSchema.optional(),\n});\nexport type AppConfig = z.infer<typeof AppConfigSchema>;\n\n// _systemData/adminList — the admin / jrAdmin uid lists; source of truth synced to custom claims\n// by setAdminStatusClaim. (functions/src/auth/runUpdateAdminList.ts)\nexport const AdminListSchema = z.object({\n admins: z.array(z.string()),\n jrAdmins: z.array(z.string()),\n});\nexport type AdminList = z.infer<typeof AdminListSchema>;\n\n// _systemData/profanityList — the moderation word list read by the runtime text filter.\n// (functions/src/utility/seedProfanityList.ts / curateProfanityList.ts)\nexport const ProfanityListSchema = z.object({\n words: z.array(z.string()),\n updatedAt: z.number(),\n wordCount: z.number(),\n // Monotonic version for the chat-realtime word-list sync (Contract B). Bumped by each\n // curation; the chat Worker KV snapshot + the channel-DO lazy version check key on it.\n // Absent ⇒ 0.\n wordListVersion: z.number().optional(),\n});\nexport type ProfanityList = z.infer<typeof ProfanityListSchema>;\n\n// _systemData/reservedUsernames — curated brand/impersonation terms blocked at registration\n// by exact normalized (UPPERCASE) match. Distinct from the per-user `reservedDisplayNames`\n// uniqueness collection. Seeded by functions/src/utility/seedReservedUsernames.ts; consulted by\n// registerUser + checkDisplayNameAvailable. Names are stored UPPERCASE to match the\n// reservedDisplayName case-folding convention.\nexport const ReservedUsernamesSchema = z.object({\n names: z.array(z.string()),\n updatedAt: z.number(),\n});\nexport type ReservedUsernames = z.infer<typeof ReservedUsernamesSchema>;\n\n// _systemData/blockedFranchiseNames — curated famous-franchise / IP names (UPPERCASE), consulted\n// when creating/publishing a Work or Realm title and by the admin trademark-assist button. Distinct\n// from reservedUsernames (registration). Seeded by an admin seed callable. (legal-convo [BUILD].)\nexport const BlockedFranchiseNamesSchema = z.object({\n names: z.array(z.string()),\n updatedAt: z.number(),\n});\nexport type BlockedFranchiseNames = z.infer<typeof BlockedFranchiseNamesSchema>;\n\n// _systemData/appMode — the charter→full mode marker that recordAppModeFlip writes.\n// `current` mirrors the deployed `APP_MODE` constant after a flip is recorded.\nexport const AppModeMarkerSchema = z.object({\n current: z.enum(['charter', 'full']),\n updatedAt: z.number(),\n});\nexport type AppModeMarker = z.infer<typeof AppModeMarkerSchema>;\n"]}
@@ -1,8 +1,15 @@
1
1
  import { type AppConfig } from '../doc-schemas/system.js';
2
+ /**
3
+ * Every operator lever of `_appConfig/app`, each at a value its AppConfigSchema field accepts.
4
+ * The release-owned `publicDocumentVersions` block is not a lever.
5
+ */
6
+ export type AppConfigLevers = Readonly<Required<Omit<AppConfig, 'publicDocumentVersions'>>>;
7
+ /** The name of one operator lever — a key of DEFAULT_APP_CONFIG. */
8
+ export type AppConfigLeverKey = keyof AppConfigLevers;
2
9
  /**
3
10
  * Every operator lever of `_appConfig/app` at rest — the doc a fresh environment behaves as if
4
- * it held, and the default each reader falls back to for a missing field. The OPEN posture:
5
- * maintenance off, registration open, no banner, no throttle.
11
+ * it held, and the value each reader falls back to for a missing or unreadable field. The OPEN
12
+ * posture: maintenance off, registration open, no banner, no throttle.
6
13
  *
7
14
  * `appVersion` is `''`: no version has been published. VersionGate skips a falsy version, so it
8
15
  * stays dormant — no reload for any browser, whatever version it stored before — until an
@@ -12,7 +19,12 @@ import { type AppConfig } from '../doc-schemas/system.js';
12
19
  * The release-owned `publicDocumentVersions` block is not a lever and has no default: it is
13
20
  * absent until the first release.
14
21
  */
15
- export declare const DEFAULT_APP_CONFIG: Readonly<Required<Omit<AppConfig, 'publicDocumentVersions'>>>;
22
+ export declare const DEFAULT_APP_CONFIG: AppConfigLevers;
23
+ /**
24
+ * The line shown while maintenance is on and the operator left `maintenanceMessage` blank — on
25
+ * the maintenance takeover screen and in the callable refusal alike.
26
+ */
27
+ export declare const DEFAULT_MAINTENANCE_MESSAGE = "TTT Productions is down for maintenance right now. We will be back shortly \u2014 thank you for your patience.";
16
28
  /**
17
29
  * The complete `_appConfig/app` doc to write after applying `update` to `existing` (the doc as
18
30
  * read in the writer's transaction; `undefined` or `null` when it does not exist). The update's
@@ -23,4 +35,19 @@ export declare const DEFAULT_APP_CONFIG: Readonly<Required<Omit<AppConfig, 'publ
23
35
  * invalid field already on the doc — so nothing invalid is ever written over it.
24
36
  */
25
37
  export declare function mergeAppConfigUpdate(existing: Readonly<Record<string, unknown>> | null | undefined, update: Readonly<Partial<AppConfig>>): AppConfig;
38
+ /**
39
+ * One operator lever off an unvalidated `_appConfig/app` snapshot (the raw stored doc; `null` or
40
+ * `undefined` when it is missing or not yet read): the stored value when its AppConfigSchema
41
+ * field accepts it, otherwise the lever's DEFAULT_APP_CONFIG value — for an absent field and for
42
+ * a value the schema refuses (a wrong type, an over-cap message, an out-of-range multiplier)
43
+ * alike. Pure; never throws.
44
+ */
45
+ export declare function readAppConfigLever<L extends AppConfigLeverKey>(snapshot: Readonly<Record<string, unknown>> | null | undefined, lever: L): AppConfigLevers[L];
46
+ /**
47
+ * Every operator lever off an unvalidated `_appConfig/app` snapshot, each read through
48
+ * `readAppConfigLever` — so a field the schema refuses falls back to its default while every
49
+ * valid field survives, and a missing doc reads as DEFAULT_APP_CONFIG: the open posture. Pure;
50
+ * never throws.
51
+ */
52
+ export declare function readAppConfigLevers(snapshot: Readonly<Record<string, unknown>> | null | undefined): AppConfigLevers;
26
53
  //# sourceMappingURL=app-config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-config.d.ts","sourceRoot":"","sources":["../../src/utils/app-config.ts"],"names":[],"mappings":"AAWA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE3E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,CAAC,CAQzF,CAAC;AAML;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAC9D,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GACnC,SAAS,CAcX"}
1
+ {"version":3,"file":"app-config.d.ts","sourceRoot":"","sources":["../../src/utils/app-config.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE3E;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,CAAC,CAAC,CAAC;AAE5F,oEAAoE;AACpE,MAAM,MAAM,iBAAiB,GAAG,MAAM,eAAe,CAAC;AAEtD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,EAAE,eAO/B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,2BAA2B,mHACqE,CAAC;AAM9G;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAC9D,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GACnC,SAAS,CAcX;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,iBAAiB,EAC5D,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAC9D,KAAK,EAAE,CAAC,GACP,eAAe,CAAC,CAAC,CAAC,CAKpB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GAC7D,eAAe,CAMjB"}
@@ -1,18 +1,26 @@
1
- // The `_appConfig/app` singleton at rest, and the ONE rule for writing to it.
1
+ // The `_appConfig/app` singleton at rest, and the ONE rule each for writing to it and reading it.
2
2
  //
3
- // Several backend writers touch this doc (the admin App configuration card through
3
+ // WRITE. Several backend writers touch this doc (the admin App configuration card through
4
4
  // updateAppConfig, the public-document release publish, a seed), and each sends only the fields
5
5
  // it owns. Written as a bare merge, the first of them to run after the doc is gone (a fresh or
6
6
  // wiped environment) creates a doc WITHOUT the fields AppConfigSchema requires. The write rule
7
7
  // is `mergeAppConfigUpdate(existing, update)`: the writer's update on top of the doc it read,
8
8
  // with any field that doc lacks taken from DEFAULT_APP_CONFIG, validated as a whole.
9
9
  //
10
- // See docs/design/ttt-productions-version-gate.md (ttt-prod) § The `_appConfig/app` document.
10
+ // READ. The stored doc is untrusted — an operator can console-edit any field into any shape —
11
+ // and a config read must never take the product down by itself. The read rule is
12
+ // `readAppConfigLever(snapshot, lever)`: the lever's stored value when its AppConfigSchema field
13
+ // accepts it, otherwise its DEFAULT_APP_CONFIG value, one lever at a time so a bad field never
14
+ // costs the good ones. `readAppConfigLevers` is every lever through it. The backend's cached
15
+ // snapshot reader and the client shell both read through these.
16
+ //
17
+ // See docs/design/ttt-productions-version-gate.md (ttt-prod) § The `_appConfig/app` document and
18
+ // § Operational levers.
11
19
  import { AppConfigSchema } from '../doc-schemas/system.js';
12
20
  /**
13
21
  * Every operator lever of `_appConfig/app` at rest — the doc a fresh environment behaves as if
14
- * it held, and the default each reader falls back to for a missing field. The OPEN posture:
15
- * maintenance off, registration open, no banner, no throttle.
22
+ * it held, and the value each reader falls back to for a missing or unreadable field. The OPEN
23
+ * posture: maintenance off, registration open, no banner, no throttle.
16
24
  *
17
25
  * `appVersion` is `''`: no version has been published. VersionGate skips a falsy version, so it
18
26
  * stays dormant — no reload for any browser, whatever version it stored before — until an
@@ -30,6 +38,11 @@ export const DEFAULT_APP_CONFIG = Object.freeze({
30
38
  announcementMessage: '',
31
39
  rateLimitMultiplier: 1,
32
40
  });
41
+ /**
42
+ * The line shown while maintenance is on and the operator left `maintenanceMessage` blank — on
43
+ * the maintenance takeover screen and in the callable refusal alike.
44
+ */
45
+ export const DEFAULT_MAINTENANCE_MESSAGE = 'TTT Productions is down for maintenance right now. We will be back shortly — thank you for your patience.';
33
46
  function definedFields(fields) {
34
47
  return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
35
48
  }
@@ -55,4 +68,30 @@ export function mergeAppConfigUpdate(existing, update) {
55
68
  }
56
69
  return parsed.data;
57
70
  }
71
+ /**
72
+ * One operator lever off an unvalidated `_appConfig/app` snapshot (the raw stored doc; `null` or
73
+ * `undefined` when it is missing or not yet read): the stored value when its AppConfigSchema
74
+ * field accepts it, otherwise the lever's DEFAULT_APP_CONFIG value — for an absent field and for
75
+ * a value the schema refuses (a wrong type, an over-cap message, an out-of-range multiplier)
76
+ * alike. Pure; never throws.
77
+ */
78
+ export function readAppConfigLever(snapshot, lever) {
79
+ const parsed = AppConfigSchema.shape[lever].safeParse(snapshot?.[lever]);
80
+ return parsed.success && parsed.data !== undefined
81
+ ? parsed.data
82
+ : DEFAULT_APP_CONFIG[lever];
83
+ }
84
+ /**
85
+ * Every operator lever off an unvalidated `_appConfig/app` snapshot, each read through
86
+ * `readAppConfigLever` — so a field the schema refuses falls back to its default while every
87
+ * valid field survives, and a missing doc reads as DEFAULT_APP_CONFIG: the open posture. Pure;
88
+ * never throws.
89
+ */
90
+ export function readAppConfigLevers(snapshot) {
91
+ const levers = {};
92
+ for (const lever of Object.keys(DEFAULT_APP_CONFIG)) {
93
+ levers[lever] = readAppConfigLever(snapshot, lever);
94
+ }
95
+ return levers;
96
+ }
58
97
  //# sourceMappingURL=app-config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-config.js","sourceRoot":"","sources":["../../src/utils/app-config.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,EAAE;AACF,mFAAmF;AACnF,gGAAgG;AAChG,+FAA+F;AAC/F,+FAA+F;AAC/F,8FAA8F;AAC9F,qFAAqF;AACrF,EAAE;AACF,8FAA8F;AAE9F,OAAO,EAAE,eAAe,EAAkB,MAAM,0BAA0B,CAAC;AAE3E;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAC7B,MAAM,CAAC,MAAM,CAAC;IACZ,UAAU,EAAE,EAAE;IACd,eAAe,EAAE,KAAK;IACtB,kBAAkB,EAAE,EAAE;IACtB,mBAAmB,EAAE,IAAI;IACzB,mBAAmB,EAAE,EAAE;IACvB,mBAAmB,EAAE,CAAC;CACvB,CAAC,CAAC;AAEL,SAAS,aAAa,CAAC,MAAyC;IAC9D,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC;AAC/F,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAA8D,EAC9D,MAAoC;IAEpC,MAAM,MAAM,GAAG,eAAe,CAAC,SAAS,CAAC;QACvC,GAAG,kBAAkB;QACrB,GAAG,aAAa,CAAC,QAAQ,IAAI,EAAE,CAAC;QAChC,GAAG,aAAa,CAAC,MAAM,CAAC;KACzB,CAAC,CAAC;IACH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,qEAAqE,KAAK,KAAK,KAAK,EAAE,OAAO,IAAI,SAAS,+BAA+B,CAC1I,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC","sourcesContent":["// The `_appConfig/app` singleton at rest, and the ONE rule for writing to it.\n//\n// Several backend writers touch this doc (the admin App configuration card through\n// updateAppConfig, the public-document release publish, a seed), and each sends only the fields\n// it owns. Written as a bare merge, the first of them to run after the doc is gone (a fresh or\n// wiped environment) creates a doc WITHOUT the fields AppConfigSchema requires. The write rule\n// is `mergeAppConfigUpdate(existing, update)`: the writer's update on top of the doc it read,\n// with any field that doc lacks taken from DEFAULT_APP_CONFIG, validated as a whole.\n//\n// See docs/design/ttt-productions-version-gate.md (ttt-prod) § The `_appConfig/app` document.\n\nimport { AppConfigSchema, type AppConfig } from '../doc-schemas/system.js';\n\n/**\n * Every operator lever of `_appConfig/app` at rest — the doc a fresh environment behaves as if\n * it held, and the default each reader falls back to for a missing field. The OPEN posture:\n * maintenance off, registration open, no banner, no throttle.\n *\n * `appVersion` is `''`: no version has been published. VersionGate skips a falsy version, so it\n * stays dormant — no reload for any browser, whatever version it stored before — until an\n * operator sets a real one through updateAppConfig (whose input refuses `''`). From then on the\n * gate compares as usual.\n *\n * The release-owned `publicDocumentVersions` block is not a lever and has no default: it is\n * absent until the first release.\n */\nexport const DEFAULT_APP_CONFIG: Readonly<Required<Omit<AppConfig, 'publicDocumentVersions'>>> =\n Object.freeze({\n appVersion: '',\n maintenanceMode: false,\n maintenanceMessage: '',\n registrationEnabled: true,\n announcementMessage: '',\n rateLimitMultiplier: 1,\n });\n\nfunction definedFields(fields: Readonly<Record<string, unknown>>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));\n}\n\n/**\n * The complete `_appConfig/app` doc to write after applying `update` to `existing` (the doc as\n * read in the writer's transaction; `undefined` or `null` when it does not exist). The update's\n * fields win; `existing` supplies the rest; any field still missing comes from\n * DEFAULT_APP_CONFIG. An `undefined` update field changes nothing. Pure.\n *\n * Throws when the result would not parse under AppConfigSchema — an invalid update, or an\n * invalid field already on the doc — so nothing invalid is ever written over it.\n */\nexport function mergeAppConfigUpdate(\n existing: Readonly<Record<string, unknown>> | null | undefined,\n update: Readonly<Partial<AppConfig>>,\n): AppConfig {\n const parsed = AppConfigSchema.safeParse({\n ...DEFAULT_APP_CONFIG,\n ...definedFields(existing ?? {}),\n ...definedFields(update),\n });\n if (!parsed.success) {\n const issue = parsed.error.issues[0];\n const field = issue?.path.join('.') || 'document';\n throw new Error(\n `_appConfig/app would not match AppConfigSchema after this update (${field}: ${issue?.message ?? 'invalid'}). Nothing should be written.`,\n );\n }\n return parsed.data;\n}\n"]}
1
+ {"version":3,"file":"app-config.js","sourceRoot":"","sources":["../../src/utils/app-config.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,EAAE;AACF,0FAA0F;AAC1F,gGAAgG;AAChG,+FAA+F;AAC/F,+FAA+F;AAC/F,8FAA8F;AAC9F,qFAAqF;AACrF,EAAE;AACF,8FAA8F;AAC9F,iFAAiF;AACjF,iGAAiG;AACjG,+FAA+F;AAC/F,6FAA6F;AAC7F,gEAAgE;AAChE,EAAE;AACF,iGAAiG;AACjG,wBAAwB;AAExB,OAAO,EAAE,eAAe,EAAkB,MAAM,0BAA0B,CAAC;AAW3E;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAoB,MAAM,CAAC,MAAM,CAAC;IAC/D,UAAU,EAAE,EAAE;IACd,eAAe,EAAE,KAAK;IACtB,kBAAkB,EAAE,EAAE;IACtB,mBAAmB,EAAE,IAAI;IACzB,mBAAmB,EAAE,EAAE;IACvB,mBAAmB,EAAE,CAAC;CACvB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,2BAA2B,GACtC,2GAA2G,CAAC;AAE9G,SAAS,aAAa,CAAC,MAAyC;IAC9D,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC;AAC/F,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAA8D,EAC9D,MAAoC;IAEpC,MAAM,MAAM,GAAG,eAAe,CAAC,SAAS,CAAC;QACvC,GAAG,kBAAkB;QACrB,GAAG,aAAa,CAAC,QAAQ,IAAI,EAAE,CAAC;QAChC,GAAG,aAAa,CAAC,MAAM,CAAC;KACzB,CAAC,CAAC;IACH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,qEAAqE,KAAK,KAAK,KAAK,EAAE,OAAO,IAAI,SAAS,+BAA+B,CAC1I,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAA8D,EAC9D,KAAQ;IAER,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACzE,OAAO,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAChD,CAAC,CAAE,MAAM,CAAC,IAA2B;QACrC,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;AAChC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAA8D;IAE9D,MAAM,MAAM,GAAgD,EAAE,CAAC;IAC/D,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAwB,EAAE,CAAC;QAC3E,MAAM,CAAC,KAAK,CAAC,GAAG,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,MAAyB,CAAC;AACnC,CAAC","sourcesContent":["// The `_appConfig/app` singleton at rest, and the ONE rule each for writing to it and reading it.\n//\n// WRITE. Several backend writers touch this doc (the admin App configuration card through\n// updateAppConfig, the public-document release publish, a seed), and each sends only the fields\n// it owns. Written as a bare merge, the first of them to run after the doc is gone (a fresh or\n// wiped environment) creates a doc WITHOUT the fields AppConfigSchema requires. The write rule\n// is `mergeAppConfigUpdate(existing, update)`: the writer's update on top of the doc it read,\n// with any field that doc lacks taken from DEFAULT_APP_CONFIG, validated as a whole.\n//\n// READ. The stored doc is untrusted — an operator can console-edit any field into any shape —\n// and a config read must never take the product down by itself. The read rule is\n// `readAppConfigLever(snapshot, lever)`: the lever's stored value when its AppConfigSchema field\n// accepts it, otherwise its DEFAULT_APP_CONFIG value, one lever at a time so a bad field never\n// costs the good ones. `readAppConfigLevers` is every lever through it. The backend's cached\n// snapshot reader and the client shell both read through these.\n//\n// See docs/design/ttt-productions-version-gate.md (ttt-prod) § The `_appConfig/app` document and\n// § Operational levers.\n\nimport { AppConfigSchema, type AppConfig } from '../doc-schemas/system.js';\n\n/**\n * Every operator lever of `_appConfig/app`, each at a value its AppConfigSchema field accepts.\n * The release-owned `publicDocumentVersions` block is not a lever.\n */\nexport type AppConfigLevers = Readonly<Required<Omit<AppConfig, 'publicDocumentVersions'>>>;\n\n/** The name of one operator lever — a key of DEFAULT_APP_CONFIG. */\nexport type AppConfigLeverKey = keyof AppConfigLevers;\n\n/**\n * Every operator lever of `_appConfig/app` at rest — the doc a fresh environment behaves as if\n * it held, and the value each reader falls back to for a missing or unreadable field. The OPEN\n * posture: maintenance off, registration open, no banner, no throttle.\n *\n * `appVersion` is `''`: no version has been published. VersionGate skips a falsy version, so it\n * stays dormant — no reload for any browser, whatever version it stored before — until an\n * operator sets a real one through updateAppConfig (whose input refuses `''`). From then on the\n * gate compares as usual.\n *\n * The release-owned `publicDocumentVersions` block is not a lever and has no default: it is\n * absent until the first release.\n */\nexport const DEFAULT_APP_CONFIG: AppConfigLevers = Object.freeze({\n appVersion: '',\n maintenanceMode: false,\n maintenanceMessage: '',\n registrationEnabled: true,\n announcementMessage: '',\n rateLimitMultiplier: 1,\n});\n\n/**\n * The line shown while maintenance is on and the operator left `maintenanceMessage` blank — on\n * the maintenance takeover screen and in the callable refusal alike.\n */\nexport const DEFAULT_MAINTENANCE_MESSAGE =\n 'TTT Productions is down for maintenance right now. We will be back shortly — thank you for your patience.';\n\nfunction definedFields(fields: Readonly<Record<string, unknown>>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));\n}\n\n/**\n * The complete `_appConfig/app` doc to write after applying `update` to `existing` (the doc as\n * read in the writer's transaction; `undefined` or `null` when it does not exist). The update's\n * fields win; `existing` supplies the rest; any field still missing comes from\n * DEFAULT_APP_CONFIG. An `undefined` update field changes nothing. Pure.\n *\n * Throws when the result would not parse under AppConfigSchema — an invalid update, or an\n * invalid field already on the doc — so nothing invalid is ever written over it.\n */\nexport function mergeAppConfigUpdate(\n existing: Readonly<Record<string, unknown>> | null | undefined,\n update: Readonly<Partial<AppConfig>>,\n): AppConfig {\n const parsed = AppConfigSchema.safeParse({\n ...DEFAULT_APP_CONFIG,\n ...definedFields(existing ?? {}),\n ...definedFields(update),\n });\n if (!parsed.success) {\n const issue = parsed.error.issues[0];\n const field = issue?.path.join('.') || 'document';\n throw new Error(\n `_appConfig/app would not match AppConfigSchema after this update (${field}: ${issue?.message ?? 'invalid'}). Nothing should be written.`,\n );\n }\n return parsed.data;\n}\n\n/**\n * One operator lever off an unvalidated `_appConfig/app` snapshot (the raw stored doc; `null` or\n * `undefined` when it is missing or not yet read): the stored value when its AppConfigSchema\n * field accepts it, otherwise the lever's DEFAULT_APP_CONFIG value — for an absent field and for\n * a value the schema refuses (a wrong type, an over-cap message, an out-of-range multiplier)\n * alike. Pure; never throws.\n */\nexport function readAppConfigLever<L extends AppConfigLeverKey>(\n snapshot: Readonly<Record<string, unknown>> | null | undefined,\n lever: L,\n): AppConfigLevers[L] {\n const parsed = AppConfigSchema.shape[lever].safeParse(snapshot?.[lever]);\n return parsed.success && parsed.data !== undefined\n ? (parsed.data as AppConfigLevers[L])\n : DEFAULT_APP_CONFIG[lever];\n}\n\n/**\n * Every operator lever off an unvalidated `_appConfig/app` snapshot, each read through\n * `readAppConfigLever` — so a field the schema refuses falls back to its default while every\n * valid field survives, and a missing doc reads as DEFAULT_APP_CONFIG: the open posture. Pure;\n * never throws.\n */\nexport function readAppConfigLevers(\n snapshot: Readonly<Record<string, unknown>> | null | undefined,\n): AppConfigLevers {\n const levers: Partial<Record<AppConfigLeverKey, unknown>> = {};\n for (const lever of Object.keys(DEFAULT_APP_CONFIG) as AppConfigLeverKey[]) {\n levers[lever] = readAppConfigLever(snapshot, lever);\n }\n return levers as AppConfigLevers;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/ttt-core",
3
- "version": "0.79.52",
3
+ "version": "0.79.53",
4
4
  "description": "Core types, Firestore path constants, and shared constants for TTT Productions",
5
5
  "repository": {
6
6
  "type": "git",