Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1078,5 +1078,5 @@
}
],
"marker": "{/* GENERATED FILE: DO NOT EDIT. Regenerate via `pnpm run docapi:sync`. */}",
"sourceHash": "5f439c4117bbb2e55f227e7711df415c1173f8c476954c6e412a4b8b45edd1a3"
"sourceHash": "608536b99749f3a5a1988fa9d39bfafb41aa3814485b64df6812050db98e9d0f"
}
5 changes: 3 additions & 2 deletions apps/docs/document-api/reference/comments/get.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Returns a CommentInfo object with the comment text, author, date, and thread met
| `trackedChangeLink` | CommentTrackedChangeLink \| null | no | One of: CommentTrackedChangeLink, null |
| `trackedChangeStory` | StoryLocator \| null | no | One of: StoryLocator, null |
| `trackedChangeText` | string \| null | no | |
| `trackedChangeType` | enum | no | `"insert"`, `"delete"`, `"replacement"`, `"format"` |
| `trackedChangeType` | enum | no | `"insert"`, `"delete"`, `"replacement"`, `"format"`, `"structural"` |

### Example response

Expand Down Expand Up @@ -206,7 +206,8 @@ Returns a CommentInfo object with the comment text, author, date, and thread met
"insert",
"delete",
"replacement",
"format"
"format",
"structural"
]
}
},
Expand Down
3 changes: 2 additions & 1 deletion apps/docs/document-api/reference/comments/list.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ Returns a CommentsListResult with an array of comment threads and total count.
"insert",
"delete",
"replacement",
"format"
"format",
"structural"
]
}
},
Expand Down
3 changes: 2 additions & 1 deletion apps/docs/document-api/reference/extract.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,8 @@ _No fields._
"insert",
"delete",
"replacement",
"format"
"format",
"structural"
],
"type": "string"
},
Expand Down
4 changes: 4 additions & 0 deletions apps/docs/document-api/reference/track-changes/decide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ Returns a Receipt confirming the decision was applied; reports NO_OP if the chan
"id": {
"type": "string"
},
"range": {
"description": "Partial-range qualifier on an id target. Rejected with INVALID_INPUT for indivisible (e.g. structural) revisions.",
"type": "object"
},
"story": {
"$ref": "#/$defs/StoryLocator"
}
Expand Down
17 changes: 13 additions & 4 deletions apps/docs/document-api/reference/track-changes/get.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Retrieve a single tracked change by ID.

## Expected result

Returns a TrackChangeInfo object with the change type (`insert`, `delete`, `replacement`, `format`), author, date, affected content, and raw imported Word OOXML revision IDs (`w:id`) when available.
Returns a TrackChangeInfo object with the change type (`insert`, `delete`, `replacement`, `format`, `structural`), author, date, affected content, and raw imported Word OOXML revision IDs (`w:id`) when available. Structural changes (whole-table insert/delete) carry a `subtype` of `table-insert` or `table-delete`.

## Input fields

Expand Down Expand Up @@ -60,7 +60,8 @@ Returns a TrackChangeInfo object with the change type (`insert`, `delete`, `repl
| `id` | string | yes | |
| `insertedText` | string | no | |
| `pairedWithChangeId` | string \| null | no | |
| `type` | enum | yes | `"insert"`, `"delete"`, `"replacement"`, `"format"` |
| `subtype` | enum | no | `"table-insert"`, `"table-delete"` |
| `type` | enum | yes | `"insert"`, `"delete"`, `"replacement"`, `"format"`, `"structural"` |
| `wordRevisionIds` | object | no | |
| `wordRevisionIds.delete` | string | no | |
| `wordRevisionIds.format` | string | no | |
Expand All @@ -81,7 +82,7 @@ Returns a TrackChangeInfo object with the change type (`insert`, `delete`, `repl
},
"grouping": "standalone",
"id": "id-001",
"pairedWithChangeId": null,
"subtype": "table-insert",
"type": "insert"
}
```
Expand Down Expand Up @@ -161,12 +162,20 @@ Returns a TrackChangeInfo object with the change type (`insert`, `delete`, `repl
"null"
]
},
"subtype": {
"description": "Finer classification for structural changes (type === 'structural').",
"enum": [
"table-insert",
"table-delete"
]
},
"type": {
"enum": [
"insert",
"delete",
"replacement",
"format"
"format",
"structural"
]
},
"wordRevisionIds": {
Expand Down
21 changes: 15 additions & 6 deletions apps/docs/document-api/reference/track-changes/list.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ List all tracked changes in the document.

## Expected result

Returns a TrackChangesListResult with tracked change entries (`insert`, `delete`, `replacement`, `format`), total count, and raw imported Word OOXML revision IDs (`w:id`) when available.
Returns a TrackChangesListResult with tracked change entries (`insert`, `delete`, `replacement`, `format`, `structural`), total count, and raw imported Word OOXML revision IDs (`w:id`) when available. Structural changes (whole-table insert/delete) carry a `subtype` of `table-insert` or `table-delete`.

## Input fields

Expand All @@ -29,7 +29,7 @@ Returns a TrackChangesListResult with tracked change entries (`insert`, `delete`
| `in` | StoryLocator \| `"all"` | no | One of: StoryLocator, `"all"` |
| `limit` | integer | no | |
| `offset` | integer | no | |
| `type` | enum | no | `"insert"`, `"delete"`, `"replacement"`, `"format"` |
| `type` | enum | no | `"insert"`, `"delete"`, `"replacement"`, `"format"`, `"structural"` |

### Example request

Expand Down Expand Up @@ -75,7 +75,7 @@ Returns a TrackChangesListResult with tracked change entries (`insert`, `delete`
"targetKind": "text"
},
"id": "id-001",
"pairedWithChangeId": null,
"subtype": "table-insert",
"type": "insert"
}
],
Expand Down Expand Up @@ -123,12 +123,13 @@ Returns a TrackChangesListResult with tracked change entries (`insert`, `delete`
"type": "integer"
},
"type": {
"description": "Filter by change type: 'insert', 'delete', 'replacement', or 'format'.",
"description": "Filter by change type: 'insert', 'delete', 'replacement', 'format', or 'structural'.",
"enum": [
"insert",
"delete",
"replacement",
"format"
"format",
"structural"
]
}
},
Expand Down Expand Up @@ -192,12 +193,20 @@ Returns a TrackChangesListResult with tracked change entries (`insert`, `delete`
"null"
]
},
"subtype": {
"description": "Finer classification for structural changes (type === 'structural').",
"enum": [
"table-insert",
"table-delete"
]
},
"type": {
"enum": [
"insert",
"delete",
"replacement",
"format"
"format",
"structural"
]
},
"wordRevisionIds": {
Expand Down
9 changes: 7 additions & 2 deletions apps/mcp/src/generated/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2442,9 +2442,9 @@ export const MCP_TOOL_CATALOG = {
"Number of tracked changes to skip for pagination. Only for action 'list'. Omit for other actions.",
},
type: {
enum: ['insert', 'delete', 'replacement', 'format'],
enum: ['insert', 'delete', 'replacement', 'format', 'structural'],
description:
"Filter by change type: 'insert', 'delete', 'replacement', or 'format'. Only for action 'list'. Omit for other actions.",
"Filter by change type: 'insert', 'delete', 'replacement', 'format', or 'structural'. Only for action 'list'. Omit for other actions.",
},
force: {
type: 'boolean',
Expand All @@ -2471,6 +2471,11 @@ export const MCP_TOOL_CATALOG = {
story: {
$ref: '#/$defs/StoryLocator',
},
range: {
type: 'object',
description:
'Partial-range qualifier on an id target. Rejected with INVALID_INPUT for indivisible (e.g. structural) revisions.',
},
},
additionalProperties: false,
required: ['id'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,19 @@ describe('reference docs artifacts', () => {
const trackedChangeGet = artifacts.get('apps/docs/document-api/reference/track-changes/get.mdx');
expect(trackedChangeGet).toBeDefined();
expect(trackedChangeGet!).toContain('| `pairedWithChangeId` | string \\| null | no | |');
expect(trackedChangeGet!).toContain('"pairedWithChangeId": null');

const trackedChangeList = artifacts.get('apps/docs/document-api/reference/track-changes/list.mdx');
expect(trackedChangeList).toBeDefined();
expect(trackedChangeList!).toContain('| `in` | StoryLocator \\| `"all"` | no | One of: StoryLocator, `"all"` |');
expect(trackedChangeList!).toContain('"pairedWithChangeId": null');

// Nullable-primitive example values are rendered as `null`. (track-changes
// examples no longer surface a nullable primitive once `subtype` joined the
// optional-field budget, so assert this on header-footers/get, which still
// surfaces the nullable `refId` in its generated example.)
const headerFooterGet = artifacts.get('apps/docs/document-api/reference/header-footers/get.mdx');
expect(headerFooterGet).toBeDefined();
expect(headerFooterGet!).toContain('| `refId` | string \\| null | no | |');
expect(headerFooterGet!).toContain('"refId": null');

const commentsGet = artifacts.get('apps/docs/document-api/reference/comments/get.mdx');
expect(commentsGet).toBeDefined();
Expand Down
11 changes: 11 additions & 0 deletions packages/document-api/scripts/lib/reference-docs-artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
* break unquoted YAML scalars (colons, hash signs, brackets, etc.).
*/
function yamlQuote(value: string): string {
if (/[:#\[\]{}&*!|>'"%@`]/u.test(value)) {

Check warning on line 71 in packages/document-api/scripts/lib/reference-docs-artifacts.ts

View workflow job for this annotation

GitHub Actions / build

Unnecessary escape character: \[
return `"${value.replace(/\\/gu, '\\\\').replace(/"/gu, '\\"')}"`;
}
return value;
Expand Down Expand Up @@ -1016,6 +1016,17 @@
snapshot: ReturnType<typeof buildContractSnapshot>,
): { input: unknown; output: unknown } {
const inputOverrides: Partial<Record<ContractOperationSnapshot['operationId'], unknown>> = {
// The id-target variant carries an optional `range` qualifier used only to
// fail closed (INVALID_INPUT) on indivisible revisions. A canonical id
// decision does NOT pass it, so the auto-generated example's `"range": {}`
// is misleading — pin an explicit clean id-target example here.
'trackChanges.decide': {
decision: 'accept',
target: {
id: 'id-001',
story: { kind: 'story', storyType: 'body' },
},
},
insert: {
target: {
kind: 'block',
Expand Down
4 changes: 2 additions & 2 deletions packages/document-api/src/contract/operation-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2481,7 +2481,7 @@ export const OPERATION_DEFINITIONS = {
memberPath: 'trackChanges.list',
description: 'List all tracked changes in the document.',
expectedResult:
'Returns a TrackChangesListResult with tracked change entries (`insert`, `delete`, `replacement`, `format`), total count, and raw imported Word OOXML revision IDs (`w:id`) when available.',
'Returns a TrackChangesListResult with tracked change entries (`insert`, `delete`, `replacement`, `format`, `structural`), total count, and raw imported Word OOXML revision IDs (`w:id`) when available. Structural changes (whole-table insert/delete) carry a `subtype` of `table-insert` or `table-delete`.',
requiresDocumentContext: true,
metadata: readOperation({
idempotency: 'idempotent',
Expand All @@ -2496,7 +2496,7 @@ export const OPERATION_DEFINITIONS = {
memberPath: 'trackChanges.get',
description: 'Retrieve a single tracked change by ID.',
expectedResult:
'Returns a TrackChangeInfo object with the change type (`insert`, `delete`, `replacement`, `format`), author, date, affected content, and raw imported Word OOXML revision IDs (`w:id`) when available.',
'Returns a TrackChangeInfo object with the change type (`insert`, `delete`, `replacement`, `format`, `structural`), author, date, affected content, and raw imported Word OOXML revision IDs (`w:id`) when available. Structural changes (whole-table insert/delete) carry a `subtype` of `table-insert` or `table-delete`.',
requiresDocumentContext: true,
metadata: readOperation({
idempotency: 'idempotent',
Expand Down
29 changes: 26 additions & 3 deletions packages/document-api/src/contract/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

type JsonSchema = Record<string, unknown>;

const trackChangeTypeValues = ['insert', 'delete', 'replacement', 'format'] as const;
const trackChangeTypeValues = ['insert', 'delete', 'replacement', 'format', 'structural'] as const;

/** JSON Schema descriptors for a single operation's input, output, and result variants. */
export interface OperationSchemaSet {
Expand Down Expand Up @@ -644,7 +644,7 @@
const entityAddressSchema = ref('EntityAddress');
const selectionTargetSchema = ref('SelectionTarget');
const commentTrackedChangeLinkSchema = ref('CommentTrackedChangeLink');
const targetLocatorSchema = ref('TargetLocator');

Check warning on line 647 in packages/document-api/src/contract/schemas.ts

View workflow job for this annotation

GitHub Actions / build

'targetLocatorSchema' is assigned a value but never used. Allowed unused vars must match /^_/u
const deleteBehaviorSchema = ref('DeleteBehavior');
const resolvedHandleSchema = ref('ResolvedHandle');
const pageInfoSchema = ref('PageInfo');
Expand Down Expand Up @@ -926,7 +926,7 @@
text: { type: 'string' },
});

const nodeInfoSchema: JsonSchema = {

Check warning on line 929 in packages/document-api/src/contract/schemas.ts

View workflow job for this annotation

GitHub Actions / build

'nodeInfoSchema' is assigned a value but never used. Allowed unused vars must match /^_/u
type: 'object',
required: ['nodeType', 'kind'],
properties: {
Expand All @@ -942,7 +942,7 @@
additionalProperties: false,
};

const matchContextSchema = objectSchema(

Check warning on line 945 in packages/document-api/src/contract/schemas.ts

View workflow job for this annotation

GitHub Actions / build

'matchContextSchema' is assigned a value but never used. Allowed unused vars must match /^_/u
{
address: nodeAddressSchema,
snippet: { type: 'string' },
Expand All @@ -953,7 +953,7 @@
['address', 'snippet', 'highlightRange'],
);

const unknownNodeDiagnosticSchema = objectSchema(

Check warning on line 956 in packages/document-api/src/contract/schemas.ts

View workflow job for this annotation

GitHub Actions / build

'unknownNodeDiagnosticSchema' is assigned a value but never used. Allowed unused vars must match /^_/u
{
message: { type: 'string' },
address: nodeAddressSchema,
Expand Down Expand Up @@ -1560,6 +1560,10 @@
address: trackedChangeAddressSchema,
id: { type: 'string' },
type: { enum: [...trackChangeTypeValues] },
subtype: {
enum: ['table-insert', 'table-delete'],
description: "Finer classification for structural changes (type === 'structural').",
},
grouping: { enum: ['standalone', 'replacement-pair', 'unknown'] },
pairedWithChangeId: { type: ['string', 'null'] },
wordRevisionIds: trackChangeWordRevisionIdsSchema,
Expand All @@ -1578,6 +1582,10 @@
{
address: trackedChangeAddressSchema,
type: { enum: [...trackChangeTypeValues] },
subtype: {
enum: ['table-insert', 'table-delete'],
description: "Finer classification for structural changes (type === 'structural').",
},
grouping: { enum: ['standalone', 'replacement-pair', 'unknown'] },
pairedWithChangeId: { type: ['string', 'null'] },
wordRevisionIds: trackChangeWordRevisionIdsSchema,
Expand Down Expand Up @@ -5028,7 +5036,7 @@
offset: { type: 'integer', description: 'Number of tracked changes to skip for pagination.' },
type: {
enum: [...trackChangeTypeValues],
description: "Filter by change type: 'insert', 'delete', 'replacement', or 'format'.",
description: "Filter by change type: 'insert', 'delete', 'replacement', 'format', or 'structural'.",
},
in: {
oneOf: [storyLocatorSchema, { const: 'all' }],
Expand All @@ -5049,7 +5057,22 @@
decision: { enum: ['accept', 'reject'] },
target: {
oneOf: [
objectSchema({ id: { type: 'string' }, story: storyLocatorSchema }, ['id']),
objectSchema(
{
id: { type: 'string' },
story: storyLocatorSchema,
// A partial-range qualifier on an entity (id) target. Accepted by
// the schema so the executor can fail closed with INVALID_INPUT
// on indivisible (e.g. structural whole-object) revisions rather
// than the runtime rejecting it as a malformed target.
range: {
type: 'object',
description:
'Partial-range qualifier on an id target. Rejected with INVALID_INPUT for indivisible (e.g. structural) revisions.',
},
},
['id'],
),
objectSchema(
{
kind: { const: 'range' },
Expand Down
14 changes: 14 additions & 0 deletions packages/document-api/src/track-changes/track-changes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,18 @@ describe('executeTrackChangesDecide validation', () => {
} as any),
).toThrow(/exactly one/);
});

it('fails closed with INVALID_INPUT for a partial-range qualifier on an id target', () => {
const adapter = stubAdapter();
const result = executeTrackChangesDecide(adapter, {
decision: 'accept',
target: { id: 'tc1', range: { kind: 'partial', start: 0, end: 2 } } as any,
});
expect(result.success).toBe(false);
if (!result.success) {
expect(result.failure.code).toBe('INVALID_INPUT');
}
// The whole change must not be resolved as a side effect.
expect(adapter.accept).not.toHaveBeenCalled();
});
});
17 changes: 17 additions & 0 deletions packages/document-api/src/track-changes/track-changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,23 @@ export function executeTrackChangesDecide(
{ field: 'target.story', value: rawStory },
);
}
// A partial-range qualifier on an entity (id) target requests a partial
// decision on a single logical change. Indivisible changes (structural
// whole-object revisions per spec §8/§9/§19) must fail closed and leave the
// document unmutated. No divisible-by-id-range path is fixture-backed yet,
// so reject the qualifier here with INVALID_INPUT rather than silently
// resolving the whole change.
if (target.range !== undefined) {
return {
success: false,
failure: {
code: 'INVALID_INPUT',
message:
'trackChanges.decide does not support a partial range on an id target; the change is not safely divisible.',
details: { target: input.target },
},
};
}
if (typeof target.id !== 'string' || target.id.length === 0) {
throw new DocumentApiValidationError('INVALID_TARGET', 'trackChanges.decide id targets require a non-empty id.', {
field: 'target',
Expand Down
12 changes: 11 additions & 1 deletion packages/document-api/src/types/track-changes.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import type { TrackedChangeAddress } from './address.js';
import type { DiscoveryOutput } from './discovery.js';
import type { StoryLocator } from './story.types.js';

export type TrackChangeType = 'insert' | 'delete' | 'replacement' | 'format';
export type TrackChangeType = 'insert' | 'delete' | 'replacement' | 'format' | 'structural';
/**
* Finer classification for structural (`type === 'structural'`) tracked
* changes. `table-insert` / `table-delete` describe a whole-table insert or
* delete revision.
*/
export type TrackChangeSubtype = 'table-insert' | 'table-delete';
export type TrackChangeOverlapRelationship = 'parent' | 'child' | 'standalone';
export type TrackChangeGrouping = 'standalone' | 'replacement-pair' | 'unknown';
export type TrackChangeProvenanceOrigin = 'word' | 'google-docs' | 'superdoc' | 'custom' | 'unknown';
Expand Down Expand Up @@ -47,6 +53,8 @@ export interface TrackChangeInfo {
/** Convenience alias for `address.entityId`. */
id: string;
type: TrackChangeType;
/** Finer classification for structural changes (e.g. `table-insert`). */
subtype?: TrackChangeSubtype;
grouping?: TrackChangeGrouping;
pairedWithChangeId?: string | null;
/** Raw imported Word OOXML revision IDs (`w:id`) from the source document when available. */
Expand Down Expand Up @@ -87,6 +95,8 @@ export interface TrackChangesListQuery {
export interface TrackChangeDomain {
address: TrackedChangeAddress;
type: TrackChangeType;
/** Finer classification for structural changes (e.g. `table-insert`). */
subtype?: TrackChangeSubtype;
grouping?: TrackChangeGrouping;
pairedWithChangeId?: string | null;
/** Raw imported Word OOXML revision IDs (`w:id`) from the source document when available. */
Expand Down
Loading
Loading