feat: advance local-first workspace checklist
- add admin access-policy UI and local access control surfaces - add local markdown conflict resolution UI and smoke coverage - add ACP local agent changed-files audit scaffold and read-only write guard - document current P0-P2 checklist progress and verification evidence
This commit is contained in:
@@ -76,6 +76,33 @@ pub struct WorkspaceSource {
|
||||
pub capabilities: Vec<WorkspaceSourceCapability>,
|
||||
}
|
||||
|
||||
fn default_page_body_content_format() -> String {
|
||||
"editorBlocks".into()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PageBodyWriteRequest {
|
||||
pub document_id: String,
|
||||
pub workspace_id: String,
|
||||
pub source_kind: WorkspaceSourceKind,
|
||||
pub root_uri: String,
|
||||
#[serde(
|
||||
default,
|
||||
alias = "expected_file_version",
|
||||
alias = "conflictDetectionKey",
|
||||
alias = "conflict_detection_key"
|
||||
)]
|
||||
pub expected_file_version: Option<String>,
|
||||
#[serde(default)]
|
||||
pub base_content_hash: Option<String>,
|
||||
#[serde(default = "default_page_body_content_format")]
|
||||
pub content_format: String,
|
||||
pub content: Value,
|
||||
#[serde(default)]
|
||||
pub editor_source: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum KernelProjectionCapability {
|
||||
@@ -528,6 +555,14 @@ pub struct DocumentReadPageSubtree {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentContentResult {
|
||||
pub content: Value,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub editor_document: Option<Value>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub tiptap_document: Option<Value>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub block_document: Option<Value>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub block_projection_version: Option<u32>,
|
||||
pub revision: u64,
|
||||
pub conflict_detection_key: String,
|
||||
pub title: Option<String>,
|
||||
@@ -730,4 +765,56 @@ mod tests {
|
||||
serde_json::from_value(value).expect("workspace source 应可反序列化");
|
||||
assert_eq!(decoded, source);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_body_write_request_uses_file_version_contract() {
|
||||
let request: PageBodyWriteRequest = serde_json::from_value(json!({
|
||||
"documentId": "local-md:README.md",
|
||||
"workspaceId": "local-ws:user:my-space",
|
||||
"sourceKind": "local_folder",
|
||||
"rootUri": "file:///mnt/Data1T/Mnote_data/users/user/workspaces/my-space",
|
||||
"expectedFileVersion": "local-md:local-md:README.md:1:2:hash",
|
||||
"baseContentHash": "sha256:base",
|
||||
"contentFormat": "editorBlocks",
|
||||
"content": [{"type": "paragraph", "content": [{"type": "text", "text": "正文"}]}],
|
||||
"editorSource": "tiptap"
|
||||
}))
|
||||
.expect("page.body.write request 应可反序列化");
|
||||
|
||||
assert_eq!(request.document_id, "local-md:README.md");
|
||||
assert_eq!(request.source_kind, WorkspaceSourceKind::LocalFolder);
|
||||
assert_eq!(
|
||||
request.expected_file_version.as_deref(),
|
||||
Some("local-md:local-md:README.md:1:2:hash")
|
||||
);
|
||||
assert_eq!(request.content_format, "editorBlocks");
|
||||
assert_eq!(request.editor_source.as_deref(), Some("tiptap"));
|
||||
|
||||
let value = serde_json::to_value(&request).expect("request 应可序列化");
|
||||
assert_eq!(
|
||||
value["expectedFileVersion"],
|
||||
json!("local-md:local-md:README.md:1:2:hash")
|
||||
);
|
||||
assert_eq!(value["baseContentHash"], json!("sha256:base"));
|
||||
assert_eq!(value["contentFormat"], json!("editorBlocks"));
|
||||
assert_eq!(value["editorSource"], json!("tiptap"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_body_write_request_accepts_legacy_conflict_key_alias() {
|
||||
let request: PageBodyWriteRequest = serde_json::from_value(json!({
|
||||
"documentId": "local-md:README.md",
|
||||
"workspaceId": "local-ws:user:my-space",
|
||||
"sourceKind": "local_folder",
|
||||
"rootUri": "file:///tmp/workspace",
|
||||
"conflictDetectionKey": "legacy-key",
|
||||
"content": []
|
||||
}))
|
||||
.expect("legacy compat request 应可反序列化");
|
||||
|
||||
assert_eq!(request.expected_file_version.as_deref(), Some("legacy-key"));
|
||||
assert_eq!(request.content_format, "editorBlocks");
|
||||
assert_eq!(request.base_content_hash, None);
|
||||
assert_eq!(request.editor_source, None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user