@pithy-sh/core 0.4.1 → 0.6.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.
@@ -46,6 +46,11 @@ export declare const HttpError: z.ZodCodec<z.ZodUnion<readonly [z.ZodDiscriminat
46
46
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
47
47
  code: z.ZodLiteral<"auth/provider_unavailable">;
48
48
  status: z.ZodLiteral<503>;
49
+ }, z.core.$strip>, z.ZodObject<{
50
+ message: z.ZodString;
51
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
52
+ code: z.ZodLiteral<"auth/session_not_fresh">;
53
+ status: z.ZodLiteral<403>;
49
54
  }, z.core.$strip>, z.ZodObject<{
50
55
  message: z.ZodString;
51
56
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -674,6 +679,13 @@ export declare const HttpError: z.ZodCodec<z.ZodUnion<readonly [z.ZodDiscriminat
674
679
  detail: z.ZodOptional<z.ZodString>;
675
680
  code: z.ZodLiteral<"auth/provider_unavailable">;
676
681
  status: z.ZodLiteral<503>;
682
+ }, z.core.$strip>, z.ZodObject<{
683
+ message: z.ZodString;
684
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
685
+ action: z.ZodOptional<z.ZodString>;
686
+ detail: z.ZodOptional<z.ZodString>;
687
+ code: z.ZodLiteral<"auth/session_not_fresh">;
688
+ status: z.ZodLiteral<403>;
677
689
  }, z.core.$strip>, z.ZodObject<{
678
690
  message: z.ZodString;
679
691
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/error/http.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAA4B,UAAU,EAAmB,MAAM,cAAc,CAAC;AAErF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBA8BpB,CAAC;AAeH;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,KAAK,GAAG,UAAU,CAInD;AAED,gGAAgG;AAChG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,GAAG,QAAQ,CAKxE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,GAAG,QAAQ,CAElE"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/error/http.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAA4B,UAAU,EAAmB,MAAM,cAAc,CAAC;AAErF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBA8BpB,CAAC;AAeH;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,KAAK,GAAG,UAAU,CAInD;AAED,gGAAgG;AAChG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,GAAG,QAAQ,CAKxE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,GAAG,QAAQ,CAElE"}
@@ -48,6 +48,11 @@ export declare const KitPublicErrorPayload: z.ZodDiscriminatedUnion<[z.ZodObject
48
48
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
49
49
  code: z.ZodLiteral<"auth/provider_unavailable">;
50
50
  status: z.ZodLiteral<503>;
51
+ }, z.core.$strip>, z.ZodObject<{
52
+ message: z.ZodString;
53
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
54
+ code: z.ZodLiteral<"auth/session_not_fresh">;
55
+ status: z.ZodLiteral<403>;
51
56
  }, z.core.$strip>, z.ZodObject<{
52
57
  message: z.ZodString;
53
58
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -677,6 +682,13 @@ export declare const KitErrorPayload: z.ZodDiscriminatedUnion<[z.ZodObject<{
677
682
  detail: z.ZodOptional<z.ZodString>;
678
683
  code: z.ZodLiteral<"auth/provider_unavailable">;
679
684
  status: z.ZodLiteral<503>;
685
+ }, z.core.$strip>, z.ZodObject<{
686
+ message: z.ZodString;
687
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
688
+ action: z.ZodOptional<z.ZodString>;
689
+ detail: z.ZodOptional<z.ZodString>;
690
+ code: z.ZodLiteral<"auth/session_not_fresh">;
691
+ status: z.ZodLiteral<403>;
680
692
  }, z.core.$strip>, z.ZodObject<{
681
693
  message: z.ZodString;
682
694
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -1551,6 +1563,11 @@ export declare const PublicErrorPayload: z.ZodUnion<readonly [z.ZodDiscriminated
1551
1563
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
1552
1564
  code: z.ZodLiteral<"auth/provider_unavailable">;
1553
1565
  status: z.ZodLiteral<503>;
1566
+ }, z.core.$strip>, z.ZodObject<{
1567
+ message: z.ZodString;
1568
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
1569
+ code: z.ZodLiteral<"auth/session_not_fresh">;
1570
+ status: z.ZodLiteral<403>;
1554
1571
  }, z.core.$strip>, z.ZodObject<{
1555
1572
  message: z.ZodString;
1556
1573
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -2196,6 +2213,13 @@ export declare const ErrorPayload: z.ZodUnion<readonly [z.ZodDiscriminatedUnion<
2196
2213
  detail: z.ZodOptional<z.ZodString>;
2197
2214
  code: z.ZodLiteral<"auth/provider_unavailable">;
2198
2215
  status: z.ZodLiteral<503>;
2216
+ }, z.core.$strip>, z.ZodObject<{
2217
+ message: z.ZodString;
2218
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
2219
+ action: z.ZodOptional<z.ZodString>;
2220
+ detail: z.ZodOptional<z.ZodString>;
2221
+ code: z.ZodLiteral<"auth/session_not_fresh">;
2222
+ status: z.ZodLiteral<403>;
2199
2223
  }, z.core.$strip>, z.ZodObject<{
2200
2224
  message: z.ZodString;
2201
2225
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -1 +1 @@
1
- {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/error/payload.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/error/payload.ts"],"names":[],"mappings":"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"}
@@ -75,6 +75,11 @@ const AuthProviderUnavailablePublic = z.object({
75
75
  status: z.literal(503).describe("Service Unavailable — one of this service's own dependencies, the provider's credential, could not be read. Not 502/504: nothing was asked of the provider, so no hop failed or timed out."),
76
76
  ...publicFields
77
77
  }).describe("An enabled social sign-in provider could not be served (503).");
78
+ const AuthSessionNotFreshPublic = z.object({
79
+ code: z.literal("auth/session_not_fresh").describe("The caller is signed in, but has not authenticated recently enough to attach a social provider to their account. Attaching one grants permanent access — after it, sign-in resolves by account id and the email stops mattering — so it is gated on a recent authentication the way unlinking already is. A separate code from `auth/forbidden` because the remedy is specific and cheap: sign in again, then retry. Nothing was changed."),
80
+ status: z.literal(403).describe("Forbidden — a real, current credential was presented and is not sufficient for this particular decision. Not 401, which is what a caller with no credential gets: a client that cannot tell the two apart does not know whether to re-authenticate or to retry."),
81
+ ...publicFields
82
+ }).describe("The session is authentic but the holder authenticated too long ago to attach a provider (403).");
78
83
  const NotFoundPublic = z.object({
79
84
  code: z.literal("core/not_found").describe("The requested resource does not exist."),
80
85
  status: z.literal(404).describe("Not Found."),
@@ -676,6 +681,7 @@ const KitPublicErrorPayload = z.discriminatedUnion("code", [
676
681
  InvalidTokenPublic,
677
682
  ForbiddenPublic,
678
683
  AuthProviderUnavailablePublic,
684
+ AuthSessionNotFreshPublic,
679
685
  NotFoundPublic,
680
686
  ConflictPublic,
681
687
  RateLimitPublic,
@@ -799,6 +805,7 @@ const InvalidInput = InvalidInputPublic.extend(internalFields).describe(InvalidI
799
805
  const InvalidToken = InvalidTokenPublic.extend(internalFields).describe(InvalidTokenPublic.description ?? "");
800
806
  const Forbidden = ForbiddenPublic.extend(internalFields).describe(ForbiddenPublic.description ?? "");
801
807
  const AuthProviderUnavailable = AuthProviderUnavailablePublic.extend(internalFields).describe(AuthProviderUnavailablePublic.description ?? "");
808
+ const AuthSessionNotFresh = AuthSessionNotFreshPublic.extend(internalFields).describe(AuthSessionNotFreshPublic.description ?? "");
802
809
  const NotFound = NotFoundPublic.extend(internalFields).describe(NotFoundPublic.description ?? "");
803
810
  const Conflict = ConflictPublic.extend(internalFields).describe(ConflictPublic.description ?? "");
804
811
  const RateLimit = RateLimitPublic.extend(internalFields).describe(RateLimitPublic.description ?? "");
@@ -927,6 +934,7 @@ const KitErrorPayload = z.discriminatedUnion("code", [
927
934
  InvalidToken,
928
935
  Forbidden,
929
936
  AuthProviderUnavailable,
937
+ AuthSessionNotFresh,
930
938
  NotFound,
931
939
  Conflict,
932
940
  RateLimit,
@@ -18,6 +18,7 @@ export declare const AuthContext: z.ZodObject<{
18
18
  sessionId: z.ZodString;
19
19
  scopes: z.ZodDefault<z.ZodArray<z.ZodString>>;
20
20
  locale: z.ZodOptional<z.ZodNullable<z.ZodString>>;
21
+ authenticatedAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
21
22
  }, z.core.$strip>;
22
23
  export type AuthContext = z.infer<typeof AuthContext>;
23
24
  //# sourceMappingURL=authContext.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"authContext.d.ts","sourceRoot":"","sources":["../../src/http/authContext.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW;;;;;iBASiE,CAAC;AAC1F,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC"}
1
+ {"version":3,"file":"authContext.d.ts","sourceRoot":"","sources":["../../src/http/authContext.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW;;;;;;iBAeiE,CAAC;AAC1F,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC"}
@@ -19,7 +19,8 @@ const AuthContext = z.object({
19
19
  userId: z.string().describe("ID of the authenticated user (populated by @pithy-sh/auth)."),
20
20
  sessionId: z.string().describe("ID of the active session this request belongs to."),
21
21
  scopes: z.array(z.string()).default([]).describe("Permission scopes granted to this session."),
22
- locale: Locale.nullish().describe("The reader's own stored language, from `pithy_auth_users.locale`; null or absent when they have never chosen.")
22
+ locale: Locale.nullish().describe("The reader's own stored language, from `pithy_auth_users.locale`; null or absent when they have never chosen."),
23
+ authenticatedAt: z.date().nullish().describe("When the holder of this session last actually authenticated — a sign-in, never a rotation, which carries the value forward unchanged. It rides here for the same reason `locale` does: the session lookup has already loaded the row, so a gate that needs it should not pay for a second read. Distinct from the session's own `createdAt`, which `/token/rotate` stamps fresh and is therefore resettable by anyone holding a stolen refresh token. Null or absent for a session written before the column existed, and for a Worker whose auth capability predates it.")
23
24
  }).describe("Per-request authenticated identity; the seam other capabilities depend on.");
24
25
  //#endregion
25
26
  export { AuthContext };
@@ -77,6 +77,13 @@ export declare const LogRecord: z.ZodObject<{
77
77
  detail: z.ZodOptional<z.ZodString>;
78
78
  code: z.ZodLiteral<"auth/provider_unavailable">;
79
79
  status: z.ZodLiteral<503>;
80
+ }, z.core.$strip>, z.ZodObject<{
81
+ message: z.ZodString;
82
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
83
+ action: z.ZodOptional<z.ZodString>;
84
+ detail: z.ZodOptional<z.ZodString>;
85
+ code: z.ZodLiteral<"auth/session_not_fresh">;
86
+ status: z.ZodLiteral<403>;
80
87
  }, z.core.$strip>, z.ZodObject<{
81
88
  message: z.ZodString;
82
89
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -1 +1 @@
1
- {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/logger/record.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;GAKG;AAEH,4GAA4G;AAC5G,eAAO,MAAM,QAAQ;;;;;EAE4F,CAAC;AAClH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAgD,CAAC;AAEtG;;;;GAIG;AACH,eAAO,MAAM,SAAS,wCAEsF,CAAC;AAC7G,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAW+E,CAAC;AACtG,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAmBnD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAMzD"}
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/logger/record.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;GAKG;AAEH,4GAA4G;AAC5G,eAAO,MAAM,QAAQ;;;;;EAE4F,CAAC;AAClH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAgD,CAAC;AAEtG;;;;GAIG;AACH,eAAO,MAAM,SAAS,wCAEsF,CAAC;AAC7G,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAW+E,CAAC;AACtG,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAmBnD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAMzD"}
@@ -3,5 +3,5 @@
3
3
  /** This package's npm name — the join key against a release feed. */
4
4
  export declare const PACKAGE_NAME = "@pithy-sh/core";
5
5
  /** This package's version, stamped from its own package.json at generation time. */
6
- export declare const PACKAGE_VERSION = "0.4.1";
6
+ export declare const PACKAGE_VERSION = "0.5.0";
7
7
  //# sourceMappingURL=version.generated.d.ts.map
@@ -4,6 +4,6 @@
4
4
  /** This package's npm name — the join key against a release feed. */
5
5
  const PACKAGE_NAME = "@pithy-sh/core";
6
6
  /** This package's version, stamped from its own package.json at generation time. */
7
- const PACKAGE_VERSION = "0.4.1";
7
+ const PACKAGE_VERSION = "0.5.0";
8
8
  //#endregion
9
9
  export { PACKAGE_NAME, PACKAGE_VERSION };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pithy-sh/core",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -497,6 +497,13 @@ export const CONFIG_SEAMS = {
497
497
  describe:
498
498
  "Which organization this caller is acting for. Scaffolded unimplemented — this Worker refuses to boot until src/billing/subject.ts answers it.",
499
499
  },
500
+ organizationRoles: {
501
+ binding: "roles",
502
+ module: "src/organization/roles.ts",
503
+ specifier: "./src/organization/roles",
504
+ describe:
505
+ "Who may do what here. Scaffolded with a starter matrix — the role names and the powers they hold are this product's, and only this project's code can state them.",
506
+ },
500
507
  } as const satisfies Record<string, ConfigSeamSpec>;
501
508
 
502
509
  /** Which scaffolded seam a choice needs, by key. */
@@ -681,6 +688,12 @@ export const CapabilityManifest = z
681
688
  .describe(
682
689
  "Every secret from this capability's registry that declares how it comes to exist and how it is replaced. The route `devSecrets` proved: a client reads it without executing the package, so `pithy doctor` says *not set — run this* or *not set — get it here*, and no client keeps its own table of names. A secret declaring neither axis is absent, which reads as *nothing is known*, not as *nothing can help*.",
683
690
  ),
691
+ seams: z
692
+ .array(ConfigSeam)
693
+ .default([])
694
+ .describe(
695
+ "Seams this capability needs **always**, rather than under one choice of one option. `pithy add` writes each module into the Worker, imports it, and passes it to the factory beside the config values — the same machinery `choicesNeedingSeam` drives, without the condition. `organization`'s role catalog is the one: a tenancy capability has no answer to *who may do what* that is not the adopter's, so there is no configuration under which the module is unnecessary and nothing to make it conditional on. Named from `CONFIG_SEAMS`, so nothing a manifest states is written into generated source unquoted (#183).",
696
+ ),
684
697
  scaffold: z.array(z.string()).default([]).describe("Human-readable scaffold steps the CLI performs or explains."),
685
698
  configOptions: z
686
699
  .array(ConfigOption)
@@ -117,6 +117,22 @@ const AuthProviderUnavailablePublic = z
117
117
  })
118
118
  .describe("An enabled social sign-in provider could not be served (503).");
119
119
 
120
+ const AuthSessionNotFreshPublic = z
121
+ .object({
122
+ code: z
123
+ .literal("auth/session_not_fresh")
124
+ .describe(
125
+ "The caller is signed in, but has not authenticated recently enough to attach a social provider to their account. Attaching one grants permanent access — after it, sign-in resolves by account id and the email stops mattering — so it is gated on a recent authentication the way unlinking already is. A separate code from `auth/forbidden` because the remedy is specific and cheap: sign in again, then retry. Nothing was changed.",
126
+ ),
127
+ status: z
128
+ .literal(403)
129
+ .describe(
130
+ "Forbidden — a real, current credential was presented and is not sufficient for this particular decision. Not 401, which is what a caller with no credential gets: a client that cannot tell the two apart does not know whether to re-authenticate or to retry.",
131
+ ),
132
+ ...publicFields,
133
+ })
134
+ .describe("The session is authentic but the holder authenticated too long ago to attach a provider (403).");
135
+
120
136
  const NotFoundPublic = z
121
137
  .object({
122
138
  code: z.literal("core/not_found").describe("The requested resource does not exist."),
@@ -1259,6 +1275,90 @@ const ControlPlaneKeyConflictPublic = z
1259
1275
  })
1260
1276
  .describe("A key operation conflicts with the connection's current state (409).");
1261
1277
 
1278
+ // --- @pithy-sh/organization: tenancy, roles, invitations and ownership codes ---
1279
+
1280
+ const OrganizationNotFoundPublic = z
1281
+ .object({
1282
+ code: z
1283
+ .literal("organization/not_found")
1284
+ .describe(
1285
+ "The named organization does not exist, **or** the caller is not a member of it. One code for two facts, deliberately: a distinguishable refusal is an existence oracle, and iterating it produces the customer list. Which of the two it was survives in `detail`, which the HTTP codec strips and the log keeps.",
1286
+ ),
1287
+ status: z.literal(404).describe("Not Found."),
1288
+ ...publicFields,
1289
+ })
1290
+ .describe("No such organization, or none this caller belongs to — the same answer for both (404).");
1291
+
1292
+ const OrganizationForbiddenPublic = z
1293
+ .object({
1294
+ code: z
1295
+ .literal("organization/forbidden")
1296
+ .describe(
1297
+ "The caller is a proved member whose role does not hold the power the route asked for. A 403 rather than the 404 above, and the difference is earned: by the time this is raised they already know the organization exists, because they belong to it.",
1298
+ ),
1299
+ status: z.literal(403).describe("Forbidden — a member, but not one who may do this."),
1300
+ ...publicFields,
1301
+ })
1302
+ .describe("A member's role does not hold the required power (403).");
1303
+
1304
+ const OrganizationInvalidRoleCatalogPublic = z
1305
+ .object({
1306
+ code: z
1307
+ .literal("organization/invalid_role_catalog")
1308
+ .describe(
1309
+ "A role catalog failed validation. Raised by `defineRoles` at author time — a redeclared kit power, an `administrativePower` nobody holds, a kit power no role holds, or a claimed nesting that does not hold — so a mistake fails where the catalog is written rather than the first time somebody is refused by it.",
1310
+ ),
1311
+ status: z.literal(400).describe("Bad Request — the catalog declaration is malformed."),
1312
+ ...publicFields,
1313
+ })
1314
+ .describe("A role catalog declaration is invalid (400).");
1315
+
1316
+ const OrganizationInvitationInvalidPublic = z
1317
+ .object({
1318
+ code: z
1319
+ .literal("organization/invitation_invalid")
1320
+ .describe(
1321
+ "An invitation cannot be redeemed: no such token, already used, withdrawn, past its date, or presented by a session whose own address is not the one invited. One code for all five, because telling somebody which it was tells whoever forwarded the link the same thing.",
1322
+ ),
1323
+ status: z.literal(400).describe("Bad Request — the offer is not redeemable by this caller."),
1324
+ ...publicFields,
1325
+ })
1326
+ .describe("An invitation is not redeemable (400).");
1327
+
1328
+ const OrganizationLastAdministratorPublic = z
1329
+ .object({
1330
+ code: z
1331
+ .literal("organization/last_administrator")
1332
+ .describe(
1333
+ "The write would leave the account with nobody holding `administrativePower`. Counted over the power rather than over a role spelled `admin`, so a catalog where an owner also administers does not become unadministrable when its one admin leaves.",
1334
+ ),
1335
+ status: z.literal(409).describe("Conflict — an account nobody can administer is not a state to arrive at."),
1336
+ ...publicFields,
1337
+ })
1338
+ .describe("The last administrator cannot be demoted, removed, or leave (409).");
1339
+
1340
+ const OrganizationSlugTakenPublic = z
1341
+ .object({
1342
+ code: z
1343
+ .literal("organization/slug_taken")
1344
+ .describe("Another organization already holds that slug. A slug addresses one account, so it is unique."),
1345
+ status: z.literal(409).describe("Conflict."),
1346
+ ...publicFields,
1347
+ })
1348
+ .describe("That slug is already in use (409).");
1349
+
1350
+ const OrganizationNominationInvalidPublic = z
1351
+ .object({
1352
+ code: z
1353
+ .literal("organization/nomination_invalid")
1354
+ .describe(
1355
+ "An ownership offer cannot be accepted: none stands, it was replaced, it has expired, or it was made to somebody else. Ownership moves only by offer and acceptance, so a refusal here is the transfer declining to happen rather than a permission being short.",
1356
+ ),
1357
+ status: z.literal(400).describe("Bad Request — no offer this caller may accept."),
1358
+ ...publicFields,
1359
+ })
1360
+ .describe("No standing ownership offer this caller may accept (400).");
1361
+
1262
1362
  // --- @pithy-sh/support: inbound inbox, taxonomy, and classification codes ---
1263
1363
 
1264
1364
  const SupportNotFoundPublic = z
@@ -1458,6 +1558,7 @@ export const KitPublicErrorPayload = z
1458
1558
  InvalidTokenPublic,
1459
1559
  ForbiddenPublic,
1460
1560
  AuthProviderUnavailablePublic,
1561
+ AuthSessionNotFreshPublic,
1461
1562
  NotFoundPublic,
1462
1563
  ConflictPublic,
1463
1564
  RateLimitPublic,
@@ -1560,6 +1661,13 @@ export const KitPublicErrorPayload = z
1560
1661
  ControlPlaneInsufficientScopePublic,
1561
1662
  ControlPlaneKeyNotFoundPublic,
1562
1663
  ControlPlaneKeyConflictPublic,
1664
+ OrganizationNotFoundPublic,
1665
+ OrganizationForbiddenPublic,
1666
+ OrganizationInvalidRoleCatalogPublic,
1667
+ OrganizationInvitationInvalidPublic,
1668
+ OrganizationLastAdministratorPublic,
1669
+ OrganizationSlugTakenPublic,
1670
+ OrganizationNominationInvalidPublic,
1563
1671
  SupportNotFoundPublic,
1564
1672
  SupportInvalidCategoryPublic,
1565
1673
  SupportUnparseableMessagePublic,
@@ -1587,6 +1695,9 @@ const Forbidden = ForbiddenPublic.extend(internalFields).describe(ForbiddenPubli
1587
1695
  const AuthProviderUnavailable = AuthProviderUnavailablePublic.extend(internalFields).describe(
1588
1696
  AuthProviderUnavailablePublic.description ?? "",
1589
1697
  );
1698
+ const AuthSessionNotFresh = AuthSessionNotFreshPublic.extend(internalFields).describe(
1699
+ AuthSessionNotFreshPublic.description ?? "",
1700
+ );
1590
1701
  const NotFound = NotFoundPublic.extend(internalFields).describe(NotFoundPublic.description ?? "");
1591
1702
  const Conflict = ConflictPublic.extend(internalFields).describe(ConflictPublic.description ?? "");
1592
1703
  const RateLimit = RateLimitPublic.extend(internalFields).describe(RateLimitPublic.description ?? "");
@@ -1897,6 +2008,28 @@ const TestersNotConfigured = TestersNotConfiguredPublic.extend(internalFields).d
1897
2008
  TestersNotConfiguredPublic.description ?? "",
1898
2009
  );
1899
2010
 
2011
+ const OrganizationNotFound = OrganizationNotFoundPublic.extend(internalFields).describe(
2012
+ OrganizationNotFoundPublic.description ?? "",
2013
+ );
2014
+ const OrganizationForbidden = OrganizationForbiddenPublic.extend(internalFields).describe(
2015
+ OrganizationForbiddenPublic.description ?? "",
2016
+ );
2017
+ const OrganizationInvalidRoleCatalog = OrganizationInvalidRoleCatalogPublic.extend(internalFields).describe(
2018
+ OrganizationInvalidRoleCatalogPublic.description ?? "",
2019
+ );
2020
+ const OrganizationInvitationInvalid = OrganizationInvitationInvalidPublic.extend(internalFields).describe(
2021
+ OrganizationInvitationInvalidPublic.description ?? "",
2022
+ );
2023
+ const OrganizationLastAdministrator = OrganizationLastAdministratorPublic.extend(internalFields).describe(
2024
+ OrganizationLastAdministratorPublic.description ?? "",
2025
+ );
2026
+ const OrganizationSlugTaken = OrganizationSlugTakenPublic.extend(internalFields).describe(
2027
+ OrganizationSlugTakenPublic.description ?? "",
2028
+ );
2029
+ const OrganizationNominationInvalid = OrganizationNominationInvalidPublic.extend(internalFields).describe(
2030
+ OrganizationNominationInvalidPublic.description ?? "",
2031
+ );
2032
+
1900
2033
  const SupportNotFound = SupportNotFoundPublic.extend(internalFields).describe(SupportNotFoundPublic.description ?? "");
1901
2034
  const SupportInvalidCategory = SupportInvalidCategoryPublic.extend(internalFields).describe(
1902
2035
  SupportInvalidCategoryPublic.description ?? "",
@@ -1923,6 +2056,7 @@ export const KitErrorPayload = z
1923
2056
  InvalidToken,
1924
2057
  Forbidden,
1925
2058
  AuthProviderUnavailable,
2059
+ AuthSessionNotFresh,
1926
2060
  NotFound,
1927
2061
  Conflict,
1928
2062
  RateLimit,
@@ -2025,6 +2159,13 @@ export const KitErrorPayload = z
2025
2159
  ControlPlaneInsufficientScope,
2026
2160
  ControlPlaneKeyNotFound,
2027
2161
  ControlPlaneKeyConflict,
2162
+ OrganizationNotFound,
2163
+ OrganizationForbidden,
2164
+ OrganizationInvalidRoleCatalog,
2165
+ OrganizationInvitationInvalid,
2166
+ OrganizationLastAdministrator,
2167
+ OrganizationSlugTaken,
2168
+ OrganizationNominationInvalid,
2028
2169
  SupportNotFound,
2029
2170
  SupportInvalidCategory,
2030
2171
  SupportUnparseableMessage,
@@ -24,6 +24,12 @@ export const AuthContext = z
24
24
  locale: Locale.nullish().describe(
25
25
  "The reader's own stored language, from `pithy_auth_users.locale`; null or absent when they have never chosen.",
26
26
  ),
27
+ authenticatedAt: z
28
+ .date()
29
+ .nullish()
30
+ .describe(
31
+ "When the holder of this session last actually authenticated — a sign-in, never a rotation, which carries the value forward unchanged. It rides here for the same reason `locale` does: the session lookup has already loaded the row, so a gate that needs it should not pay for a second read. Distinct from the session's own `createdAt`, which `/token/rotate` stamps fresh and is therefore resettable by anyone holding a stolen refresh token. Null or absent for a session written before the column existed, and for a Worker whose auth capability predates it.",
32
+ ),
27
33
  })
28
34
  .describe("Per-request authenticated identity; the seam other capabilities depend on.");
29
35
  export type AuthContext = z.infer<typeof AuthContext>;
@@ -0,0 +1,209 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { z } from "zod";
5
+
6
+ /**
7
+ * What may be stored as an image, and how it is served — stated once, for every capability that holds
8
+ * a small picture in a column.
9
+ *
10
+ * Two capabilities hold one: `@pithy-sh/auth` keeps a person's face, `@pithy-sh/organization` keeps an
11
+ * account's mark. They are the same object at different scales, and **two answers to *what may be
12
+ * stored as an image* is one of them being wrong.** So the allowlist, the ceiling, the derived byte
13
+ * figure and the serving split live here and are imported, exactly as `normalizeAddress` moved here in
14
+ * `#280`.
15
+ *
16
+ * ## Stored, not linked
17
+ *
18
+ * A remote URL means the page fetching an attacker-chosen host from a screen listing people, leaking
19
+ * the viewer's IP and referrer before they had done anything, and it needs a CSP hole to render at all.
20
+ * A bounded `data:` URL in a column has no third party in it.
21
+ *
22
+ * ## Rendered through `<img>`, never inlined
23
+ *
24
+ * A browser disables scripting inside an `<img>` by specification, and that is the entire basis for
25
+ * accepting SVG here at all. Nothing in the kit writes one of these values into a document.
26
+ *
27
+ * ## Rasters get a URL, vectors never do — and that is the security answer, not a mitigation
28
+ *
29
+ * A `data:` URL has one property it cannot have: **it cannot be cached.** It is inline in the response
30
+ * body, so a roster of twenty faces re-sends twenty images on every read, forever. Serving from the
31
+ * app's own origin fixes that and costs one real hazard — **an SVG fetched by *navigation* runs script
32
+ * in the origin that served it.** An `<img src>` is inert; a URL is navigable.
33
+ *
34
+ * So a vector never gets a URL. {@link storedImageBytes} answers null for one, the serving route 404s
35
+ * on null, and there is no arrangement of ids and versions that makes the origin return an SVG. The
36
+ * class of attack is removed rather than managed with headers, and there is no header anybody has to
37
+ * keep right.
38
+ */
39
+
40
+ /**
41
+ * The image types a stored picture may be.
42
+ *
43
+ * Three rasters and one vector. The vector is here because `<img>` renders it inertly and a mark drawn
44
+ * as one is a few hundred bytes; every rule below that treats it differently is treating *navigability*
45
+ * differently, not the format.
46
+ */
47
+ export const STORED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml"] as const;
48
+
49
+ /** One of {@link STORED_IMAGE_TYPES}. */
50
+ export type StoredImageType = (typeof STORED_IMAGE_TYPES)[number];
51
+
52
+ /**
53
+ * The types that stay inline whatever else happens.
54
+ *
55
+ * One entry, and the module note argues it at length. Written as a list rather than as a comparison so
56
+ * that a second scriptable format added to {@link STORED_IMAGE_TYPES} one day is added here too, next
57
+ * to the reason.
58
+ */
59
+ export const INLINE_IMAGE_TYPES: readonly string[] = ["image/svg+xml"];
60
+
61
+ /**
62
+ * The ceiling on a stored image, in characters of `data:` URL.
63
+ *
64
+ * 32KB of base64, which is about 24KB of image. A face drawn at 34px does not need more, and the
65
+ * ceiling is what stops one row making every read expensive: the record, the account menu and the
66
+ * chooser all carry it.
67
+ */
68
+ export const MAX_STORED_IMAGE_CHARS = 32 * 1024;
69
+
70
+ /**
71
+ * The longest `data:<type>;base64,` a stored image can begin with, in characters.
72
+ *
73
+ * Derived from the allowlist rather than written down, so a longer type name added above cannot leave a
74
+ * ceiling below it stale. `image/svg+xml` is the long one today, at 26 characters.
75
+ */
76
+ export const LONGEST_STORED_IMAGE_PREFIX = Math.max(...STORED_IMAGE_TYPES.map((type) => `data:${type};base64,`.length));
77
+
78
+ /**
79
+ * The largest file a browser may encode into that ceiling, in bytes of image.
80
+ *
81
+ * **Not `MAX_STORED_IMAGE_CHARS * 3 / 4`, and the difference is a bug that shipped once.** That is the
82
+ * base64 inverse, and it silently assumes the stored string is base64 and nothing else. It is not: it
83
+ * is `data:<type>;base64,` and *then* the base64. A file at the plain inverse encodes to 22–26
84
+ * characters more than the column will take, so it passes a picker's own check and is refused by Zod —
85
+ * the person gets a validator's sentence where the product had written them one of its own.
86
+ *
87
+ * Base64 is four characters per three bytes and rounds up to a whole group, so the arithmetic runs
88
+ * backwards from the characters left after the prefix: `floor((chars - prefix) / 4)` groups, three
89
+ * bytes each. Derived from the *longest* prefix, because one ceiling has to hold for every type a
90
+ * picker offers and the tightest constraint is the one that binds.
91
+ *
92
+ * **This is the figure a client-side picker checks a file against before encoding it.** The column
93
+ * enforces {@link MAX_STORED_IMAGE_CHARS} on the whole string, which is the bound that matters; this is
94
+ * the same fact stated as bytes of image, so a picker and a column cannot disagree.
95
+ */
96
+ export const MAX_STORED_IMAGE_BYTES = 3 * Math.floor((MAX_STORED_IMAGE_CHARS - LONGEST_STORED_IMAGE_PREFIX) / 4);
97
+
98
+ /** `data:<type>;base64,<payload>` and nothing else — no bare URL, no `data:text/html`, no charset games. */
99
+ const STORED_IMAGE_PATTERN = new RegExp(
100
+ `^data:(?:${STORED_IMAGE_TYPES.join("|").replace(/\+/g, "\\+")});base64,[A-Za-z0-9+/]+=*$`,
101
+ );
102
+
103
+ /**
104
+ * A stored image, validated at the column and therefore at every boundary that decodes a row.
105
+ *
106
+ * **The pattern is an allowlist of types, not a check that the string starts with `data:`.**
107
+ * `data:text/html` is a `data:` URL too, and the difference between the two is the whole of what makes
108
+ * this column safe to render. Anchored at both ends, so nothing follows the payload.
109
+ */
110
+ export const StoredImage = z
111
+ .string()
112
+ .max(MAX_STORED_IMAGE_CHARS)
113
+ .regex(STORED_IMAGE_PATTERN)
114
+ .describe(
115
+ "An image held as a base64 `data:` URL of an allowed type, bounded to 32KB. Stored rather than linked, and rendered only through `<img>` — see `@pithy-sh/core/src/image/storedImage`.",
116
+ );
117
+ export type StoredImage = z.output<typeof StoredImage>;
118
+
119
+ /** The `data:` prefix a stored image of `type` begins with. */
120
+ function prefixOf(type: string): string {
121
+ return `data:${type};base64,`;
122
+ }
123
+
124
+ /** The type a stored image declares, or null when it is not a value this rule accepts. */
125
+ export function typeOfStoredImage(stored: string): StoredImageType | null {
126
+ return STORED_IMAGE_TYPES.find((type) => stored.startsWith(prefixOf(type))) ?? null;
127
+ }
128
+
129
+ /**
130
+ * What a screen should draw this image from.
131
+ *
132
+ * A URL for a raster, the stored value itself for a vector, and null for nothing. The caller renders
133
+ * whichever it gets through the same `<img src>`, so no screen has to know which it was handed.
134
+ *
135
+ * `path` is the route the owning capability serves bytes from, already carrying whatever identifies the
136
+ * subject — this function appends only the version, because the version is the half that has to be
137
+ * right for `immutable` to be honest.
138
+ *
139
+ * The version is the owning row's `updatedAt`: the URL changes whenever the image can have changed, so
140
+ * the response may be `immutable` and a browser never revalidates. It over-invalidates slightly — a
141
+ * rename busts a picture that did not change — which is one wasted fetch on a rare action, against a
142
+ * hash column and a migration to carry it.
143
+ */
144
+ export function storedImageSource(stored: string | null, path: string, version: Date | number): string | null {
145
+ if (stored === null) return null;
146
+ const type = typeOfStoredImage(stored);
147
+ // Not a value this build recognizes. Answered as nothing rather than served: a face falls back to
148
+ // initials, which is a real answer, and a value the allowlist does not match is one the column should
149
+ // never have held.
150
+ if (type === null) return null;
151
+ if (INLINE_IMAGE_TYPES.includes(type)) return stored;
152
+ const at = version instanceof Date ? version.getTime() : version;
153
+ return `${path}?v=${at}`;
154
+ }
155
+
156
+ /** A stored image decoded into what a response body needs. */
157
+ export interface StoredImageBytes {
158
+ /** The `Content-Type` to serve it as. Always one of {@link STORED_IMAGE_TYPES}, never sniffed. */
159
+ readonly type: StoredImageType;
160
+ /** The image itself. */
161
+ readonly body: Uint8Array;
162
+ }
163
+
164
+ /**
165
+ * Decode a stored image for serving, or null when it must not be served.
166
+ *
167
+ * **Null for a vector, and that is the gate rather than a caution.** The route asks this and answers
168
+ * 404 when it gets null, so there is no arrangement of ids and versions that makes the origin return an
169
+ * SVG. The module note gives the reason.
170
+ *
171
+ * Null too for anything the allowlist does not match, which the column should have refused on the way
172
+ * in — this is the second place that is true, and a value that reached the column past a bug does not
173
+ * get served out of it.
174
+ */
175
+ export function storedImageBytes(stored: string | null): StoredImageBytes | null {
176
+ if (stored === null) return null;
177
+ const type = typeOfStoredImage(stored);
178
+ if (type === null || INLINE_IMAGE_TYPES.includes(type)) return null;
179
+ const payload = stored.slice(prefixOf(type).length);
180
+ let binary: string;
181
+ try {
182
+ binary = atob(payload);
183
+ } catch {
184
+ // A column value that is not decodable base64. Nothing to serve, and nothing to say about it here.
185
+ return null;
186
+ }
187
+ const body = new Uint8Array(binary.length);
188
+ for (let at = 0; at < binary.length; at += 1) body[at] = binary.charCodeAt(at);
189
+ return { type, body };
190
+ }
191
+
192
+ /**
193
+ * The headers a served image carries.
194
+ *
195
+ * **`immutable`, which is only honest because the URL carries a version.** {@link storedImageSource}
196
+ * puts the owning row's `updatedAt` in the query, so a changed image is a different URL and a browser
197
+ * that never revalidates can never show a stale one.
198
+ *
199
+ * **`private`, because the fetch is authorized against a session.** A shared cache holding this would
200
+ * be one person's face served to whoever asked next.
201
+ *
202
+ * **`nosniff`, because the type is the allowlist's and must not be re-guessed.** Content sniffing is
203
+ * how a file served as an image gets treated as something else, and the whole safety of this route is
204
+ * that the type is one of three rasters.
205
+ */
206
+ export const STORED_IMAGE_HEADERS: Readonly<Record<string, string>> = {
207
+ "cache-control": "private, max-age=31536000, immutable",
208
+ "x-content-type-options": "nosniff",
209
+ };
@@ -12,10 +12,16 @@ import { JsonDate } from "../data/codecs";
12
12
  * the seam they already share, so the shape is stated once instead of being duplicated (and drifting) at
13
13
  * both ends.
14
14
  *
15
- * **The file holds a live credential.** A signed session cookie for a local database is a way in for
16
- * anyone who can read it, which is why it is written under `logs/` — gitignored by the starter template,
17
- * so it can never be committed — and why nothing here is ever put in a `PithyError` (the payload reaches
18
- * logs verbatim).
15
+ * **The file holds a live credential.** A signed claim for a local database is a way in for anyone who
16
+ * can read it, which is why it is written under `logs/` — gitignored by the starter template, so it can
17
+ * never be committed — and why nothing here is ever put in a `PithyError` (the payload reaches logs
18
+ * verbatim).
19
+ *
20
+ * **It used to hold a session cookie, and now it holds a claim — `#572`.** The difference is what
21
+ * survives: a cookie names a row in `pithy_auth_sessions`, so signing out of the app revoked it and the
22
+ * dev login 404'd until somebody reseeded. A claim names a *user*, signed with the session secret, and
23
+ * the route exchanges it for a fresh session on every open. Sign-out then revokes the session it should
24
+ * revoke and leaves the way back in alone.
19
25
  */
20
26
 
21
27
  /** The directory seed artifacts are written to, relative to the project root. Gitignored, so transient. */
@@ -39,29 +45,29 @@ export const DEV_LOGIN_PATH = `${SEED_ARTIFACT_DIR}/${DEV_LOGIN_FILE}`;
39
45
  * anything the kit serves that is not part of a capability's public surface lives under this prefix, so
40
46
  * a route added here can never collide with an application path someone already shipped.
41
47
  *
42
- * Registered **only** in a `dev` composition, and never under CI. It mints an authenticated session with
43
- * no credential presented, which is the whole risk of the feature and the reason its gates live at
44
- * registration rather than inside the handler.
48
+ * Registered **only** in a `dev` composition, and never under CI. It mints an authenticated session for
49
+ * whoever presents a claim this project's own secret signed, which is the whole risk of the feature and
50
+ * the reason its gates live at registration rather than inside the handler.
45
51
  */
46
52
  export const DEV_LOGIN_ROUTE = "/__pithy/dev-login";
47
53
 
54
+ /** The query parameter {@link DEV_LOGIN_ROUTE} takes the claim in. Spelled once, read by both ends. */
55
+ export const DEV_LOGIN_CLAIM_PARAM = "t";
56
+
48
57
  /**
49
58
  * A seeded dev login — everything a browser needs to be signed in as a seeded user, and nothing else.
50
59
  * `z.input` is the JSON on disk (dates as ISO-8601 strings); `z.output` is the app shape.
51
60
  */
52
61
  export const DevLogin = z
53
62
  .object({
54
- email: z.string().describe("The seeded user this cookie signs in as. Shown on the `pithy dev` ready banner."),
55
- userId: z.string().describe("The seeded user's id, so a tool can correlate the cookie with the seeded rows."),
56
- cookieName: z
57
- .string()
58
- .describe("The cookie name the auth capability's session is read from, e.g. `better-auth.session_token`."),
59
- cookieValue: z
63
+ email: z.string().describe("The seeded user this claim signs in as. Shown on the `pithy dev` ready banner."),
64
+ userId: z.string().describe("The seeded user's id, so a tool can correlate the login with the seeded rows."),
65
+ claim: z
60
66
  .string()
61
67
  .describe(
62
- "The signed, URI-encoded cookie value. A live credential for the local database — never logged, never committed, never in an error payload.",
68
+ "The signed, URI-encoded claim naming the user to sign in as. A live credential for the local database — never logged, never committed, never in an error payload. **Not a session:** it names a user and the route mints a session against it, so revoking a session leaves it working.",
63
69
  ),
64
- expiresAt: JsonDate.describe("When the seeded session expires. ISO-8601 text on disk; a `Date` in app code."),
70
+ expiresAt: JsonDate.describe("When the claim stops being accepted. ISO-8601 text on disk; a `Date` in app code."),
65
71
  })
66
- .describe("A seeded dev login: the signed session cookie for one seeded user, written to `logs/dev-login.json`.");
72
+ .describe("A seeded dev login: the signed claim for one seeded user, written to `logs/dev-login.json`.");
67
73
  export type DevLogin = z.output<typeof DevLogin>;
@@ -13,4 +13,4 @@
13
13
  export const PACKAGE_NAME = "@pithy-sh/core";
14
14
 
15
15
  /** This package's version, stamped from its own package.json at generation time. */
16
- export const PACKAGE_VERSION = "0.4.1";
16
+ export const PACKAGE_VERSION = "0.6.0";