@adhdev/mesh-shared 1.0.60-rc.1 → 1.0.60-rc.11

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.
@@ -48,3 +48,50 @@ export declare const DAEMON_TO_SERVER_WS_MSGS: readonly DaemonToServerWsMsg[];
48
48
  export declare const SERVER_TO_DAEMON_WS_MSGS: readonly ServerToDaemonWsMsg[];
49
49
  export declare function isDaemonToServerWsMsg(value: unknown): value is DaemonToServerWsMsg;
50
50
  export declare function isServerToDaemonWsMsg(value: unknown): value is ServerToDaemonWsMsg;
51
+ /**
52
+ * `auth_ok.payload.limits` — the plan-limit contract the server sends DOWN to
53
+ * the daemon for client-side enforcement.
54
+ *
55
+ * WHY THIS EXISTS: the wire spelling is `maxP2Pconnections` (lowercase `c`),
56
+ * inherited from `PlanLimits` in the proprietary server's plan-limits.ts. The
57
+ * daemon-cloud consumer independently declared the same field as
58
+ * `maxP2PConnections` (capital `C`) and assigned it from a `payload as any`
59
+ * cast — so the two spellings NEVER met under a type, TypeScript had nothing
60
+ * to compare, and the limit arrived as `undefined` at every comparison site.
61
+ * `undefined !== -1` passes the "is it unlimited?" guard, then every
62
+ * `count >= undefined` is false: enforcement silently degraded to unlimited on
63
+ * all plans, in production, for months.
64
+ *
65
+ * The lesson is NOT "pick a spelling" — it is that a cross-package wire
66
+ * contract asserted by hand on both ends with an `any` in between cannot fail
67
+ * loudly. Both producer (server) and consumer (daemon-cloud) must now import
68
+ * THIS symbol, so a rename breaks the build on both sides instead of silently
69
+ * disabling a paid-plan limit. Use `normalizeAuthOkLimits()` at the parse
70
+ * boundary; never re-spell these fields locally.
71
+ */
72
+ export interface AuthOkPlanLimits {
73
+ /** P2P concurrent connection count (-1 = unlimited). WIRE SPELLING — lowercase `c`. */
74
+ maxP2Pconnections: number;
75
+ /** Screenshot send interval floor (seconds). 0 = real-time. */
76
+ screenshotIntervalSeconds: number;
77
+ /** Daily screenshot usage budget (minutes). -1 = unlimited. */
78
+ dailyScreenshotMinutes: number;
79
+ /** Max connectable machines/daemons (-1 = unlimited). */
80
+ maxMachines: number;
81
+ }
82
+ /**
83
+ * Parse-boundary normalizer for `auth_ok.payload.limits`.
84
+ *
85
+ * Returns `null` when the payload carries no usable limits object, so callers
86
+ * keep their existing "no limits known yet" branch. Individual non-numeric or
87
+ * missing fields fall back to the UNLIMITED sentinel (-1) for count-style
88
+ * limits and 0 for the interval floor, matching how each consumer already
89
+ * treats "no constraint" — a malformed field must never be coerced to a
90
+ * MORE restrictive value than the server intended, or a server-side schema
91
+ * change would start throttling paying users.
92
+ *
93
+ * Accepts the capital-`C` misspelling as an input alias ONLY so a daemon
94
+ * talking to a mixed-version fleet cannot regress; output is always the
95
+ * canonical wire spelling.
96
+ */
97
+ export declare function normalizeAuthOkLimits(raw: unknown): AuthOkPlanLimits | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/mesh-shared",
3
- "version": "1.0.60-rc.1",
3
+ "version": "1.0.60-rc.11",
4
4
  "description": "ADHDev mesh-shared — pure mesh/git status normalizers shared by daemon-core and web-core",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -105,3 +105,66 @@ export function isDaemonToServerWsMsg(value: unknown): value is DaemonToServerWs
105
105
  export function isServerToDaemonWsMsg(value: unknown): value is ServerToDaemonWsMsg {
106
106
  return typeof value === 'string' && (SERVER_TO_DAEMON_WS_MSGS as readonly string[]).includes(value);
107
107
  }
108
+
109
+ /**
110
+ * `auth_ok.payload.limits` — the plan-limit contract the server sends DOWN to
111
+ * the daemon for client-side enforcement.
112
+ *
113
+ * WHY THIS EXISTS: the wire spelling is `maxP2Pconnections` (lowercase `c`),
114
+ * inherited from `PlanLimits` in the proprietary server's plan-limits.ts. The
115
+ * daemon-cloud consumer independently declared the same field as
116
+ * `maxP2PConnections` (capital `C`) and assigned it from a `payload as any`
117
+ * cast — so the two spellings NEVER met under a type, TypeScript had nothing
118
+ * to compare, and the limit arrived as `undefined` at every comparison site.
119
+ * `undefined !== -1` passes the "is it unlimited?" guard, then every
120
+ * `count >= undefined` is false: enforcement silently degraded to unlimited on
121
+ * all plans, in production, for months.
122
+ *
123
+ * The lesson is NOT "pick a spelling" — it is that a cross-package wire
124
+ * contract asserted by hand on both ends with an `any` in between cannot fail
125
+ * loudly. Both producer (server) and consumer (daemon-cloud) must now import
126
+ * THIS symbol, so a rename breaks the build on both sides instead of silently
127
+ * disabling a paid-plan limit. Use `normalizeAuthOkLimits()` at the parse
128
+ * boundary; never re-spell these fields locally.
129
+ */
130
+ export interface AuthOkPlanLimits {
131
+ /** P2P concurrent connection count (-1 = unlimited). WIRE SPELLING — lowercase `c`. */
132
+ maxP2Pconnections: number;
133
+ /** Screenshot send interval floor (seconds). 0 = real-time. */
134
+ screenshotIntervalSeconds: number;
135
+ /** Daily screenshot usage budget (minutes). -1 = unlimited. */
136
+ dailyScreenshotMinutes: number;
137
+ /** Max connectable machines/daemons (-1 = unlimited). */
138
+ maxMachines: number;
139
+ }
140
+
141
+ /**
142
+ * Parse-boundary normalizer for `auth_ok.payload.limits`.
143
+ *
144
+ * Returns `null` when the payload carries no usable limits object, so callers
145
+ * keep their existing "no limits known yet" branch. Individual non-numeric or
146
+ * missing fields fall back to the UNLIMITED sentinel (-1) for count-style
147
+ * limits and 0 for the interval floor, matching how each consumer already
148
+ * treats "no constraint" — a malformed field must never be coerced to a
149
+ * MORE restrictive value than the server intended, or a server-side schema
150
+ * change would start throttling paying users.
151
+ *
152
+ * Accepts the capital-`C` misspelling as an input alias ONLY so a daemon
153
+ * talking to a mixed-version fleet cannot regress; output is always the
154
+ * canonical wire spelling.
155
+ */
156
+ export function normalizeAuthOkLimits(raw: unknown): AuthOkPlanLimits | null {
157
+ if (!raw || typeof raw !== 'object') return null;
158
+ const src = raw as Record<string, unknown>;
159
+ const num = (value: unknown, fallback: number): number =>
160
+ typeof value === 'number' && Number.isFinite(value) ? value : fallback;
161
+ return {
162
+ maxP2Pconnections: num(
163
+ src.maxP2Pconnections !== undefined ? src.maxP2Pconnections : src.maxP2PConnections,
164
+ -1,
165
+ ),
166
+ screenshotIntervalSeconds: num(src.screenshotIntervalSeconds, 0),
167
+ dailyScreenshotMinutes: num(src.dailyScreenshotMinutes, -1),
168
+ maxMachines: num(src.maxMachines, -1),
169
+ };
170
+ }