@company-semantics/contracts 39.6.1 → 39.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "39.6.1",
3
+ "version": "39.8.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
2
- export const SPEC_HASH = 'b02ad24e598a' as const;
3
- export const SPEC_HASH_FULL = 'b02ad24e598abefeaaa58e146e9784fea88bc3abbd1af8b24a7d9c5d947db757' as const;
2
+ export const SPEC_HASH = 'e8cf36a33d43' as const;
3
+ export const SPEC_HASH_FULL = 'e8cf36a33d43bec5e4383c8edbfbdccd37fe8d081a5ddd02e96c83cb5e7bd21a' as const;
@@ -4740,6 +4740,8 @@ export interface components {
4740
4740
  };
4741
4741
  AccessRequestCreate: {
4742
4742
  message?: string;
4743
+ /** @enum {string} */
4744
+ requestedAccessLevel?: "editor" | "commenter" | "viewer";
4743
4745
  };
4744
4746
  ApproveAccessRequest: {
4745
4747
  /** @enum {string} */
package/src/index.ts CHANGED
@@ -907,6 +907,17 @@ export type {
907
907
  DisableSecretRequest,
908
908
  } from "./security/index";
909
909
 
910
+ // Auth token storage/entropy policy (PRD-00901)
911
+ // @see src/security/token-policy.ts — single source for the runtime issuance
912
+ // path and the secure-token-generation CI guard
913
+ export { AUTH_TOKEN_POLICY_REQUIREMENTS } from "./security/index";
914
+ export type {
915
+ TokenStorageFormat,
916
+ TokenPolicyConformance,
917
+ TokenPolicyRequirement,
918
+ AuthTokenPolicyClass,
919
+ } from "./security/index";
920
+
910
921
  // Analytics response metadata (shared vocabulary for OLTP/OLAP separation)
911
922
  // @see ADR-CTRL-053 for design rationale
912
923
  export type {
@@ -15,6 +15,7 @@ exports[`NOTIFICATION_DEFINITIONS > titles every kind 1`] = `
15
15
  "companyMd.access_request_denied · No reason": "Your access request was reviewed",
16
16
  "companyMd.access_request_denied · With reason": "Your access request was reviewed",
17
17
  "companyMd.access_requested · No message": "Someone requested access to a document",
18
+ "companyMd.access_requested · Requested editor": "Someone requested access to a document",
18
19
  "companyMd.access_requested · With message": "Someone requested access to a document",
19
20
  "org.invite · Admin": "You've been invited to join Acme Corp on Company Semantics",
20
21
  "org.invite · Member": "You've been invited to join Acme Corp on Company Semantics",
@@ -198,6 +198,13 @@ add("companyMd.access_requested", "No message", {
198
198
  docTitle: "Engineering Handbook",
199
199
  reviewUrl: `${APP}/doc/handbook?request=req_456`,
200
200
  });
201
+ add("companyMd.access_requested", "Requested editor", {
202
+ requesterName: "Sam Chen",
203
+ docTitle: "Engineering Handbook",
204
+ message: "I maintain this section day to day — may I edit it directly?",
205
+ requestedAccessLevel: "editor",
206
+ reviewUrl: `${APP}/doc/handbook?request=req_789`,
207
+ });
201
208
 
202
209
  add("companyMd.access_request_approved", "Editor", {
203
210
  approverName: "Jordan Lee",
@@ -8,11 +8,28 @@
8
8
 
9
9
  import type { NotificationDefinition } from "../definition";
10
10
 
11
+ /**
12
+ * How each requestable band reads in prose ("requested edit access") and in the
13
+ * key-value table ("Editor"). Absent band ⇒ band-neutral copy, so senders that
14
+ * predate `requestedAccessLevel` (ADR-CONTRACTS-098) render exactly as before.
15
+ */
16
+ const LEVEL_PHRASE = {
17
+ viewer: "view",
18
+ commenter: "comment",
19
+ editor: "edit",
20
+ } as const;
21
+ const LEVEL_LABEL = {
22
+ viewer: "Viewer",
23
+ commenter: "Commenter",
24
+ editor: "Editor",
25
+ } as const;
26
+
11
27
  export const accessRequestedDefinition: NotificationDefinition<"companyMd.access_requested"> =
12
28
  {
13
29
  kind: "companyMd.access_requested",
14
30
  compose: (payload, context) => {
15
- const { requesterName, docTitle, message, reviewUrl } = payload;
31
+ const { requesterName, docTitle, message, requestedAccessLevel, reviewUrl } =
32
+ payload;
16
33
 
17
34
  return {
18
35
  metadata: {
@@ -23,7 +40,12 @@ export const accessRequestedDefinition: NotificationDefinition<"companyMd.access
23
40
  {
24
41
  elements: [
25
42
  { type: "greeting" },
26
- { type: "body", text: "Someone requested document access." },
43
+ {
44
+ type: "body",
45
+ text: requestedAccessLevel
46
+ ? `Someone requested ${LEVEL_PHRASE[requestedAccessLevel]} access to a document.`
47
+ : "Someone requested document access.",
48
+ },
27
49
  {
28
50
  type: "chatUnit",
29
51
  items: [
@@ -47,6 +69,14 @@ export const accessRequestedDefinition: NotificationDefinition<"companyMd.access
47
69
  rows: [
48
70
  { label: "From", value: requesterName },
49
71
  { label: "Document", value: `"${docTitle}"` },
72
+ ...(requestedAccessLevel
73
+ ? [
74
+ {
75
+ label: "Requested",
76
+ value: LEVEL_LABEL[requestedAccessLevel],
77
+ },
78
+ ]
79
+ : []),
50
80
  ],
51
81
  },
52
82
  {
@@ -132,6 +132,12 @@ export interface NotificationPayloads {
132
132
  docTitle: string;
133
133
  /** Optional message from the requester (notification only) */
134
134
  message?: string;
135
+ /**
136
+ * The band the requester asked for (ADR-CONTRACTS-098): names the intent
137
+ * in the owner's email ("requested edit access"). Absent = unspecified
138
+ * (older senders / clients) — the copy stays band-neutral.
139
+ */
140
+ requestedAccessLevel?: "viewer" | "commenter" | "editor";
135
141
  /** Deep-link that opens the doc's ShareDialog scrolled to the pending request */
136
142
  reviewUrl: string;
137
143
  };
@@ -28,6 +28,15 @@ export type AccessRequestStatus = z.infer<typeof AccessRequestStatusSchema>;
28
28
  export const AccessRequestCreateSchema = z.object({
29
29
  /** Optional context the requester sends to the owner(s). */
30
30
  message: z.string().max(2000).optional(),
31
+ /**
32
+ * The band the requester is asking for (ADR-CONTRACTS-098 / ADR-BE-454):
33
+ * `viewer` from the locked preview, `editor` from the view-only Editor tab
34
+ * (elevation, ADR-BE-453). A REQUEST, not a claim — the owner still chooses
35
+ * the granted band on approval; this only seeds their picker. Optional so
36
+ * older clients keep working (absent ⇒ unspecified, owners default to
37
+ * viewer).
38
+ */
39
+ requestedAccessLevel: GrantableAccessLevelSchema.optional(),
31
40
  });
32
41
  export type AccessRequestCreate = z.infer<typeof AccessRequestCreateSchema>;
33
42
 
@@ -45,6 +54,12 @@ export const AccessRequestSchema = z.object({
45
54
  docId: z.string(),
46
55
  status: AccessRequestStatusSchema,
47
56
  message: z.string().nullable(),
57
+ /**
58
+ * The band the requester asked for (ADR-CONTRACTS-098) — seeds the owner
59
+ * inbox's approval picker. Null for requests filed before the field existed
60
+ * or by clients that did not specify one.
61
+ */
62
+ requestedAccessLevel: GrantableAccessLevelSchema.nullable(),
48
63
  createdAt: z.string(),
49
64
  resolvedAt: z.string().nullable(),
50
65
  });
@@ -19,3 +19,11 @@ export type {
19
19
  RotateSecretRequest,
20
20
  DisableSecretRequest,
21
21
  } from "./org-secrets";
22
+
23
+ export { AUTH_TOKEN_POLICY_REQUIREMENTS } from "./token-policy";
24
+ export type {
25
+ TokenStorageFormat,
26
+ TokenPolicyConformance,
27
+ TokenPolicyRequirement,
28
+ AuthTokenPolicyClass,
29
+ } from "./token-policy";
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Declared storage and entropy policy for auth-bearing tokens.
3
+ *
4
+ * This is the single source consumed by BOTH the runtime issuance path and the
5
+ * secure-token-generation CI guard. A declaration with no enforcing consumer is
6
+ * the defect class this module exists to close.
7
+ *
8
+ * Every field states what an implementation MUST satisfy. None of them describe
9
+ * what the code does today — that is `conformance`, and it is mandatory per
10
+ * class. Keeping the two apart is the point: a requirement record that reads as
11
+ * a description silently re-asserts the guarantee it was written to obtain, and
12
+ * is the same declaration-without-enforcement defect wearing a different hat.
13
+ */
14
+ export type TokenStorageFormat = "hashed" | "hmac-signed" | "encrypted";
15
+
16
+ /**
17
+ * Whether every known issuance site already satisfies the declared requirement.
18
+ *
19
+ * `enforced` — no known site violates it.
20
+ * `unmet` — at least one known site violates it; `gap` names what is wrong.
21
+ *
22
+ * This is NOT a suppression and confers no exemption. The requirement holds
23
+ * either way and the secure-token-generation guard reports the violating sites
24
+ * regardless — the field exists so the canonical record cannot be read as
25
+ * asserting a guarantee the implementation has not yet earned.
26
+ */
27
+ export type TokenPolicyConformance =
28
+ | { readonly status: "enforced" }
29
+ | { readonly status: "unmet"; readonly gap: string };
30
+
31
+ export interface TokenPolicyRequirement {
32
+ /** Minimum entropy the raw token must carry, in bits. */
33
+ readonly minEntropyBits: number;
34
+ /** Algorithm the stored representation must be derived with. */
35
+ readonly hashAlgorithm: string;
36
+ /**
37
+ * How the token must be persisted. Never 'plaintext' — that is not a member,
38
+ * because plaintext is never an acceptable requirement. A class that IS
39
+ * persisted in plaintext today says so through `conformance`, which is why
40
+ * the union does not need to express it.
41
+ */
42
+ readonly storageFormat: TokenStorageFormat;
43
+ /**
44
+ * Whether the implementation currently meets the requirement above. Required,
45
+ * not optional: an omitted status would default to an unearned claim, which
46
+ * is exactly the drift this record exists to surface.
47
+ */
48
+ readonly conformance: TokenPolicyConformance;
49
+ /**
50
+ * Identifier / function-name patterns that bind a call site to this policy
51
+ * class. Heuristic only: the AUTHORITATIVE binding is the explicit policy
52
+ * argument passed at the generation site. These patterns exist so the guard
53
+ * can flag an UNCLASSIFIED site, never to silently classify one.
54
+ */
55
+ readonly contextPatterns: readonly string[];
56
+ }
57
+
58
+ export const AUTH_TOKEN_POLICY_REQUIREMENTS = {
59
+ SessionToken: {
60
+ minEntropyBits: 256,
61
+ hashAlgorithm: "SHA-256",
62
+ storageFormat: "hashed",
63
+ contextPatterns: ["session"],
64
+ conformance: {
65
+ status: "unmet",
66
+ gap:
67
+ "Minted with randomUUID() (122 bits) and persisted verbatim: sessions.token " +
68
+ "holds the cookie value itself, and lookup is raw equality against it.",
69
+ },
70
+ },
71
+ ChatShareToken: {
72
+ minEntropyBits: 256,
73
+ hashAlgorithm: "SHA-256",
74
+ storageFormat: "hashed",
75
+ contextPatterns: ["share"],
76
+ conformance: {
77
+ status: "unmet",
78
+ gap:
79
+ "Minted with randomBytes(32) and persisted verbatim: chat_shares.token holds " +
80
+ "the share-URL value itself, and lookup is raw equality against it.",
81
+ },
82
+ },
83
+ InviteToken: {
84
+ minEntropyBits: 256,
85
+ hashAlgorithm: "SHA-256",
86
+ storageFormat: "hashed",
87
+ contextPatterns: ["invite"],
88
+ conformance: { status: "enforced" },
89
+ },
90
+ OAuthStateToken: {
91
+ minEntropyBits: 128,
92
+ hashAlgorithm: "HMAC-SHA256",
93
+ storageFormat: "hmac-signed",
94
+ contextPatterns: ["state", "nonce"],
95
+ conformance: { status: "enforced" },
96
+ },
97
+ OTPCode: {
98
+ minEntropyBits: 20,
99
+ hashAlgorithm: "bcrypt",
100
+ storageFormat: "hashed",
101
+ contextPatterns: ["otp", "loginCode"],
102
+ conformance: { status: "enforced" },
103
+ },
104
+ OAuthTokenEncryption: {
105
+ minEntropyBits: 256,
106
+ hashAlgorithm: "AES-256-GCM",
107
+ storageFormat: "encrypted",
108
+ contextPatterns: ["tokenEncryption"],
109
+ conformance: { status: "enforced" },
110
+ },
111
+ } as const satisfies Record<string, TokenPolicyRequirement>;
112
+
113
+ export type AuthTokenPolicyClass = keyof typeof AUTH_TOKEN_POLICY_REQUIREMENTS;