@company-semantics/contracts 39.7.0 → 40.0.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.7.0",
3
+ "version": "40.0.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 = 'e8cf36a33d43' as const;
3
- export const SPEC_HASH_FULL = 'e8cf36a33d43bec5e4383c8edbfbdccd37fe8d081a5ddd02e96c83cb5e7bd21a' as const;
2
+ export const SPEC_HASH = '402a9b3f7788' as const;
3
+ export const SPEC_HASH_FULL = '402a9b3f77881eef9d41ee5ad49aa2b184a5b838ce948cc43ea4226e434f9b81' as const;
@@ -1423,23 +1423,6 @@ export interface paths {
1423
1423
  patch?: never;
1424
1424
  trace?: never;
1425
1425
  };
1426
- "/api/company-md/docs/{id}/content": {
1427
- parameters: {
1428
- query?: never;
1429
- header?: never;
1430
- path?: never;
1431
- cookie?: never;
1432
- };
1433
- get?: never;
1434
- /** Update a company.md document content */
1435
- put: operations["updateCompanyMdDocContent"];
1436
- post?: never;
1437
- delete?: never;
1438
- options?: never;
1439
- head?: never;
1440
- patch?: never;
1441
- trace?: never;
1442
- };
1443
1426
  "/api/company-md/docs/{id}/title": {
1444
1427
  parameters: {
1445
1428
  query?: never;
@@ -4660,9 +4643,6 @@ export interface components {
4660
4643
  createdAt: string;
4661
4644
  updatedAt: string;
4662
4645
  };
4663
- UpdateCompanyMdContentRequest: {
4664
- content: string;
4665
- };
4666
4646
  UpdateCompanyMdTitleRequest: {
4667
4647
  title: string;
4668
4648
  };
@@ -8224,30 +8204,6 @@ export interface operations {
8224
8204
  };
8225
8205
  };
8226
8206
  };
8227
- updateCompanyMdDocContent: {
8228
- parameters: {
8229
- query?: never;
8230
- header?: never;
8231
- path: {
8232
- id: string;
8233
- };
8234
- cookie?: never;
8235
- };
8236
- requestBody: {
8237
- content: {
8238
- "application/json": components["schemas"]["UpdateCompanyMdContentRequest"];
8239
- };
8240
- };
8241
- responses: {
8242
- /** @description Content updated */
8243
- 204: {
8244
- headers: {
8245
- [name: string]: unknown;
8246
- };
8247
- content?: never;
8248
- };
8249
- };
8250
- };
8251
8207
  updateCompanyMdDocTitle: {
8252
8208
  parameters: {
8253
8209
  query?: never;
@@ -34,7 +34,6 @@ export const openApiRoutes = {
34
34
  '/api/company-md/docs/{id}/collab/stream': ['GET'],
35
35
  '/api/company-md/docs/{id}/collab/sync': ['GET'],
36
36
  '/api/company-md/docs/{id}/collab/updates': ['POST'],
37
- '/api/company-md/docs/{id}/content': ['PUT'],
38
37
  '/api/company-md/docs/{id}/context-bank': ['GET'],
39
38
  '/api/company-md/docs/{id}/context-bank/associate': ['POST'],
40
39
  '/api/company-md/docs/{id}/context-bank/retry': ['POST'],
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 {
@@ -28,8 +28,13 @@ export const accessRequestedDefinition: NotificationDefinition<"companyMd.access
28
28
  {
29
29
  kind: "companyMd.access_requested",
30
30
  compose: (payload, context) => {
31
- const { requesterName, docTitle, message, requestedAccessLevel, reviewUrl } =
32
- payload;
31
+ const {
32
+ requesterName,
33
+ docTitle,
34
+ message,
35
+ requestedAccessLevel,
36
+ reviewUrl,
37
+ } = payload;
33
38
 
34
39
  return {
35
40
  metadata: {
@@ -24,10 +24,38 @@ export const ACCESS_REQUEST_STATUSES = [
24
24
  export const AccessRequestStatusSchema = z.enum(ACCESS_REQUEST_STATUSES);
25
25
  export type AccessRequestStatus = z.infer<typeof AccessRequestStatusSchema>;
26
26
 
27
+ /**
28
+ * Character cap on an access-request message.
29
+ *
30
+ * A note to an owner, not a document: 280 is enough to say why you want access
31
+ * and short enough to read in an inbox row. Exported so the request dialog's
32
+ * `maxLength` and its remaining-character counter read the SAME number the API
33
+ * rejects on — a UI that let you type past the server's limit would fail the
34
+ * send with no explanation.
35
+ *
36
+ * Enforced in three places, deliberately: the input caps typing, this schema
37
+ * rejects the request, and a CHECK constraint on
38
+ * `company_md_doc_access_requests.message` bounds anything that reaches the
39
+ * table by another path.
40
+ */
41
+ export const ACCESS_REQUEST_MESSAGE_MAX_LENGTH = 280;
42
+
43
+ /**
44
+ * Plain text only. Rejects C0/C1 control characters — a pasted binary blob or a
45
+ * terminal dump — while allowing the newlines and tabs a human types. The field
46
+ * is rendered as text, never as markup, so this is about what may be STORED,
47
+ * not about escaping at render time.
48
+ */
49
+ const PLAIN_TEXT = /^[^\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]*$/;
50
+
27
51
  /** POST body to create an access request. */
28
52
  export const AccessRequestCreateSchema = z.object({
29
53
  /** Optional context the requester sends to the owner(s). */
30
- message: z.string().max(2000).optional(),
54
+ message: z
55
+ .string()
56
+ .max(ACCESS_REQUEST_MESSAGE_MAX_LENGTH)
57
+ .regex(PLAIN_TEXT, "Message must be plain text")
58
+ .optional(),
31
59
  /**
32
60
  * The band the requester is asking for (ADR-CONTRACTS-098 / ADR-BE-454):
33
61
  * `viewer` from the locked preview, `editor` from the view-only Editor tab
@@ -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;