@company-semantics/contracts 59.0.0 → 60.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 +1 -1
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +13 -3
- package/src/comments/__tests__/schemas.test.ts +11 -0
- package/src/comments/schemas.ts +50 -0
- package/src/content/schemas.ts +23 -0
- package/src/execution/__tests__/registry.test.ts +25 -2
- package/src/execution/kinds.ts +9 -1
- package/src/execution/registry.ts +23 -0
- package/src/guards/evolution-config.ts +28 -2
- package/src/message-parts/__tests__/confirmation.test.ts +1 -0
- package/src/message-parts/confirmation.ts +1 -0
package/package.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
|
|
2
|
-
export const SPEC_HASH = '
|
|
3
|
-
export const SPEC_HASH_FULL = '
|
|
2
|
+
export const SPEC_HASH = '4ee1bc3befbf' as const;
|
|
3
|
+
export const SPEC_HASH_FULL = '4ee1bc3befbf8bb865b4bdad38b892c59f198fec45acdcdde93a067a678516b3' as const;
|
package/src/api/generated.ts
CHANGED
|
@@ -5065,7 +5065,7 @@ export interface components {
|
|
|
5065
5065
|
summary: {
|
|
5066
5066
|
executionId: string;
|
|
5067
5067
|
/** @enum {string} */
|
|
5068
|
-
kind: "integration.connect" | "integration.disconnect" | "integration.purge" | "profile.update" | "slack.send" | "data.ingest" | "data.scope" | "system.cleanup" | "member.changeManager" | "companyMd.ingest" | "companyMd.commentPass" | "semantic.transform" | "companyMd.importContextDoc";
|
|
5068
|
+
kind: "integration.connect" | "integration.disconnect" | "integration.purge" | "profile.update" | "slack.send" | "data.ingest" | "data.scope" | "system.cleanup" | "member.changeManager" | "companyMd.ingest" | "companyMd.commentPass" | "companyMd.commentAction" | "semantic.transform" | "companyMd.importContextDoc";
|
|
5069
5069
|
target: {
|
|
5070
5070
|
/** @constant */
|
|
5071
5071
|
type: "slack";
|
|
@@ -5313,6 +5313,7 @@ export interface components {
|
|
|
5313
5313
|
canUploadContext?: boolean;
|
|
5314
5314
|
canManageAcl?: boolean;
|
|
5315
5315
|
canComment: boolean;
|
|
5316
|
+
canDecideSuggestion: boolean;
|
|
5316
5317
|
canChangeVisibility?: boolean;
|
|
5317
5318
|
inheritsFromId: string | null;
|
|
5318
5319
|
inheritsFrom: string | null;
|
|
@@ -6992,6 +6993,7 @@ export interface components {
|
|
|
6992
6993
|
createdAt: string;
|
|
6993
6994
|
updatedAt: string;
|
|
6994
6995
|
comments: components["schemas"]["Comment"][];
|
|
6996
|
+
capabilities: components["schemas"]["ThreadCapabilities"];
|
|
6995
6997
|
};
|
|
6996
6998
|
/** @description Opaque, versioned comment anchor: document-level, a text anchor with a required quote and optional relative positions, or a zero-width text-insertion point for insert suggestions. */
|
|
6997
6999
|
CommentAnchor: {
|
|
@@ -7046,6 +7048,14 @@ export interface components {
|
|
|
7046
7048
|
endOffset: number | null;
|
|
7047
7049
|
displayName: string;
|
|
7048
7050
|
};
|
|
7051
|
+
/** @description What the requesting actor may do to one thread, decided server-side. */
|
|
7052
|
+
ThreadCapabilities: {
|
|
7053
|
+
canReply: boolean;
|
|
7054
|
+
canResolve: boolean;
|
|
7055
|
+
canReopen: boolean;
|
|
7056
|
+
canDecideSuggestion: boolean;
|
|
7057
|
+
canWithdraw: boolean;
|
|
7058
|
+
};
|
|
7049
7059
|
/** @description A comment thread without its comments. */
|
|
7050
7060
|
CommentThreadSummary: {
|
|
7051
7061
|
id: string;
|
|
@@ -9116,7 +9126,7 @@ export interface operations {
|
|
|
9116
9126
|
query?: {
|
|
9117
9127
|
cursor?: string;
|
|
9118
9128
|
limit?: number;
|
|
9119
|
-
kind?: "integration.connect" | "integration.disconnect" | "integration.purge" | "profile.update" | "slack.send" | "data.ingest" | "data.scope" | "system.cleanup" | "member.changeManager" | "companyMd.ingest" | "companyMd.commentPass" | "semantic.transform" | "companyMd.importContextDoc";
|
|
9129
|
+
kind?: "integration.connect" | "integration.disconnect" | "integration.purge" | "profile.update" | "slack.send" | "data.ingest" | "data.scope" | "system.cleanup" | "member.changeManager" | "companyMd.ingest" | "companyMd.commentPass" | "companyMd.commentAction" | "semantic.transform" | "companyMd.importContextDoc";
|
|
9120
9130
|
targetType?: string;
|
|
9121
9131
|
periodStart?: string;
|
|
9122
9132
|
periodEnd?: string;
|
|
@@ -9251,7 +9261,7 @@ export interface operations {
|
|
|
9251
9261
|
query?: {
|
|
9252
9262
|
limit?: number;
|
|
9253
9263
|
offset?: number;
|
|
9254
|
-
kind?: "integration.connect" | "integration.disconnect" | "integration.purge" | "profile.update" | "slack.send" | "data.ingest" | "data.scope" | "system.cleanup" | "member.changeManager" | "companyMd.ingest" | "companyMd.commentPass" | "semantic.transform" | "companyMd.importContextDoc";
|
|
9264
|
+
kind?: "integration.connect" | "integration.disconnect" | "integration.purge" | "profile.update" | "slack.send" | "data.ingest" | "data.scope" | "system.cleanup" | "member.changeManager" | "companyMd.ingest" | "companyMd.commentPass" | "companyMd.commentAction" | "semantic.transform" | "companyMd.importContextDoc";
|
|
9255
9265
|
kindPrefix?: string;
|
|
9256
9266
|
};
|
|
9257
9267
|
header?: never;
|
|
@@ -73,6 +73,17 @@ function makeThread(over: Record<string, unknown> = {}) {
|
|
|
73
73
|
createdAt: "2026-08-01T12:00:00.000Z",
|
|
74
74
|
updatedAt: "2026-08-01T12:00:00.000Z",
|
|
75
75
|
comments: [makeComment()],
|
|
76
|
+
// REQUIRED on a thread carried WITH its comments. The summary shape has no
|
|
77
|
+
// capabilities and no comments — a `CommentThreadSummary` parse of this
|
|
78
|
+
// fixture simply ignores both, which is why the summary cases below still
|
|
79
|
+
// pass unchanged.
|
|
80
|
+
capabilities: {
|
|
81
|
+
canReply: true,
|
|
82
|
+
canResolve: true,
|
|
83
|
+
canReopen: false,
|
|
84
|
+
canDecideSuggestion: false,
|
|
85
|
+
canWithdraw: false,
|
|
86
|
+
},
|
|
76
87
|
...over,
|
|
77
88
|
};
|
|
78
89
|
}
|
package/src/comments/schemas.ts
CHANGED
|
@@ -216,8 +216,58 @@ export type CommentThreadSummary = z.infer<typeof CommentThreadSummarySchema>;
|
|
|
216
216
|
* order a redacted row used to hold open no longer has anything to hold. The
|
|
217
217
|
* wire SHAPE is unchanged either way; what changed is which rows arrive.
|
|
218
218
|
*/
|
|
219
|
+
/**
|
|
220
|
+
* What the requesting actor may do to ONE thread.
|
|
221
|
+
*
|
|
222
|
+
* SERVER-COMPUTED, and that is the point. The app used to reconstruct this
|
|
223
|
+
* model client-side from doc-level signals plus the viewer's id, which is how
|
|
224
|
+
* the two surfaces came to disagree — the agent's read reported `canResolve:
|
|
225
|
+
* false` to actors who had a working Resolve button in the rail. One predicate,
|
|
226
|
+
* on the server, answering for every surface.
|
|
227
|
+
*
|
|
228
|
+
* Each flag ANDs the actor's subject-level band with the thread's own kind and
|
|
229
|
+
* status, so a flag is `true` only where the verb would actually be admitted.
|
|
230
|
+
* Two things it deliberately cannot express, both documented rather than
|
|
231
|
+
* papered over:
|
|
232
|
+
* - a LIVE ACCEPTANCE LEASE, which makes both decide verbs 409 while
|
|
233
|
+
* `suggestion.status` is still `open`; the claim columns are never
|
|
234
|
+
* projected, so `canDecideSuggestion: true` is "band and status permit",
|
|
235
|
+
* not "nobody else is mid-accept".
|
|
236
|
+
* - anything about OTHER threads; these are per-thread answers.
|
|
237
|
+
*/
|
|
238
|
+
export const ThreadCapabilitiesSchema = z.object({
|
|
239
|
+
/** Write a reply. Not conditioned on status — a reply reopens a resolved thread. */
|
|
240
|
+
canReply: z.boolean(),
|
|
241
|
+
/** Resolve it: an OPEN comment thread only; a suggestion is decided, not resolved. */
|
|
242
|
+
canResolve: z.boolean(),
|
|
243
|
+
/** Reopen it: the exact complement of `canResolve`. */
|
|
244
|
+
canReopen: z.boolean(),
|
|
245
|
+
/** Accept or reject an OPEN suggestion — the edit band (ADR-BE-536). */
|
|
246
|
+
canDecideSuggestion: z.boolean(),
|
|
247
|
+
/**
|
|
248
|
+
* Withdraw one's OWN open suggestion. IDENTITY, not band: the author needs no
|
|
249
|
+
* edit capability, so this is true for authors that `canDecideSuggestion`
|
|
250
|
+
* refuses. Separate for exactly that reason — folded together, the stricter
|
|
251
|
+
* answer governed and a commenter-band author was told they could not close
|
|
252
|
+
* their own suggestion.
|
|
253
|
+
*/
|
|
254
|
+
canWithdraw: z.boolean(),
|
|
255
|
+
});
|
|
256
|
+
export type ThreadCapabilities = z.infer<typeof ThreadCapabilitiesSchema>;
|
|
257
|
+
|
|
219
258
|
export const CommentThreadSchema = CommentThreadSummarySchema.extend({
|
|
220
259
|
comments: z.array(CommentSchema),
|
|
260
|
+
/**
|
|
261
|
+
* REQUIRED. Every route that returns a thread WITH its comments computes it —
|
|
262
|
+
* the list read and the create; the mutation routes return a
|
|
263
|
+
* `CommentThreadSummary`, which carries no comments and no capabilities.
|
|
264
|
+
*
|
|
265
|
+
* Not optional, deliberately. An optional block means a client keeps a local
|
|
266
|
+
* derivation for when it is absent, and that second opinion is the precise
|
|
267
|
+
* thing this field exists to delete: the app and the agent-facing read had
|
|
268
|
+
* drifted into disagreeing about the same thread.
|
|
269
|
+
*/
|
|
270
|
+
capabilities: ThreadCapabilitiesSchema,
|
|
221
271
|
});
|
|
222
272
|
export type CommentThread = z.infer<typeof CommentThreadSchema>;
|
|
223
273
|
|
package/src/content/schemas.ts
CHANGED
|
@@ -202,6 +202,29 @@ export const CompanyMdDocResponseSchema = z.object({
|
|
|
202
202
|
* takes the major bump.
|
|
203
203
|
*/
|
|
204
204
|
canComment: z.boolean(),
|
|
205
|
+
/**
|
|
206
|
+
* SUGGESTION-DECIDE capability signal: whether the requesting actor holds
|
|
207
|
+
* `CompanyMd.CanEdit` on this doc as `assertCanDecideSuggestion` evaluates it
|
|
208
|
+
* — accepting or rejecting someone's suggestion arbitrates what the document
|
|
209
|
+
* will say, so it rides the edit band (PRD-00934 / ADR-BE-536).
|
|
210
|
+
*
|
|
211
|
+
* IT EXISTS BECAUSE `canEdit` IS NOT ONE ANSWER. The app used to derive the
|
|
212
|
+
* Accept/Reject affordance from `canEdit`, which is the BATCH path
|
|
213
|
+
* (`canUserEditDocBatch`): it subtracts docs whose projection is stale and
|
|
214
|
+
* adds owner / account-owner identity safeties. The gate the Accept button
|
|
215
|
+
* actually hits is `evaluate('CompanyMd.CanEdit')`, which has neither. The two
|
|
216
|
+
* disagree in both directions — a dead Accept button on a doc whose projection
|
|
217
|
+
* has not compiled, and a hidden affordance on one whose access shrank but has
|
|
218
|
+
* not recompiled. Same nominal policy, two implementations; this field is the
|
|
219
|
+
* one the gate uses, computed the way the gate computes it.
|
|
220
|
+
*
|
|
221
|
+
* REQUIRED, like `canComment` above and unlike the three sharing signals: an
|
|
222
|
+
* absent value would mean "unknown", and a client that invents a default for
|
|
223
|
+
* an authority question has replaced the server's answer with a guess. The
|
|
224
|
+
* single-doc read is the only producer of this shape and always computes it.
|
|
225
|
+
* Signal only; admittance and enforcement are unchanged.
|
|
226
|
+
*/
|
|
227
|
+
canDecideSuggestion: z.boolean(),
|
|
205
228
|
/**
|
|
206
229
|
* GOVERNANCE capability signal (ADR-BE-435): whether the requesting actor holds
|
|
207
230
|
* `CompanyMd.CanChangeVisibility` (owner OR the `org.manage_content_sharing`
|
|
@@ -243,6 +243,27 @@ describe("EXECUTION_KINDS golden snapshot", () => {
|
|
|
243
243
|
templateId: "companyMd.commentPass",
|
|
244
244
|
},
|
|
245
245
|
},
|
|
246
|
+
"companyMd.commentAction": {
|
|
247
|
+
kind: "companyMd.commentAction",
|
|
248
|
+
domain: "data",
|
|
249
|
+
display: {
|
|
250
|
+
label: "Apply Comment Action",
|
|
251
|
+
pastTenseLabel: "Comment action applied",
|
|
252
|
+
icon: "pencil",
|
|
253
|
+
},
|
|
254
|
+
governance: {
|
|
255
|
+
visibility: "user",
|
|
256
|
+
requiresAdmin: false,
|
|
257
|
+
},
|
|
258
|
+
ui: {
|
|
259
|
+
showInAdmin: false,
|
|
260
|
+
showInTimeline: true,
|
|
261
|
+
confirmBeforeRun: true,
|
|
262
|
+
},
|
|
263
|
+
explanation: {
|
|
264
|
+
templateId: "companyMd.commentAction",
|
|
265
|
+
},
|
|
266
|
+
},
|
|
246
267
|
"semantic.transform": {
|
|
247
268
|
kind: "semantic.transform",
|
|
248
269
|
domain: "organization",
|
|
@@ -300,12 +321,14 @@ describe("ExecutionKind exhaustiveness", () => {
|
|
|
300
321
|
});
|
|
301
322
|
|
|
302
323
|
it("every kind the backend dispatches is a union member, not a call-site cast", () => {
|
|
303
|
-
// These
|
|
304
|
-
//
|
|
324
|
+
// These previously ran — or, for the comment ACTION, previewed a confirmable
|
|
325
|
+
// intent that then routed nowhere — while cast to ExecutionKind at their
|
|
326
|
+
// call sites, which left the registry incomplete over what actually executes.
|
|
305
327
|
for (const kind of [
|
|
306
328
|
"companyMd.commentPass",
|
|
307
329
|
"semantic.transform",
|
|
308
330
|
"companyMd.importContextDoc",
|
|
331
|
+
"companyMd.commentAction",
|
|
309
332
|
]) {
|
|
310
333
|
expect(isValidExecutionKind(kind)).toBe(true);
|
|
311
334
|
expect(CONFIRMATION_LABELS[kind as ExecutionKind]).toBeTruthy();
|
package/src/execution/kinds.ts
CHANGED
|
@@ -55,4 +55,12 @@ export type ExecutionKind =
|
|
|
55
55
|
| "semantic.transform"
|
|
56
56
|
/** Already RUNS today, previously cast at the call site. Creates a context
|
|
57
57
|
* doc from submitted text and links it under a destination doc. */
|
|
58
|
-
| "companyMd.importContextDoc"
|
|
58
|
+
| "companyMd.importContextDoc"
|
|
59
|
+
/** ONE collaboration write on a company.md thread — resolve, reopen, reply,
|
|
60
|
+
* or decide a suggestion. Distinct from `companyMd.commentPass`: the pass is
|
|
61
|
+
* a reviewed BUNDLE gated on the whole rail's staleness token, which is the
|
|
62
|
+
* wrong gate for a single act — an unrelated new comment elsewhere on the
|
|
63
|
+
* document would refuse it. Promoted out of a call-site cast for the same
|
|
64
|
+
* reason `semantic.transform` was: a kind that runs but is absent from the
|
|
65
|
+
* registry is invisible to every reader of it. */
|
|
66
|
+
| "companyMd.commentAction";
|
|
@@ -271,6 +271,29 @@ export const EXECUTION_KINDS = {
|
|
|
271
271
|
templateId: "companyMd.commentPass",
|
|
272
272
|
},
|
|
273
273
|
},
|
|
274
|
+
"companyMd.commentAction": {
|
|
275
|
+
kind: "companyMd.commentAction",
|
|
276
|
+
domain: "data",
|
|
277
|
+
display: {
|
|
278
|
+
label: "Apply Comment Action",
|
|
279
|
+
pastTenseLabel: "Comment action applied",
|
|
280
|
+
icon: "pencil",
|
|
281
|
+
},
|
|
282
|
+
governance: {
|
|
283
|
+
visibility: "user",
|
|
284
|
+
requiresAdmin: false,
|
|
285
|
+
},
|
|
286
|
+
ui: {
|
|
287
|
+
showInAdmin: false,
|
|
288
|
+
showInTimeline: true,
|
|
289
|
+
// A single collaboration write still changes what the conversation says,
|
|
290
|
+
// and resolving or deciding is not silently reversible for other readers.
|
|
291
|
+
confirmBeforeRun: true,
|
|
292
|
+
},
|
|
293
|
+
explanation: {
|
|
294
|
+
templateId: "companyMd.commentAction",
|
|
295
|
+
},
|
|
296
|
+
},
|
|
274
297
|
"semantic.transform": {
|
|
275
298
|
kind: "semantic.transform",
|
|
276
299
|
domain: "organization",
|
|
@@ -98,8 +98,16 @@ export interface FileClusterBaseline {
|
|
|
98
98
|
stripSuffixes?: string[];
|
|
99
99
|
/** Role words to strip (e.g., 'Service', 'Handler') */
|
|
100
100
|
stripRoleWords?: string[];
|
|
101
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* Directories to ignore, **in addition to** the guard's always-ignored set
|
|
103
|
+
* (`node_modules`, `dist`, `.git`, `coverage`). This list extends the
|
|
104
|
+
* defaults; it cannot remove one (ADR-CI-215).
|
|
105
|
+
*/
|
|
102
106
|
ignoredDirectories?: string[];
|
|
107
|
+
/** Extensions the directory walk collects (default: `['.ts', '.tsx']`) */
|
|
108
|
+
extensions?: string[];
|
|
109
|
+
/** When true, violations are errors and annotations require a `reason:` suffix */
|
|
110
|
+
strict?: boolean;
|
|
103
111
|
}
|
|
104
112
|
|
|
105
113
|
/**
|
|
@@ -116,6 +124,24 @@ export interface SubdirectoryAffinityBaseline {
|
|
|
116
124
|
excludePatterns?: RegExp[];
|
|
117
125
|
/** Importer patterns to ignore */
|
|
118
126
|
ignoredImporterPatterns?: RegExp[];
|
|
119
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Directories to ignore, **in addition to** the guard's always-ignored set
|
|
129
|
+
* (`node_modules`, `dist`, `.git`, `coverage`). This list extends the
|
|
130
|
+
* defaults; it cannot remove one (ADR-CI-215).
|
|
131
|
+
*/
|
|
120
132
|
ignoredDirectories?: string[];
|
|
133
|
+
/**
|
|
134
|
+
* Extensions the directory walk collects (default: `['.ts', '.tsx']`).
|
|
135
|
+
* Import RESOLUTION is governed by the guard's own module-graph extension
|
|
136
|
+
* list, not by this one.
|
|
137
|
+
*/
|
|
138
|
+
extensions?: string[];
|
|
139
|
+
/**
|
|
140
|
+
* Import alias prefix to repo-relative root, e.g. `{ '@/': 'src/' }`.
|
|
141
|
+
* Omit it and only relative specifiers resolve, which is what the guard did
|
|
142
|
+
* before ADR-CI-214. The trailing slash on both halves is load-bearing.
|
|
143
|
+
*/
|
|
144
|
+
aliases?: Record<string, string>;
|
|
145
|
+
/** When true, violations are errors and annotations require a `reason:` suffix */
|
|
146
|
+
strict?: boolean;
|
|
121
147
|
}
|
|
@@ -123,6 +123,7 @@ describe("CONFIRMATION_LABELS", () => {
|
|
|
123
123
|
"member.changeManager": "Change Reporting Manager",
|
|
124
124
|
"companyMd.ingest": "Ingest Company Knowledge",
|
|
125
125
|
"companyMd.commentPass": "Apply Comment Pass",
|
|
126
|
+
"companyMd.commentAction": "Apply Comment Action",
|
|
126
127
|
"semantic.transform": "Apply Reporting Transformation",
|
|
127
128
|
"companyMd.importContextDoc": "Import Context Document",
|
|
128
129
|
});
|
|
@@ -48,6 +48,7 @@ export const CONFIRMATION_LABELS: Record<ExecutionKind, string> = {
|
|
|
48
48
|
"member.changeManager": "Change Reporting Manager",
|
|
49
49
|
"companyMd.ingest": "Ingest Company Knowledge",
|
|
50
50
|
"companyMd.commentPass": "Apply Comment Pass",
|
|
51
|
+
"companyMd.commentAction": "Apply Comment Action",
|
|
51
52
|
"semantic.transform": "Apply Reporting Transformation",
|
|
52
53
|
"companyMd.importContextDoc": "Import Context Document",
|
|
53
54
|
};
|