@chalksurf/cli 0.3.6 → 0.4.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/docs/manifest.md CHANGED
@@ -1,174 +1,21 @@
1
- # Manifest Reference
1
+ # Content manifests
2
2
 
3
- ChalkSurf uses JSON manifests for batch and agent-driven imports.
3
+ Use `--manifest FILE` for repeated content operations. Files and stdin (`--manifest -`) are limited to 16 MiB. A manifest is a strict object containing `schemaVersion: "v1"`, a canonical dot-separated `operation`, and `items`. Every item has a unique `itemId`; explicit target IDs must also be unique. Do not put profile, organization, wait, timeout, request ID, or artifact settings in the manifest. Competing flags or positional targets are rejected.
4
4
 
5
- One manifest file is passed to exactly one command:
5
+ Local paths resolve relative to the manifest file. Stdin paths resolve relative to the current directory. Sources use `kind: "local"` with `path`, `kind: "directory"` with `path`, or `kind: "url"` with an HTTP(S) `url`. Optional `sourceId` identifies a source within its ordered group; `relativePath` sets a file's logical path, and `relativeRoot` applies to directory sources. File order matters. Duplicate source IDs within a group are rejected.
6
6
 
7
- - `chalksurf sheet import --manifest <path|->`
8
- - `chalksurf exercise import --manifest <path|->`
9
- - `chalksurf exercise import-solution --manifest <path|->`
10
- - `chalksurf sheet import-solutions --manifest <path|->`
7
+ Exercise imports use optional `exerciseSheetId`, `sources`, and optional `targetLanguages`. Exercise solution imports require `exerciseId`; sheet solution imports require `exerciseSheetId`. Authoritative sheet translation imports require `exerciseSheetId`, `sourceLanguage`, `targetLanguage`, and `sources`. Target languages are the existing English enum names, such as `english` and `hungarian`.
11
8
 
12
- Use `--manifest -` to pipe JSON on stdin.
9
+ A `sheet.import` item describes one logical group using exactly one of:
13
10
 
14
- ## Common Fields
11
+ - `sources`, optional `targetFolderPath`, `title`, and `targetLanguages`.
12
+ - `sources` and `components`, each with a unique `componentId`, a `description` identifying the section, and optional destination/title/target languages.
13
+ - `base: {language, sources}` and `translations: [{language, sources}]`, with optional destination/title. Authoritative groups require explicit files or URLs, preserve language-local order, and accept no source roles.
15
14
 
16
- Every manifest is a JSON object with these shared metadata fields:
15
+ The CLI accepts at most 20 logical sheet import groups and 100 known work units after component/language expansion. It never truncates inputs or implicitly splits an oversized atomic operation. Directory extraction and AI exercise counts remain unknown in preflight; no remote source is downloaded by `--dry-run`.
17
16
 
18
- | Field | Type | Required | Notes |
19
- | --- | --- | --- | --- |
20
- | `organizationId` | string | no | Overrides the default organization for this invocation. |
21
- | `wait` | boolean | no | Behaves like `--wait`. The CLI also accepts `--wait`, which wins if set. Requested translations run as follow-up jobs listed in the import result. |
17
+ Examples in [examples](examples/) use this v1 contract. Old top-level `sheets`/`sources`, manifest organization/wait controls, and `translateTo` fields are rejected. Use `targetLanguages` in manifests and repeatable `--target-language` in direct commands.
22
18
 
23
- Every source object supports these common fields. For `sheet import`, sources appear inside `sheets[].sources[]`. For the exercise import commands and `sheet import-solutions`, they appear in top-level `sources[]`.
19
+ Import results have `items` and `summary`; each item retains its identity and accepted jobs. Follow-up details and extracted resources appear in `observation.jobs` when waiting. Request/attempt evidence supports an intentional exact resend. Read [content operations](content-operations.md) before recovering an uncertain acceptance; never blindly reimport successful siblings.
24
20
 
25
- | Field | Type | Required | Notes |
26
- | --- | --- | --- | --- |
27
- | `sourceId` | string | no | Optional stable identifier for correlating source inputs to results. Must be unique within the manifest. |
28
- | `kind` | `local` \| `directory` \| `url` | yes | Selects the source shape. |
29
-
30
- ### Source Kinds
31
-
32
- `local`
33
-
34
- | Field | Type | Required | Notes |
35
- | --- | --- | --- | --- |
36
- | `path` | string | yes | Local file path relative to `cwd` unless already absolute. |
37
- | `relativePath` | string | no | Stored path shown to ChalkSurf. Defaults to the basename of `path`. |
38
-
39
- `directory`
40
-
41
- | Field | Type | Required | Notes |
42
- | --- | --- | --- | --- |
43
- | `path` | string | yes | Directory to expand recursively. |
44
- | `relativeRoot` | string | no | Base directory used to compute stored relative paths. Defaults to `path`. |
45
-
46
- `url`
47
-
48
- | Field | Type | Required | Notes |
49
- | --- | --- | --- | --- |
50
- | `url` | string | yes | HTTP(S) URL to download before import. |
51
- | `relativePath` | string | no | Stored path shown to ChalkSurf. Defaults to the filename derived from the URL. |
52
-
53
- ## Command-Specific Fields
54
-
55
- ### Sheet Import
56
-
57
- Top-level fields:
58
-
59
- | Field | Type | Required | Notes |
60
- | --- | --- | --- | --- |
61
- | `sheets` | array | yes | One or more sheets to import. Each sheet may contain one or more source files. |
62
-
63
- Normal sheet fields:
64
-
65
- | Field | Type | Required | Notes |
66
- | --- | --- | --- | --- |
67
- | `targetFolderPath` | string \| null | yes | Destination folder for the resulting sheet. Use `null` for the root folder. |
68
- | `sources` | array | yes | One or more source files that belong to this sheet, in import order. |
69
- | `title` | string | no | Overrides the imported sheet title. |
70
- | `translateTo` | string[] | no | Target translation languages. Must be unique and non-empty when present. |
71
-
72
- Sheet import metadata is sheet-level, not source-level. Do not put `title` or `translateTo` on individual sources inside `sheets[].sources[]`.
73
-
74
- Source-level `relativePath` is only a source label or filename override. It does not choose the destination folder for grouped sheet imports; `targetFolderPath` is the only destination field.
75
-
76
- Declared-component sheet fields:
77
-
78
- Use `components[]` when the same source group contains a known fixed set of component sheets. In this mode, do not put `targetFolderPath`, `title`, or `translateTo` on the sheet itself; put output metadata on each component.
79
-
80
- | Field | Type | Required | Notes |
81
- | --- | --- | --- | --- |
82
- | `sources` | array | yes | One or more source files that contain the declared components. |
83
- | `components` | array | yes | The explicitly requested component sheets to extract from the shared source group. |
84
-
85
- Component fields:
86
-
87
- | Field | Type | Required | Notes |
88
- | --- | --- | --- | --- |
89
- | `componentId` | string | yes | Stable key for this requested component. Must match `/^[a-zA-Z0-9_-]{1,80}$/` and be unique within the sheet group. |
90
- | `description` | string | yes | Matching instructions that identify this component in the source group. |
91
- | `targetFolderPath` | string \| null | no | Destination folder for this component. Omit or use `null` for the root folder. |
92
- | `title` | string | no | Overrides the imported component sheet title. |
93
- | `translateTo` | string[] | no | Target translation languages for this component. Must be unique and non-empty when present. |
94
-
95
- The model must return an explicit `import` or `not_present` decision for every declared `componentId`. Jobs complete when at least one component is imported and fail when all declared components are `not_present`.
96
-
97
- Canonical example:
98
-
99
- - [docs/examples/sheet-import-manifest.json](./examples/sheet-import-manifest.json)
100
- - [schemas/sheet-import-manifest.schema.json](../schemas/sheet-import-manifest.schema.json)
101
-
102
- ### Exercise Import
103
-
104
- Top-level fields:
105
-
106
- | Field | Type | Required | Notes |
107
- | --- | --- | --- | --- |
108
- | `sources` | array | yes | One or more import sources. |
109
- | `exerciseSheetId` | string | no | Default target sheet for the imported exercises. Can still be overridden by `--sheet-id`. |
110
- | `translateTo` | string[] | no | Target translation languages for every imported exercise. Must be unique and non-empty when present. |
111
-
112
- Per-source fields:
113
-
114
- - Only the common source fields are allowed.
115
- - `title` and source-level `translateTo` are rejected for this command.
116
-
117
- Canonical example:
118
-
119
- - [docs/examples/exercise-import-manifest.json](./examples/exercise-import-manifest.json)
120
- - [schemas/exercise-import-manifest.schema.json](../schemas/exercise-import-manifest.schema.json)
121
-
122
- ### Exercise Solution Import
123
-
124
- Top-level fields:
125
-
126
- | Field | Type | Required | Notes |
127
- | --- | --- | --- | --- |
128
- | `sources` | array | yes | One or more import sources. |
129
- | `exerciseId` | string | no | Default target exercise for the imported solution files. The command still requires an exercise id overall, either here or positionally. |
130
-
131
- Per-source fields:
132
-
133
- - Only the common source fields are allowed.
134
- - `title` and `translateTo` are rejected for this command.
135
-
136
- Canonical example:
137
-
138
- - [docs/examples/exercise-solution-import-manifest.json](./examples/exercise-solution-import-manifest.json)
139
- - [schemas/exercise-solution-import-manifest.schema.json](../schemas/exercise-solution-import-manifest.schema.json)
140
-
141
- ### Exercise Sheet Solution Import
142
-
143
- Top-level fields:
144
-
145
- | Field | Type | Required | Notes |
146
- | --- | --- | --- | --- |
147
- | `sources` | array | yes | One or more source files containing solutions for some or all exercises in the target sheet. |
148
- | `exerciseSheetId` | string | no | Default target sheet for the imported solution files. The command still requires a sheet id overall, either here or positionally. |
149
-
150
- Per-source fields:
151
-
152
- - Only the common source fields are allowed.
153
- - `title` and `translateTo` are rejected for this command.
154
-
155
- Canonical example:
156
-
157
- - [docs/examples/exercise-sheet-solution-import-manifest.json](./examples/exercise-sheet-solution-import-manifest.json)
158
- - [schemas/exercise-sheet-solution-import-manifest.schema.json](../schemas/exercise-sheet-solution-import-manifest.schema.json)
159
-
160
- ## Validation Notes
161
-
162
- The CLI validates more than the JSON schema can express on its own:
163
-
164
- - `sourceId` values must be unique within one manifest.
165
- - Sheet import `translateTo` values must be unique within one sheet.
166
- - Sheet import component `componentId` values must be unique within one sheet group.
167
- - Sheet import components require non-empty descriptions.
168
- - Exercise import top-level `translateTo` values must be unique.
169
- - `relativePath` cannot be empty or contain `..`.
170
- - Sheet import `targetFolderPath` must be `null` or a normalized folder path without `.` or `..` segments.
171
- - `directory` imports must resolve to at least one file.
172
- - URL imports must use `http` or `https`.
173
-
174
- Treat the schema files as a reference format and the CLI as the final validator.
21
+ Import dry runs report SHA-256 fingerprints and byte counts for readable local files in `localSources`. These describe the observed local bytes, not future extraction results. Remote URL bytes remain unknown and are never downloaded by a dry run.
package/docs/manual.md CHANGED
@@ -36,7 +36,7 @@ Config resolution rules:
36
36
  - `--profile` overrides `CHALKSURF_PROFILE`, which overrides the stored default profile.
37
37
  - `--base-url` overrides `CHALKSURF_BASE_URL`, which overrides the active profile.
38
38
  - `CHALKSURF_TOKEN` overrides the active profile token.
39
- - `--organization` overrides manifest `organizationId`, which overrides `CHALKSURF_ORGANIZATION_ID`, which overrides the active profile.
39
+ - `--organization` overrides `CHALKSURF_ORGANIZATION_ID`, which overrides the active profile. Content manifests cannot select an organization.
40
40
 
41
41
  ## Organization Selection
42
42
 
@@ -77,7 +77,7 @@ Import one sheet, override its title, and request an English translation after i
77
77
  ```bash
78
78
  chalksurf sheet import ./fixtures/algebra.pdf \
79
79
  --title "OKTV 2014 Round 1" \
80
- --translate-to english \
80
+ --target-language english \
81
81
  --wait
82
82
  ```
83
83
 
@@ -95,14 +95,14 @@ Import exercises into an existing sheet:
95
95
  ```bash
96
96
  chalksurf exercise import ./fixtures/problem-set.pdf \
97
97
  --sheet-id 00000000-0000-4000-8000-000000000001 \
98
- --translate-to english \
98
+ --target-language english \
99
99
  --wait
100
100
  ```
101
101
 
102
102
  Import a solution into an existing exercise:
103
103
 
104
104
  ```bash
105
- chalksurf exercise import-solution \
105
+ chalksurf exercise solution import \
106
106
  00000000-0000-4000-8000-000000000001 \
107
107
  ./fixtures/solution.pdf \
108
108
  --wait
@@ -130,7 +130,7 @@ chalksurf quality-issue artifact reconcile --run-dir PATH --json
130
130
  Import a separate solution file for an existing exercise sheet:
131
131
 
132
132
  ```bash
133
- chalksurf sheet import-solutions \
133
+ chalksurf sheet solution import \
134
134
  00000000-0000-4000-8000-000000000001 \
135
135
  ./fixtures/sheet-solutions.pdf \
136
136
  --wait
@@ -151,7 +151,7 @@ Use a manifest for multi-source imports or when each resulting sheet needs expli
151
151
  chalksurf sheet import --manifest ./import.json --wait
152
152
  ```
153
153
 
154
- Sheet import manifests use top-level `sheets[]`. Each sheet has one destination `targetFolderPath`, optional `title` and `translateTo`, and one or more ordered `sources[]`.
154
+ Content manifests use `schemaVersion: "v1"`, `operation: "sheet.import"` and `items[]` with unique `itemId` values. Normal items have ordered `sources`, optional `targetFolderPath`, `title` and `targetLanguages`. Files resolve relative to the manifest file; stdin paths use the invocation directory. Component and authoritative language groups are described in the [manifest reference](./manifest.md).
155
155
 
156
156
  ## Working With Jobs
157
157
 
@@ -179,17 +179,25 @@ Wait on one or more jobs later:
179
179
  chalksurf job wait job_123 job_124
180
180
  ```
181
181
 
182
- `--wait` on an import command means the parsed content has been saved before the command exits. Requested translations run as separate jobs; use `--json` to capture their job IDs or `chalksurf job list` to inspect them later.
182
+ A successful `--wait` observes the primary import jobs and authoritative translation-import children. Automatically generated translations and metadata aftercare remain separate follow-ups; inspect `result.observation` and job IDs. A timeout, failed observation or interruption preserves known results and does not cancel remote work.
183
183
 
184
184
  Queue translations for existing resources:
185
185
 
186
186
  ```bash
187
- chalksurf exercise translate 00000000-0000-4000-8000-000000000001 --translate-to english
188
- chalksurf sheet translate 00000000-0000-4000-8000-000000000002 --translate-to english --source-language hungarian
187
+ chalksurf exercise translation generate 00000000-0000-4000-8000-000000000001 --target-language english
188
+ chalksurf sheet translation generate 00000000-0000-4000-8000-000000000002 --target-language english --source-language hungarian
189
189
  ```
190
190
 
191
191
  Add `--wait` to either translate command to wait for the queued translation job or jobs to finish.
192
192
 
193
+ Import an authoritative translation from source documents into an existing sheet:
194
+
195
+ ```bash
196
+ chalksurf sheet translation import 00000000-0000-4000-8000-000000000002 ./official-german-paper.pdf --source-language hungarian --target-language german --wait
197
+ ```
198
+
199
+ This accepts a complete ready source language and a distinct missing or failed target language. It starts a fresh import job; successful sibling translations remain untouched.
200
+
193
201
  ## Searching From The CLI
194
202
 
195
203
  Search commands default to `--ownership own`, which means resources in the selected organization.
@@ -245,8 +253,8 @@ chalksurf exercise get 00000000-0000-4000-8000-000000000002 --json
245
253
  Inspect sheet version history and one stored snapshot:
246
254
 
247
255
  ```bash
248
- chalksurf sheet versions 00000000-0000-4000-8000-000000000001 --json
249
- chalksurf sheet version 00000000-0000-4000-8000-000000000001 00000000-0000-4000-8000-000000000003 --json
256
+ chalksurf sheet version list 00000000-0000-4000-8000-000000000001 --json
257
+ chalksurf sheet version get 00000000-0000-4000-8000-000000000001 00000000-0000-4000-8000-000000000003 --json
250
258
  ```
251
259
 
252
260
  List sheet issues that are useful for repair workflows:
@@ -268,34 +276,28 @@ Patch an exercise with an optimistic update precondition:
268
276
  ```bash
269
277
  chalksurf exercise update 00000000-0000-4000-8000-000000000002 \
270
278
  --expected-updated-at 2026-01-01T00:00:00.000Z \
271
- --patch-json '{"translations":{"english":{"exercise_text":"Find $x^2$."}}}' \
272
- --json
279
+ --input - --json <<'JSON'
280
+ {"translations":{"english":{"exercise_text":"Find $x^2$."}}}
281
+ JSON
273
282
  ```
274
283
 
275
284
  Write commands return a receipt with the affected resource, changed paths, warnings, and operation-specific details. Fetch the resource first and use its current `updated_at` value for patch commands.
276
285
 
277
- Admin-only sheet-series labeling supports a two-step bulk flow:
286
+ Repeated sheet patches use a reviewed plan. Series-assignment changes retain their administrator requirement:
278
287
 
279
288
  ```bash
280
289
  chalksurf sheet series list --subject math --json
281
- chalksurf sheet bulk-update \
282
- --input batch-001.dry-run.json --result-file batch-001.dry-run.result.json --json
283
- chalksurf sheet bulk-update \
284
- --input batch-001.apply.json --result-file batch-001.apply.result.json --json
290
+ chalksurf sheet update --manifest batch-001.json --dry-run --plan-out batch-001.plan.json --json
291
+ chalksurf plan show batch-001.plan.json --json
292
+ chalksurf plan apply batch-001.plan.json --result-file batch-001.apply.json --json
293
+ chalksurf plan verify batch-001.plan.json --result-file batch-001.verify.json --json
285
294
  ```
286
295
 
287
- Use `--input -` to read the object from stdin, or `--input-json` for short inline JSON. Exactly one input option is required. The exact same update list must be submitted in both calls; only change `mode` and add the returned `confirmationDigest`. A batch contains at most 100 sheets, applies atomically, and cannot update sheets outside the selected organization.
296
+ A saved plan binds the exact request, environment, organization, targets and effects. Updates to multiple sheets remain atomic; exercise updates retain explicit best-effort outcomes. `--plan-out` requires `--dry-run`. Only updates and solution/description generation support executable plans. A result envelope is not a plan, and old bulk-update envelopes must be regenerated from the original input.
288
297
 
289
- Make one apply attempt. If its response is lost or uncertain, verify the original manifest before retrying:
298
+ Verification reports `desired`, `original`, `conflicting` or `unreadable` current update state; it does not establish authorship. For generation, verification reads recorded acceptance and job outcomes. Never retry a write because a lookup failed. See [content operations](./content-operations.md) for exact resend and recovery.
290
299
 
291
- ```bash
292
- chalksurf sheet bulk-update verify \
293
- --input batch-001.apply.json --result-file batch-001.verify.result.json --json
294
- ```
295
-
296
- An `applied` result means the intended target is already present. `pending` means the original preconditions still match but the target is absent; rerun dry-run before a later apply. Inspect `conflicting` and `error` results. Never retry an uncertain apply blindly.
297
-
298
- For `sheet list`, `sheet bulk-update`, and `sheet bulk-update verify`, `--result-file` stores the complete JSON envelope while stdout reports only a compact summary. Result files are created privately and exclusively: an existing destination is never overwritten, so use a new path for every attempt. If artifact publication fails after the server confirms an apply, the CLI returns the complete confirmed response on stdout with a warning and a successful exit code. Preserve that response and do not retry the apply.
300
+ Every operational command accepts `--result-file PATH`, a new private file containing the complete terminal envelope. Stdout then contains the absolute artifact path. Destination preflight failures stop execution; publication failure after a response prints the complete fallback and preserves the command's exit code.
299
301
 
300
302
  Manage folders:
301
303
 
@@ -327,6 +329,20 @@ Use JSON mode when you need exact machine-readable output:
327
329
  chalksurf job wait job_123 --json
328
330
  ```
329
331
 
332
+ Generate solutions and descriptions, or manage jobs directly:
333
+
334
+ ```bash
335
+ chalksurf exercise solution generate EXERCISE_ID --dry-run --json
336
+ chalksurf sheet solution generate SHEET_ID --json
337
+ chalksurf exercise description generate --sheet-id SHEET_ID --dry-run --plan-out descriptions.json --json
338
+ chalksurf plan apply descriptions.json --wait --json
339
+ chalksurf job list --request-id REQUEST_ID --json
340
+ chalksurf job cancel JOB_ID --wait --json
341
+ chalksurf job dismiss JOB_ID --json
342
+ ```
343
+
344
+ Use `--replace-existing` to acknowledge replacing solution fields. Use `--allow-shared-exercise-update` only after reviewing shared usage. Draft translation uses `exercise translation generate --draft --input FILE` and never persists content. Published language removal uses `sheet translation remove SHEET_ID --target-language LANGUAGE --confirm-resource-id SHEET_ID`; pending work is cancelled with `job cancel`.
345
+
330
346
  Further reference:
331
347
 
332
348
  - [Agent and Codex guide](./agents.md)
package/docs/mcp.md CHANGED
@@ -8,7 +8,7 @@ ChalkSurf exposes a remote MCP endpoint for agent workflows that need the same c
8
8
  - Protocols: modern `2026-07-28` and stateless legacy `2025-11-25` are both supported on the same endpoint.
9
9
  - Authentication: OAuth access tokens issued through the ChalkSurf MCP authorization flow.
10
10
  - CLI tokens and browser session tokens are rejected on `/mcp`.
11
- - Tools advertise `chalksurf:read` or `chalksurf:write` during OAuth authorization. The editable MCP grant's current
11
+ - Tools advertise the single `chalksurf:mcp` resource scope during OAuth authorization. The editable MCP grant's current
12
12
  per-organization permission is the authoritative runtime read/write boundary, so changing the grant does not require
13
13
  issuing a new access token.
14
14
  - Organization-scoped tools require an explicit `organizationId` argument for normal execution.
@@ -29,9 +29,11 @@ MCP clients should use their built-in tool listing/search. ChalkSurf does not ex
29
29
 
30
30
  The MCP tools and CLI commands are backed by the same agent resource contracts in `packages/shared/src/agent-tools`.
31
31
 
32
- Read tools return structured resource data. Write tools return a structured `receipt`:
32
+ The core MCP exercise, sheet, and folder search/list/get tools return their primary structured result plus a top-level `resources` array of absolute links. `get_exercise_usage` links the sheets in its usage result. Nested matching sources, embedded exercises, versions, issues, and metadata are not linked. Empty primary results return `resources: []` without an extra link block. These sidecars are MCP-only; equivalent CLI and direct API read payloads are unchanged.
33
33
 
34
- - `resource`: the resource type and ID affected by the write
34
+ Write tools return a structured `receipt`:
35
+
36
+ - `resource`: the resource type and ID affected by the write, plus an absolute `url` for a surviving navigable exercise, sheet, or folder
35
37
  - `operation`: the stable operation name, such as `update_exercise`
36
38
  - `changedPaths`: field paths changed by the operation
37
39
  - `precondition`: optimistic update status, including `expectedUpdatedAt` and `actualUpdatedAt`
@@ -40,6 +42,8 @@ Read tools return structured resource data. Write tools return a structured `rec
40
42
 
41
43
  Treat write receipts as an agent audit log. They are suitable for user-visible summaries after an agent edits a sheet.
42
44
 
45
+ The same absolute links are repeated in ordinary MCP text so clients can include them in conversation. Completed jobs returned by `list_jobs`, `get_job`, and `wait_for_jobs` contain a plural `resources` array for their resulting exercises or sheets; non-completed or malformed job results use an empty array. Browser links include `organizationId` as a navigation hint. ChalkSurf waits for the signed-in user and memberships before applying it, ignores and clears inaccessible hints, and preserves it through login when the browser is anonymous. The query parameter does not grant access; resource endpoints remain authoritative. Delete receipts and the legacy singular `bulk_update_sheets` receipt resource deliberately have no URL.
46
+
43
47
  Exercise content writes are stricter for agents than for the human UI. `create_exercise` validates all submitted normalized exercise translation fields before writing. `update_exercise` validates only translation fields explicitly present in `patch.translations`; omitted invalid fields do not block unrelated partial updates.
44
48
 
45
49
  Successful exercise create/update receipts include `details.latexValidation` with one of these scopes:
@@ -73,62 +77,63 @@ Use `error.agentErrorDetails.results[].patchPath` and `validation.issues[]` to r
73
77
 
74
78
  ## Tool Surface
75
79
 
76
- | Tool | Permission | Purpose |
77
- | ----------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------- |
78
- | `get_auth_status` | read | Confirm the authenticated user and return the complete current organization catalog. |
79
- | `list_organizations` | read | Return the complete current organizations usable through the MCP grant and membership. |
80
- | `search_exercises` | read | Search exercises by text, labels, status, ownership, age, difficulty, or semantic query. |
81
- | `get_exercise` | read | Fetch full details for one visible exercise. |
82
- | `list_exercise_labels` | read | List the canonical exercise label taxonomy agents may write to exercises. |
83
- | `get_exercise_usage` | read | Inspect which sheets use one visible exercise. |
84
- | `list_quality_issues` | read | List active findings for organization-owned exercises with bounded selectors and keyset pagination. |
85
- | `figure_get_capabilities` | read | Inspect supported semantic 2D figure objects, construction definitions, assertions, limits, and snippets. |
86
- | `figure_render_draft` | read | Render a semantic figure draft to SVG/PNG metadata, PNG image content, diagnostics, assertions, and review hints. |
87
- | `upload_exercise_figure` | write | Upload and attach one reviewed image from a ChatGPT file parameter, HTTPS image, base64 image, or rendered figure to an exercise. |
88
- | `create_exercise` | write | Create one exercise in the selected organization. |
89
- | `copy_exercise` | write | Copy one visible exercise into the selected organization. |
90
- | `delete_exercise` | write | Soft-delete one unused writable exercise after `updated_at` and exact-ID confirmation checks. |
91
- | `update_exercise` | write | Patch exercise fields with an `updated_at` precondition and shared-usage acknowledgement. |
92
- | `resolve_quality_issue` | write | Resolve one active finding after checking the exercise evidence timestamp. |
93
- | `dismiss_quality_issue` | write | Dismiss one active false positive after checking the exercise evidence timestamp. |
94
- | `set_exercise_visibility` | write | Make one writable exercise public or private when visibility controls are enabled. |
95
- | `generate_exercise_translation_job` | write | Queue translation generation for one writable exercise. |
96
- | `import_exercise` | write | Queue an exercise import from ChatGPT file parameters or HTTPS URLs. |
97
- | `import_exercise_solution` | write | Queue a solution import for one exercise. |
98
- | `search_sheets` | read | Search visible sheets by text, subject, ownership, readiness, series, or edition year. |
99
- | `list_sheets` | read | List sheets owned by the selected organization, including folder paths and current series assignments. |
100
- | `get_sheet` | read | Fetch full details for one visible sheet. |
101
- | `list_sheet_series` | read | List the authoritative sheet-series hierarchy and valid assignment paths. |
102
- | `list_sheet_versions` | read | List version history for one sheet owned by the granted organization. |
103
- | `get_sheet_version` | read | Fetch one owned sheet version snapshot with referenced exercise details. |
104
- | `get_sheet_issues` | read | Fetch actionable sheet issues: invalid LaTeX, missing translations, missing solutions, and missing scores. |
105
- | `create_sheet` | write | Create one exercise sheet in the selected organization. |
106
- | `copy_sheet` | write | Copy a visible sheet, either keeping exercise references or copying all visible exercises. |
107
- | `delete_sheet` | write | Soft-delete one writable sheet after `updated_at` and exact-ID confirmation checks. |
108
- | `update_sheet` | write | Patch sheet metadata with an `updated_at` precondition. |
109
- | `bulk_update_sheets` | write | Dry-run or atomically apply up to 100 sheet patches using a confirmation digest. |
110
- | `verify_bulk_sheet_updates` | write | Read back up to 100 exact sheet-patch targets after an uncertain bulk apply response. |
111
- | `set_sheet_visibility` | write | Make one writable sheet public or private when visibility controls are enabled. |
112
- | `append_exercises_to_sheet` | write | Append existing visible exercises to the end of one writable sheet. |
113
- | `generate_sheet_translation_job` | write | Queue translation generation for one writable exercise sheet. |
114
- | `list_folders` | read | List sheet folders in the selected organization. |
115
- | `create_folder` | write | Create a sheet folder. |
116
- | `rename_folder` | write | Rename a sheet folder. |
117
- | `delete_folder` | write | Delete one empty sheet folder after exact-ID confirmation. |
118
- | `validate_latex_snippets` | read | Validate proposed LaTeX snippets before writing. |
119
- | `import_sheet` | write | Queue sheet imports from ChatGPT file parameters or HTTPS URLs. |
120
- | `import_sheet_solutions` | write | Queue solution import for an existing sheet. |
121
- | `list_jobs` | read | List jobs in the granted organization. |
122
- | `get_job` | read | Fetch one job. |
123
- | `wait_for_jobs` | read | Wait for one or more jobs to complete. |
124
- | `send_user_feedback` | read | Send user-consented product feedback, bug reports, or blocked-request reports. |
125
- | `send_agent_feedback` | read | Send agent-observed API, MCP, CLI, or documentation feedback. |
80
+ | Tool | Permission | Purpose |
81
+ | ----------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------- |
82
+ | `get_auth_status` | read | Confirm the authenticated user and return the complete current organization catalog. |
83
+ | `list_organizations` | read | Return the complete current organizations usable through the MCP grant and membership. |
84
+ | `search_exercises` | read | Search exercises by text, labels, status, ownership, age, difficulty, or semantic query. |
85
+ | `get_exercise` | read | Fetch full details for one visible exercise. |
86
+ | `list_exercise_labels` | read | List the canonical exercise label taxonomy agents may write to exercises. |
87
+ | `get_exercise_usage` | read | Inspect which sheets use one visible exercise. |
88
+ | `list_quality_issues` | read | List active findings for organization-owned exercises with bounded selectors and keyset pagination. |
89
+ | `figure_get_capabilities` | read | Inspect supported semantic 2D figure objects, construction definitions, assertions, limits, and snippets. |
90
+ | `figure_render_draft` | read | Render a semantic figure draft to SVG/PNG metadata, PNG image content, diagnostics, assertions, and review hints. |
91
+ | `upload_exercise_figure` | write | Upload and attach one reviewed image from a ChatGPT file parameter, HTTPS image, base64 image, or rendered figure to an exercise. |
92
+ | `create_exercise` | write | Create one exercise in the selected organization. |
93
+ | `copy_exercise` | write | Copy one visible exercise into the selected organization. |
94
+ | `delete_exercise` | write | Soft-delete one unused writable exercise after `updated_at` and exact-ID confirmation checks. |
95
+ | `update_exercise` | write | Patch exercise fields with an `updated_at` precondition and shared-usage acknowledgement. |
96
+ | `resolve_quality_issue` | write | Resolve one active finding after checking the exercise evidence timestamp. |
97
+ | `dismiss_quality_issue` | write | Dismiss one active false positive after checking the exercise evidence timestamp. |
98
+ | `set_exercise_visibility` | write | Make one writable exercise public or private when visibility controls are enabled. |
99
+ | `generate_exercise_translation_job` | write | Queue translation generation for one writable exercise. |
100
+ | `import_exercise` | write | Queue an exercise import from ChatGPT file parameters or HTTPS URLs. |
101
+ | `import_exercise_solution` | write | Queue a solution import for one exercise. |
102
+ | `search_sheets` | read | Search visible sheets by text, subject, ownership, readiness, series, or edition year. |
103
+ | `list_sheets` | read | List sheets owned by the selected organization, including folder paths and current series assignments. |
104
+ | `get_sheet` | read | Fetch full details for one visible sheet. |
105
+ | `list_sheet_series` | read | List the authoritative sheet-series hierarchy and valid assignment paths. |
106
+ | `list_sheet_versions` | read | List version history for one sheet owned by the granted organization. |
107
+ | `get_sheet_version` | read | Fetch one owned sheet version snapshot with referenced exercise details. |
108
+ | `get_sheet_issues` | read | Fetch actionable sheet issues: invalid LaTeX, missing translations, missing solutions, and missing scores. |
109
+ | `create_sheet` | write | Create one exercise sheet in the selected organization. |
110
+ | `copy_sheet` | write | Copy a visible sheet, either keeping exercise references or copying all visible exercises. |
111
+ | `delete_sheet` | write | Soft-delete one writable sheet after `updated_at` and exact-ID confirmation checks. |
112
+ | `update_sheet` | write | Patch sheet metadata with an `updated_at` precondition. |
113
+ | `bulk_update_sheets` | write | Dry-run or atomically apply up to 100 sheet patches using a confirmation digest. |
114
+ | `verify_bulk_sheet_updates` | write | Read back up to 100 exact sheet-patch targets after an uncertain bulk apply response. |
115
+ | `set_sheet_visibility` | write | Make one writable sheet public or private when visibility controls are enabled. |
116
+ | `append_exercises_to_sheet` | write | Append existing visible exercises to the end of one writable sheet. |
117
+ | `generate_sheet_translation` | write | Queue translation generation for one writable exercise sheet. |
118
+ | `list_folders` | read | List sheet folders in the selected organization. |
119
+ | `create_folder` | write | Create a sheet folder. |
120
+ | `rename_folder` | write | Rename a sheet folder. |
121
+ | `delete_folder` | write | Delete one empty sheet folder after exact-ID confirmation. |
122
+ | `validate_latex_snippets` | read | Validate proposed LaTeX snippets before writing. |
123
+ | `import_sheet` | write | Queue sheet imports from ChatGPT file parameters or HTTPS URLs. |
124
+ | `import_sheet_solutions` | write | Queue solution import for an existing sheet. |
125
+ | `import_sheet_translation` | write | Import one authoritative translation into an existing sheet. |
126
+ | `list_jobs` | read | List jobs in the granted organization. |
127
+ | `get_job` | read | Fetch one job. |
128
+ | `wait_for_jobs` | read | Wait for one or more jobs to complete. |
129
+ | `send_user_feedback` | read | Send user-consented product feedback, bug reports, or blocked-request reports. |
130
+ | `send_agent_feedback` | read | Send agent-observed API, MCP, CLI, or documentation feedback. |
126
131
 
127
132
  For `create_sheet`, pass `folderId` to create the sheet in a target folder. Omit it or pass `null` to create the sheet at the organization root.
128
133
 
129
- Sheet-series writes are currently restricted to admin users and sheets owned by the selected organization. Use `list_sheet_series`, then call `bulk_update_sheets` with `mode: "dry_run"`. Apply the unchanged batch with the returned `confirmationDigest`; no idempotency key or separate idempotency table is used. CLI `sheet bulk-update` and `sheet bulk-update verify` provide the same operation and recovery contracts. CLI file/stdin input, `--result-file`, and `sheet list --all` are local orchestration conveniences rather than MCP parameters. MCP inventory callers can use `list_sheets.afterId` with the previous page's last sheet ID for keyset pagination.
134
+ Bulk sheet updates require write access and sheets owned by the selected organization. Requests containing any `patch.seriesAssignments` field are additionally restricted to admin users; `expectedSeriesAssignments` alone remains available as a non-mutating precondition for ordinary bulk updates. For series writes, use `list_sheet_series`, then call `bulk_update_sheets` with `mode: "dry_run"`. Apply the unchanged batch with the returned `confirmationDigest`; no idempotency key or separate idempotency table is used. CLI `sheet bulk-update`, `sheet bulk-update apply --dry-run-result FILE`, and `sheet bulk-update verify` provide the same operation and recovery contracts. Applying a complete dry-run result artifact, CLI file/stdin input, the global `--result-file` artifact publisher, and `sheet list --all` are local orchestration conveniences rather than MCP parameters. MCP inventory callers can use `list_sheets.afterId` with the previous page's last sheet ID for keyset pagination.
130
135
 
131
- If the apply response is lost or uncertain, pass the exact same `updates` array to `verify_bulk_sheet_updates`. It compares the normalized target fields before checking the original preconditions, so a completed apply is reported as `applied` even though its write changed `updated_at`. A result is `pending` when target fields still differ and the preconditions still match, `conflicting` when target fields differ and a precondition changed, and `error` when the target cannot be read or validated. The verifier performs no writes and creates no agent audit entry, but it requires the same administrator access, organization write grant, and `chalksurf:write` OAuth scope as the bulk apply because it evaluates privileged write intent.
136
+ If the apply response is lost or uncertain, pass the exact same `updates` array to `verify_bulk_sheet_updates`. It compares the normalized target fields before checking the original preconditions, so a completed apply is reported as `applied` even though its write changed `updated_at`. A result is `pending` when target fields still differ and the preconditions still match, `conflicting` when target fields differ and a precondition changed, and `error` when the target cannot be read or validated. The verifier performs no writes and creates no agent audit entry. It requires an organization write grant and the `chalksurf:mcp` OAuth scope, plus administrator access when any verified update contains `patch.seriesAssignments`.
132
137
 
133
138
  The bulk receipt's singular `resource` remains populated for backward compatibility. It is not a complete attribution: use `details.sheetIds` and the per-sheet results for the authoritative affected set. ChalkSurf omits that compatibility value from durable bulk audit primary-resource attribution and displays the validated resource count instead.
134
139
 
@@ -140,7 +145,7 @@ Figure tools are available for agents to create and attach figures to exercises.
140
145
 
141
146
  ### File Parameters
142
147
 
143
- ChatGPT file uploads are advertised with `_meta["openai/fileParams"]`. For `import_exercise`, `import_exercise_solution`, `import_sheet`, and `import_sheet_solutions`, ChatGPT should pass uploaded files in top-level `file_id_N` arguments. The field names are model-facing: ChatGPT receives file IDs for those fields, then the harness passes ChalkSurf resolved file objects:
148
+ ChatGPT file uploads are advertised with `_meta["openai/fileParams"]`. For `import_exercise`, `import_exercise_solution`, `import_sheet`, `import_sheet_solutions`, and `import_sheet_translation`, ChatGPT should pass uploaded files in top-level `file_id_N` arguments. The field names are model-facing: ChatGPT receives file IDs for those fields, then the harness passes ChalkSurf resolved file objects:
144
149
 
145
150
  ```json
146
151
  {
@@ -167,7 +172,7 @@ Generic MCP clients should pass absolute HTTPS URLs in `sourceUrls` for import t
167
172
 
168
173
  No MCP tool currently transfers resources between organizations.
169
174
 
170
- `send_user_feedback` and `send_agent_feedback` are not read-only operations, but they require only the `chalksurf:read` OAuth scope because they do not mutate ChalkSurf exercises, sheets, or folders. Use `send_user_feedback` only after the user explicitly agrees to send feedback. Use `send_agent_feedback` when the agent itself observes inconsistent behavior, inaccurate docs, or missing agent-facing API coverage.
175
+ `send_user_feedback` and `send_agent_feedback` are not read-only operations, but their application permission remains `read` because they do not mutate ChalkSurf exercises, sheets, or folders. Like every MCP tool, they require the `chalksurf:mcp` OAuth resource scope. Use `send_user_feedback` only after the user explicitly agrees to send feedback. Use `send_agent_feedback` when the agent itself observes inconsistent behavior, inaccurate docs, or missing agent-facing API coverage.
171
176
 
172
177
  ## Common Workflows
173
178