@company-semantics/contracts 46.0.0 → 47.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": "46.0.0",
3
+ "version": "47.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -86,6 +86,7 @@
86
86
  "build": "tsc -b --noEmit",
87
87
  "typecheck": "NODE_OPTIONS='--max-old-space-size=4096' tsc -b --noEmit",
88
88
  "typecheck:ci": "NODE_OPTIONS='--max-old-space-size=4096' tsc -p scripts/ci/tsconfig.json",
89
+ "lint": "eslint src/ scripts/ tests/ __tests__/ vitest.config.ts",
89
90
  "lint:md": "markdownlint-cli2 '**/*.md' '#node_modules' '#.ralph/worktrees' '#.claude/worktrees'",
90
91
  "format": "prettier --write src/",
91
92
  "format:check": "prettier --check src/",
@@ -137,9 +138,11 @@
137
138
  "zod": "^4.4.3"
138
139
  },
139
140
  "devDependencies": {
141
+ "@eslint/js": "^10.0.1",
140
142
  "@types/node": "^22.20.1",
141
143
  "amphtml-validator": "^1.0.38",
142
144
  "culori": "^4.0.2",
145
+ "eslint": "^10.8.1",
143
146
  "husky": "^9.1.7",
144
147
  "lint-staged": "^17.3.0",
145
148
  "markdownlint-cli2": "^0.23.2",
@@ -147,6 +150,7 @@
147
150
  "prettier": "^3.9.6",
148
151
  "tsx": "^4.23.1",
149
152
  "typescript": "^5.8.3",
153
+ "typescript-eslint": "^8.66.0",
150
154
  "vite": "^8.2.0",
151
155
  "vitest": "^4.1.10",
152
156
  "yaml": "^2.9.0"
@@ -158,6 +162,8 @@
158
162
  "nanoid@<3.3.17": ">=3.3.17",
159
163
  "js-yaml@>=4.0.0 <4.3.1": ">=4.3.1 <5.0.0",
160
164
  "js-yaml@>=5.0.0 <5.2.2": ">=5.2.2 <6.0.0",
165
+ "ajv@<6.14.0": ">=6.14.0",
166
+ "flatted@<3.4.0": ">=3.4.0",
161
167
  "picomatch@<4.0.4": ">=4.0.4",
162
168
  "esbuild@<0.28.1": ">=0.28.1",
163
169
  "linkify-it@<5.0.2": ">=5.0.2",
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
2
- export const SPEC_HASH = '9421bda67789' as const;
3
- export const SPEC_HASH_FULL = '9421bda67789792a5303137aaeb2f7f886cb490e8db9bee11e03f2de13f94806' as const;
2
+ export const SPEC_HASH = '018976cde74a' as const;
3
+ export const SPEC_HASH_FULL = '018976cde74aa4a20c06c5fb333963bf58199fba93d623cd89cf2a360d95eceb' as const;
@@ -4840,6 +4840,7 @@ export interface components {
4840
4840
  canEdit: boolean;
4841
4841
  canUploadContext?: boolean;
4842
4842
  canManageAcl?: boolean;
4843
+ canComment: boolean;
4843
4844
  canChangeVisibility?: boolean;
4844
4845
  inheritsFromId: string | null;
4845
4846
  inheritsFrom: string | null;
@@ -14,7 +14,6 @@ import { buildCapabilityGraph } from "../../../mcp/capability-graph";
14
14
  import type {
15
15
  MCPToolDescriptor,
16
16
  ToolDiscoveryResponse,
17
- CapabilityGraph,
18
17
  } from "../../../mcp/index";
19
18
 
20
19
  /**
@@ -173,6 +173,29 @@ export const CompanyMdDocResponseSchema = z.object({
173
173
  * outside the single-doc read.
174
174
  */
175
175
  canManageAcl: z.boolean().optional(),
176
+ /**
177
+ * CONVERSATION capability signal (ADR-CONTRACTS-118 / ADR-BE-520): whether the
178
+ * requesting actor holds `CompanyMd.CanComment` (the `commenter` band and up)
179
+ * on this doc — the SAME binding `assertCanCommentSubject` enforces on every
180
+ * comment write AND, since ADR-BE-520, `assertCanReadSubject` enforces on the
181
+ * thread read. The app mounts or withholds the ENTIRE comment surface on it —
182
+ * the rail, the toolbar control, the open-thread badge, the preview
183
+ * highlights, the gutter marks and the thread fetch itself — not merely a
184
+ * create affordance: a document's conversation sits inside that document's
185
+ * confidentiality envelope rather than forming an annotation layer over it
186
+ * (ADR-APP-122). Signal only; admittance and enforcement are unchanged.
187
+ *
188
+ * REQUIRED, unlike the three signals above it, and the departure is
189
+ * deliberate. Those are optional for back-compat with doc shapes returned
190
+ * outside the single-doc read; this one gates a confidentiality surface, where
191
+ * an absent value means "unknown" rather than "false" and any client-invented
192
+ * default is a guess. Making it required is what lets the app delete the
193
+ * `canComment ?? canEdit` shadow-field fallback outright instead of carrying a
194
+ * second, weaker answer beside the server's. Pre-launch there is no
195
+ * back-compat consumer to protect, so the field is required and the package
196
+ * takes the major bump.
197
+ */
198
+ canComment: z.boolean(),
176
199
  /**
177
200
  * GOVERNANCE capability signal (ADR-BE-435): whether the requesting actor holds
178
201
  * `CompanyMd.CanChangeVisibility` (owner OR the `org.manage_content_sharing`
@@ -50,7 +50,7 @@ describe("resolveAvatar", () => {
50
50
  });
51
51
 
52
52
  it("returns initials source when no avatarUrl", () => {
53
- const result = resolveAvatar({ fullName: "Ian Heidt" } as any);
53
+ const result = resolveAvatar({ fullName: "Ian Heidt" });
54
54
  expect(result.source).toBe("initials");
55
55
  expect(result.initials).toBe("IH");
56
56
  expect(result.url).toBeUndefined();
@@ -1,26 +1,35 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import { WireSurfaceBuilder } from "../wire.js";
3
+ import type { MCPToolDescriptor } from "../../mcp/index.js";
3
4
  import type { ExecutionResultData } from "../execution.js";
4
5
  import type { UndoResultData } from "../execution.js";
5
6
 
7
+ // Deliberately partial fixtures: toolList is a pure wrapper that passes `tools`
8
+ // straight through, so these assert the wrapping, not descriptor validity. Cast
9
+ // through `unknown` rather than `any` — the narrowing is intentional and stated,
10
+ // not an absence of typing.
11
+ const partialTools = (
12
+ tools: ReadonlyArray<Record<string, unknown>>,
13
+ ): MCPToolDescriptor[] => tools as unknown as MCPToolDescriptor[];
14
+
6
15
  describe("WireSurfaceBuilder.toolList", () => {
7
16
  it("returns data part with type data-tool-list", () => {
8
- const tools = [
17
+ const tools = partialTools([
9
18
  { name: "search", description: "Search tool", inputSchema: {} },
10
- ] as any;
19
+ ]);
11
20
  const result = WireSurfaceBuilder.toolList(tools);
12
21
  expect(result.type).toBe("data-tool-list");
13
22
  });
14
23
 
15
24
  it("data.tools matches input array exactly", () => {
16
- const tools = [
25
+ const tools = partialTools([
17
26
  { name: "search", description: "Search tool", inputSchema: {} },
18
27
  {
19
28
  name: "fetch",
20
29
  description: "Fetch tool",
21
30
  inputSchema: { type: "object" },
22
31
  },
23
- ] as any;
32
+ ]);
24
33
  const result = WireSurfaceBuilder.toolList(tools);
25
34
  expect(result.data.tools).toEqual(tools);
26
35
  });
@@ -9,7 +9,11 @@
9
9
  * levels are materialized into `effective_acl_grants`, and ADR-CTRL-086 for
10
10
  * the aggregation rule (most-permissive across sources).
11
11
  *
12
- * The commenter level is currently a stub — see ADR-CTRL-087.
12
+ * The commenter level is NO LONGER a stub. ADR-CTRL-087 held the placeholder
13
+ * open; ADR-BE-512 discharged it by giving the band a real domain (polymorphic
14
+ * comment threads bound to `CompanyMd.CanComment`), and ADR-BE-520 made that
15
+ * binding gate thread reads as well as writes. See also ADR-CONTRACTS-118 for
16
+ * the `canComment` capability signal the doc read publishes.
13
17
  *
14
18
  * `meta` is the metadata-only tier used for org-admin lifecycle access
15
19
  * (ADR-BE-245). It is materialized/derived only — never an explicit
@@ -46,6 +46,10 @@ export const ACCESS_REQUEST_MESSAGE_MAX_LENGTH = 280;
46
46
  * is rendered as text, never as markup, so this is about what may be STORED,
47
47
  * not about escaping at render time.
48
48
  */
49
+ // The control characters ARE the subject of this pattern — it exists to reject
50
+ // them. no-control-regex flags them as probable typos, which is the opposite of
51
+ // the intent documented above.
52
+ // eslint-disable-next-line no-control-regex
49
53
  const PLAIN_TEXT = /^[^\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]*$/;
50
54
 
51
55
  /** POST body to create an access request. */
@@ -41,6 +41,9 @@ export const OrgSecretsActionSchema = z.enum([
41
41
  ]);
42
42
  export type OrgSecretsAction = z.infer<typeof OrgSecretsActionSchema>;
43
43
 
44
+ // The brand exists only to be referenced in a type position below; the value
45
+ // binding is never read, which is the whole point of the nominal-typing idiom.
46
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
44
47
  declare const SecretValueStringBrand: unique symbol;
45
48
 
46
49
  /**