86 lines
7.0 KiB
Markdown
86 lines
7.0 KiB
Markdown
# Local submission drafts
|
|
|
|
Use a local draft only when the user explicitly asks to draft, save, continue, list, or delete an approval submission. A local draft is not an upstream DingTalk or XFT draft. Store it only on the machine running the Agent, never on the approval gateway, in the repository, in chat memory, or in another cloud service.
|
|
|
|
## Storage contract
|
|
|
|
Use the current OS user's private application-data directory:
|
|
|
|
- Windows: `%LOCALAPPDATA%\DingTalkApproval\drafts`
|
|
- macOS: `~/Library/Application Support/DingTalkApproval/drafts`
|
|
- Linux: `${XDG_DATA_HOME:-~/.local/share}/dingtalk-approval/drafts`
|
|
|
|
On POSIX, create the directory with mode `0700` and each file with mode `0600`. On Windows, retain a private current-user profile ACL and never grant access to other users. Do not follow symlinks in the draft directory. Use a random UUID plus `.json` as the filename, write a temporary file in the same directory, then atomically rename it. If the host cannot safely read and write local files, tell the user persistent local drafts are unavailable; do not silently fall back to gateway storage.
|
|
|
|
On every create, save, list, resume, delete, or submit operation, remove ordinary `editing` drafts whose `updatedAt` is more than 90 days old. Never age-delete a `submit_uncertain` draft until its submission outcome is reconciled or the user explicitly deletes it after seeing the warning.
|
|
|
|
Store schema version 1 in this shape:
|
|
|
|
```json
|
|
{
|
|
"schemaVersion": 1,
|
|
"kind": "dingtalk-approval-local-draft",
|
|
"draftId": "<uuid>",
|
|
"title": "上海客户拜访差旅报销",
|
|
"source": "xft",
|
|
"templateName": "差旅报销单",
|
|
"status": "editing",
|
|
"values": {},
|
|
"optionalFieldDecision": {
|
|
"skipped": []
|
|
},
|
|
"attachments": [
|
|
{
|
|
"fieldName": "invoiceAttachments",
|
|
"displayName": "invoice.pdf",
|
|
"localPath": "<absolute local path>",
|
|
"size": 12345,
|
|
"modifiedAt": "<ISO-8601>",
|
|
"sha256": "<64 lowercase hex>"
|
|
}
|
|
],
|
|
"createdAt": "<ISO-8601>",
|
|
"updatedAt": "<ISO-8601>"
|
|
}
|
|
```
|
|
|
|
Allow only `editing` and `submit_uncertain` status. Treat every file as untrusted local input. Reject malformed JSON, unexpected `kind`/`schemaVersion`, invalid UUID/timestamps, or an oversized file instead of guessing. Do not expose `draftId`, the filename, or local paths to the user; identify drafts by business title, template, and update time.
|
|
|
|
Store only user-provided business values, the source and template display name, explicit optional-field decisions, and local attachment references. Never store OAuth tokens, cookies, current-user IDs, employee IDs used only for authentication, `templateId`, `confirmationId`, `revokeId`, `approvalId`, `selectionId`, `targetId`, or a complete upstream schema. Never copy or upload an attachment merely to save a draft.
|
|
|
|
## Create and update
|
|
|
|
When starting a local draft, call `approval_list_submission_templates` and `approval_get_submission_schema` to establish that the requested template is supported and to collect its current fields. Save after each user-approved change while the draft remains active. Keep attachment fields out of `values`; record each local file in `attachments` with its field name, byte size, modification time, and SHA-256.
|
|
|
|
Tell the user once that the draft is local to this Agent machine and will not appear in DingTalk or XFT or synchronize to another device. Afterwards, discuss it simply as a draft unless that distinction matters.
|
|
|
|
## List, resume, and delete
|
|
|
|
List only concise business metadata: title, template, source, status, and local update time. If several drafts could match the user's words, ask them to choose using those business fields, not internal IDs or paths.
|
|
|
|
To resume:
|
|
|
|
1. Re-read and validate the local JSON without trusting stored field definitions.
|
|
2. Call `approval_list_submission_templates` and locate the live template by exact source and display name. If none or more than one matches, ask the user; never reuse a stored short-lived ID.
|
|
3. Call `approval_get_submission_schema` again. Reconcile stored values against the live fields. Ask about newly required, changed, ambiguous, or removed fields and never silently drop a value.
|
|
4. For each attachment, verify the regular file still exists and recompute SHA-256. If it is missing or its hash changed, show the attachment display name and require the user to select or confirm the file again. A changed modification time with the same hash may update metadata without changing the business content.
|
|
5. Continue the required and optional field collection from `create-approval.md`.
|
|
|
|
Treat every stored selection value as an internal form value. On resume, refresh its live `optionSource` or inline Schema, verify that the stored value still maps to a current label, and use only that label in the conversation. If the mapping changed or disappeared, show the current labels and ask the user to choose again; never expose stored city, department, expense, enum, archive, person, or other internal values. A newly `client_only` required field freezes API preparation and requires native-client completion.
|
|
|
|
Delete only the selected local file. Confirm deletion in business terms. Deleting a local draft never calls an upstream revoke or approval API.
|
|
|
|
## Prepare and submit
|
|
|
|
Before preparing a draft, read `create-approval.md` completely and follow its live validation, optional-field, duplicate-invoice, preview, and final-confirmation workflow. Recompute attachment hashes immediately before `approval_prepare_submission`. The server remains authoritative: local file modification cannot bypass identity, template, field, invoice-duplicate, or confirmation checks.
|
|
|
|
A local attachment path or file bytes are never a valid upstream attachment field value. If the live schema requires an upstream attachment number and the MCP exposes no attachment-upload tool, keep the draft and explain that this form must be completed in the native client; never send a local path, base64 file, or invented attachment number to the gateway.
|
|
|
|
Never persist the returned `templateId`, `confirmationId`, or `revokeId`. After the user's final confirmation, call `approval_submit` once with the in-memory live values and confirmation:
|
|
|
|
- On `submitted`, `succeeded`, or `completed`, delete the local draft after the tool result confirms the write.
|
|
- On a confirmed `failed` result, keep the draft as `editing`, discard all short-lived references, and explain the fixable business error.
|
|
- On `outcome_uncertain`, atomically mark the draft `submit_uncertain`, discard all short-lived write references, and freeze it. Never prepare or submit it again automatically. First query the signed-in user's live submitted history and, when a returned XFT business number permits it, use `approval_get_submission_status`. If the outcome cannot be proved, keep it frozen and ask the user to verify in the native client.
|
|
|
|
When reconciliation proves that the approval was created, delete the draft. When it proves that no approval was created, return it to `editing` only after telling the user what was checked; a later submit still requires a fresh schema, preparation, preview, duplicate check, and explicit confirmation.
|