Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a57fb1ce4 |
+12
-93
@@ -24,7 +24,6 @@ jobs:
|
||||
pull-requests: read
|
||||
outputs:
|
||||
changelog_only: ${{ steps.classify.outputs.changelog_only }}
|
||||
release_seal_only: ${{ steps.classify.outputs.release_seal_only }}
|
||||
changelog_changed: ${{ steps.classify.outputs.changelog_changed }}
|
||||
docs_only: ${{ steps.classify.outputs.docs_only }}
|
||||
full_suite: ${{ steps.classify.outputs.full_suite }}
|
||||
@@ -40,7 +39,6 @@ jobs:
|
||||
with:
|
||||
script: |
|
||||
let changelogOnly = false;
|
||||
let releaseSealOnly = false;
|
||||
let changelogChanged = false;
|
||||
let docsOnly = false;
|
||||
let fullSuite = context.eventName === 'push';
|
||||
@@ -164,43 +162,6 @@ jobs:
|
||||
filename === 'go.mod' ||
|
||||
filename === 'go.sum'
|
||||
);
|
||||
const isExactReleaseSeal = (candidates) => {
|
||||
const changelog = candidates.filter(
|
||||
({ filename, status, previous_filename }) =>
|
||||
filename === 'CHANGELOG.md' &&
|
||||
status === 'modified' &&
|
||||
!previous_filename
|
||||
);
|
||||
if (changelog.length !== 1 || candidates.length < 2) {
|
||||
return false;
|
||||
}
|
||||
let version = '';
|
||||
return candidates.every((file) => {
|
||||
if (file.filename === 'CHANGELOG.md') {
|
||||
return file.status === 'modified' && !file.previous_filename;
|
||||
}
|
||||
if (
|
||||
file.status !== 'renamed' ||
|
||||
typeof file.filename !== 'string' ||
|
||||
typeof file.previous_filename !== 'string' ||
|
||||
file.additions !== 0 ||
|
||||
file.deletions !== 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const target = file.filename.match(
|
||||
/^\.changes\/released\/([0-9]+\.[0-9]+\.[0-9]+(?:-beta\.[1-9][0-9]*)?)\/([a-z0-9][a-z0-9._-]*\.md)$/
|
||||
);
|
||||
if (!target || file.previous_filename !== `.changes/${target[2]}`) {
|
||||
return false;
|
||||
}
|
||||
if (version && version !== target[1]) {
|
||||
return false;
|
||||
}
|
||||
version = target[1];
|
||||
return true;
|
||||
});
|
||||
};
|
||||
const classifyFiles = (complete) => {
|
||||
const paths = files.flatMap(({ filename, previous_filename }) =>
|
||||
[filename, previous_filename].filter(
|
||||
@@ -287,26 +248,19 @@ jobs:
|
||||
);
|
||||
}
|
||||
|
||||
const exactChangelogDiff =
|
||||
changelogOnly =
|
||||
files.length === 1 &&
|
||||
files[0].filename === 'CHANGELOG.md' &&
|
||||
files[0].status === 'modified' &&
|
||||
!files[0].previous_filename;
|
||||
releaseSealOnly = isExactReleaseSeal(files);
|
||||
changelogOnly = exactChangelogDiff || releaseSealOnly;
|
||||
changelogChanged = files.some(
|
||||
({ filename, previous_filename }) =>
|
||||
filename === 'CHANGELOG.md' ||
|
||||
previous_filename === 'CHANGELOG.md'
|
||||
);
|
||||
classifyFiles(true);
|
||||
if (releaseSealOnly) {
|
||||
fullSuite = false;
|
||||
}
|
||||
fastPathTrust = changelogOnly
|
||||
? releaseSealOnly
|
||||
? 'exact release-seal fragment archival and synthetic merge policy'
|
||||
: 'exact CHANGELOG-only revision and synthetic merge policy'
|
||||
? 'exact pull-request revision and synthetic merge policy'
|
||||
: docsOnly
|
||||
? 'documentation-only focused admission'
|
||||
: fullSuite
|
||||
@@ -341,8 +295,7 @@ jobs:
|
||||
per_page: 100,
|
||||
});
|
||||
files = Array.isArray(comparison.files) ? comparison.files : [];
|
||||
const pushFilesComplete = files.length < 300;
|
||||
classifyFiles(pushFilesComplete);
|
||||
classifyFiles(files.length < 300);
|
||||
const linearFromValidatedTip =
|
||||
comparison.status === 'ahead' &&
|
||||
comparison.merge_base_commit?.sha === expectedBefore &&
|
||||
@@ -354,10 +307,8 @@ jobs:
|
||||
files[0].filename === 'CHANGELOG.md' &&
|
||||
files[0].status === 'modified' &&
|
||||
!files[0].previous_filename;
|
||||
const exactReleaseSealDiff =
|
||||
pushFilesComplete && isExactReleaseSeal(files);
|
||||
|
||||
if (linearFromValidatedTip && (exactChangelogDiff || exactReleaseSealDiff)) {
|
||||
if (linearFromValidatedTip && exactChangelogDiff) {
|
||||
const requiredContexts = [
|
||||
'Lint',
|
||||
'Test',
|
||||
@@ -408,15 +359,9 @@ jobs:
|
||||
|
||||
if (missing.length === 0 && nonSuccess.length === 0) {
|
||||
changelogOnly = true;
|
||||
releaseSealOnly = exactReleaseSealDiff;
|
||||
changelogChanged = true;
|
||||
if (releaseSealOnly) {
|
||||
fullSuite = false;
|
||||
}
|
||||
fastPathTrust =
|
||||
releaseSealOnly
|
||||
? `exact release-seal successor of validated ${expectedBefore}`
|
||||
: `exact CHANGELOG-only successor of validated ${expectedBefore}`;
|
||||
`exact CHANGELOG-only successor of validated ${expectedBefore}`;
|
||||
} else {
|
||||
fastPathTrust =
|
||||
'predecessor Code Admission is not fully successful; ' +
|
||||
@@ -431,7 +376,6 @@ jobs:
|
||||
}
|
||||
|
||||
core.setOutput('changelog_only', String(changelogOnly));
|
||||
core.setOutput('release_seal_only', String(releaseSealOnly));
|
||||
core.setOutput('changelog_changed', String(changelogChanged));
|
||||
core.setOutput('docs_only', String(docsOnly));
|
||||
core.setOutput('full_suite', String(fullSuite));
|
||||
@@ -443,8 +387,7 @@ jobs:
|
||||
await core.summary
|
||||
.addHeading('Code Admission scope')
|
||||
.addRaw(`- Event: \`${context.eventName}\`\n`)
|
||||
.addRaw(`- Metadata-only fast path: \`${changelogOnly}\`\n`)
|
||||
.addRaw(`- Release-seal fragments only: \`${releaseSealOnly}\`\n`)
|
||||
.addRaw(`- Exact modified CHANGELOG only: \`${changelogOnly}\`\n`)
|
||||
.addRaw(`- CHANGELOG touched: \`${changelogChanged}\`\n`)
|
||||
.addRaw(`- Documentation-only: \`${docsOnly}\`\n`)
|
||||
.addRaw(`- Full suite: \`${fullSuite}\`\n`)
|
||||
@@ -459,14 +402,7 @@ jobs:
|
||||
|
||||
- name: Record CHANGELOG-only fast path
|
||||
if: steps.classify.outputs.changelog_only == 'true'
|
||||
env:
|
||||
RELEASE_SEAL_ONLY: ${{ steps.classify.outputs.release_seal_only }}
|
||||
run: |
|
||||
if [ "$RELEASE_SEAL_ONLY" = true ]; then
|
||||
echo "Lint is satisfied by the trusted release-seal fragment Policy path." >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "Lint is satisfied by the trusted CHANGELOG-only Policy path." >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
run: echo "Lint is satisfied by the trusted CHANGELOG-only Policy path." >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Record documentation-only fast path
|
||||
if: steps.classify.outputs.changelog_only != 'true' && steps.classify.outputs.docs_only == 'true'
|
||||
@@ -1320,7 +1256,6 @@ jobs:
|
||||
env:
|
||||
CLASSIFIED_CHANGELOG_CHANGED: ${{ needs.lint.outputs.changelog_changed }}
|
||||
CHANGELOG_ONLY: ${{ needs.lint.outputs.changelog_only }}
|
||||
RELEASE_SEAL_ONLY: ${{ needs.lint.outputs.release_seal_only }}
|
||||
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
run: |
|
||||
set -eu
|
||||
@@ -1347,7 +1282,7 @@ jobs:
|
||||
fi
|
||||
|
||||
mode=--content-only
|
||||
if [ "$CHANGELOG_ONLY" = true ] && [ "$RELEASE_SEAL_ONLY" != true ]; then
|
||||
if [ "$CHANGELOG_ONLY" = true ]; then
|
||||
mode=--fast-path
|
||||
fi
|
||||
./scripts/policy/check-changelog-pr.sh \
|
||||
@@ -1359,41 +1294,25 @@ jobs:
|
||||
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
run: ./scripts/policy/check-release-fragments.sh "$PR_BASE_SHA" HEAD
|
||||
|
||||
- name: Validate trusted main metadata-only push
|
||||
- name: Validate trusted main CHANGELOG-only push
|
||||
if: github.event_name == 'push' && needs.lint.outputs.changelog_only == 'true'
|
||||
env:
|
||||
PUSH_BEFORE_SHA: ${{ github.event.before }}
|
||||
PUSH_AFTER_SHA: ${{ github.event.after }}
|
||||
RELEASE_SEAL_ONLY: ${{ needs.lint.outputs.release_seal_only }}
|
||||
run: |
|
||||
set -eu
|
||||
test "$(git rev-parse HEAD)" = "$PUSH_AFTER_SHA" || {
|
||||
echo "checked-out push revision does not match event after SHA" >&2
|
||||
exit 1
|
||||
}
|
||||
mode=--fast-path
|
||||
if [ "$RELEASE_SEAL_ONLY" = true ]; then
|
||||
mode=--content-only
|
||||
fi
|
||||
./scripts/policy/check-changelog-pr.sh \
|
||||
"$mode" "$PUSH_BEFORE_SHA" "$PUSH_AFTER_SHA"
|
||||
if [ "$RELEASE_SEAL_ONLY" = true ]; then
|
||||
./scripts/policy/check-release-fragments.sh \
|
||||
"$PUSH_BEFORE_SHA" "$PUSH_AFTER_SHA"
|
||||
fi
|
||||
--fast-path "$PUSH_BEFORE_SHA" "$PUSH_AFTER_SHA"
|
||||
|
||||
- name: Record CHANGELOG-only fast path
|
||||
if: needs.lint.outputs.changelog_only == 'true'
|
||||
env:
|
||||
RELEASE_SEAL_ONLY: ${{ needs.lint.outputs.release_seal_only }}
|
||||
run: |
|
||||
if [ "$RELEASE_SEAL_ONLY" = true ]; then
|
||||
echo "Only the trusted release-seal and fragment validators ran; executable sources are unchanged." \
|
||||
>> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "Only the trusted base-equivalent CHANGELOG validator ran; executable sources are unchanged." \
|
||||
>> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
echo "Only the trusted base-equivalent CHANGELOG validator ran; executable sources are unchanged." \
|
||||
>> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Validate scoped policy
|
||||
if: ${{ needs.lint.outputs.changelog_only != 'true' && (needs.lint.outputs.docs_only == 'true' || (needs.lint.outputs.full_suite != 'true' && needs.lint.outputs.interface_sensitive != 'true')) }}
|
||||
|
||||
@@ -6,44 +6,6 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.0.58] - 2026-08-13
|
||||
|
||||
This release promotes the sealed `v1.0.58-beta.6` contents to stable.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Expanded collaborative workflows** — adds full AI Table, Sheet, Minutes,
|
||||
approval-event, Drive-comment, document export, and CSV workflow support,
|
||||
including safer validation, explicit confirmation for writes, and
|
||||
machine-readable completion receipts.
|
||||
- **More capable Chat operations** — adds robot image/file messages, toolbar
|
||||
management, streaming-card mentions, automatic pagination controls, and
|
||||
clearer post-send ID, Markdown-image, paging, and result-shape guidance.
|
||||
- **Reliable Agent and CLI contracts** — expands Agent-visible Chat and
|
||||
Minutes commands, aligns bundled skills, improves schema/result envelopes,
|
||||
and hardens parameter, pagination, runtime-token, and write-result
|
||||
verification so ambiguous or incomplete operations fail closed.
|
||||
- **Multi-skill install and upgrade** — makes the multi-skill layout the
|
||||
default for fresh installs and upgrades while preserving an explicit legacy
|
||||
mono option.
|
||||
- **Safer release delivery** — strengthens release-equivalent compatibility,
|
||||
sealing, package verification, and evaluation-dispatch checks for more
|
||||
reliable cross-platform releases.
|
||||
|
||||
## [1.0.58-beta.6] - 2026-08-13
|
||||
|
||||
### Fixed
|
||||
|
||||
- **npm package verification for multi-skill installs** (#991) — aligns the
|
||||
release verifier with the installer’s concrete Agent skill-root selection,
|
||||
preventing valid multi-skill package layouts from failing release delivery.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Release-seal CI classification** (#987) — recognizes the reviewed
|
||||
CHANGELOG-and-fragment archival shape while retaining release-contract and
|
||||
lifecycle validation, reducing unrelated CI work for release-seal PRs.
|
||||
|
||||
## [1.0.58-beta.5] - 2026-08-13
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
class DingtalkWorkspaceCliBeta < Formula
|
||||
desc "Automate DingTalk workspace tasks from the terminal (beta channel)"
|
||||
homepage "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli"
|
||||
version "1.0.58-beta.6"
|
||||
version "1.0.58-beta.4"
|
||||
license "Apache-2.0"
|
||||
keg_only "it is the beta channel and conflicts with dingtalk-workspace-cli"
|
||||
|
||||
on_macos do
|
||||
if Hardware::CPU.arm?
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58-beta.6/dws-darwin-arm64.tar.gz"
|
||||
sha256 "8f55497b84113f81b318e087c723016a02eded1cb5784cbd0811fe527d5852ca"
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58-beta.4/dws-darwin-arm64.tar.gz"
|
||||
sha256 "5c2ac92e35b1f1dba80234af8b0c9505b2883f4a37c1e73892b8a1c3087b7702"
|
||||
else
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58-beta.6/dws-darwin-amd64.tar.gz"
|
||||
sha256 "b1762f1640310fb4100634fe54d9baace46384bb270c59148fe306275554d491"
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58-beta.4/dws-darwin-amd64.tar.gz"
|
||||
sha256 "93ef787770105fe1f0d27585adcac7b740aa6c37ff490275c4113814541ae095"
|
||||
end
|
||||
end
|
||||
|
||||
on_linux do
|
||||
if Hardware::CPU.arm?
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58-beta.6/dws-linux-arm64.tar.gz"
|
||||
sha256 "55393310ef0e1f24ea2c0dc22f00c0eb3b343edc2deb18d0db7838cda65af25d"
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58-beta.4/dws-linux-arm64.tar.gz"
|
||||
sha256 "011ce16a73d8fd24275e34c3122d3d0832c60cde2480f496018eb654059b5c05"
|
||||
else
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58-beta.6/dws-linux-amd64.tar.gz"
|
||||
sha256 "3830f77d09b4da4aa39f0c08d772ff3fa1ffb837eb15bb417f97edbccb073b7d"
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58-beta.4/dws-linux-amd64.tar.gz"
|
||||
sha256 "847b17ff8a8d80dce38f0013eb35c77c102be16c9f98b955a632b983cd5ec104"
|
||||
end
|
||||
end
|
||||
|
||||
resource "skills" do
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58-beta.6/dws-skills.zip"
|
||||
sha256 "f304a883a4f9e938b26a44692cd5a8d3d8704ba70ee7f33ba7c288434da72b6f"
|
||||
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58-beta.4/dws-skills.zip"
|
||||
sha256 "f5e0c72cc92cb7e8886409319cf68bbbfc7740e969bd39a389b74af4befdbc66"
|
||||
end
|
||||
|
||||
def install
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="DWS Drive Shortcut 与 lark-cli 的业务能力、真实数据 E2E 证据和平台边界分析。">
|
||||
<title>Drive Shortcut 能力全景|业务评审版</title>
|
||||
<title>Drive Shortcut 能力全景|最终业务评审版</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light;
|
||||
@@ -126,6 +126,7 @@
|
||||
<p class="subtitle">从 lark-cli 对齐出发,但不止于命令名:逐项审查输入、校验、多步编排、失败语义、真实字节和平台边界。</p>
|
||||
<div class="meta-row">
|
||||
<span class="meta-pill good">真实账号 E2E 已执行</span>
|
||||
<span class="meta-pill good">最终读回合同已合入 main</span>
|
||||
<span class="meta-pill">28 个公开入口</span>
|
||||
<span class="meta-pill">统一 Result / Pagination</span>
|
||||
<span class="meta-pill">报告已移除 PII / 凭证 / 业务正文</span>
|
||||
@@ -231,12 +232,12 @@
|
||||
<article class="shortcut-row" data-tool data-domain="discover" data-risk="read"><div class="command"><code>dws drive +stats</code></div><div class="row-main"><p>读取访问、编辑、评论、点赞、预览和下载统计。</p><small>发现与检查</small></div><div class="badges"><span class="badge read">READ</span></div></article>
|
||||
<article class="shortcut-row" data-tool data-domain="discover" data-risk="read"><div class="command"><code>dws drive +cover</code></div><div class="row-main"><p>读取封面或缩略图;不宣称服务端多格式预览。</p><small>发现与检查</small></div><div class="badges"><span class="badge read">READ</span></div></article>
|
||||
|
||||
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +upload</code></div><div class="row-main"><p>上传凭证、OSS PUT、严格提交和远端元数据读回的一体化事务。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span><span class="badge smart">SMART</span></div></article>
|
||||
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +upload</code></div><div class="row-main"><p>上传凭证、OSS PUT、严格提交和远端元数据读回的一体化事务;读回必须命中提交返回的精确节点 ID、精确名称与远端大小。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span><span class="badge smart">SMART</span></div></article>
|
||||
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="read"><div class="command"><code>dws drive +download</code></div><div class="row-main"><p>安全落盘、no-clobber、原子发布并验证非零字节。</p><small>文件生命周期</small></div><div class="badges"><span class="badge read">READ</span><span class="badge smart">SMART</span></div></article>
|
||||
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +create-folder</code></div><div class="row-main"><p>创建文件夹后要求新 ID,并读回名称验证。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span></div></article>
|
||||
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +create-shortcut</code></div><div class="row-main"><p>创建快捷方式并读回,明确区别于独立副本。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span></div></article>
|
||||
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +copy</code></div><div class="row-main"><p>复制在线对象;普通文件预检拒绝,避免把 .dlink 当副本。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span><span class="badge smart">SMART</span></div></article>
|
||||
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +move</code></div><div class="row-main"><p>移动到指定文件夹或知识库位置,语义与 copy/shortcut 消歧。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span></div></article>
|
||||
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +move</code></div><div class="row-main"><p>移动到指定文件夹或知识库位置;读回必须仍是请求节点,并精确落在请求的目标文件夹或知识库。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span><span class="badge smart">READ-BACK</span></div></article>
|
||||
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +rename</code></div><div class="row-main"><p>重命名后读取真实节点,验证最终名称和扩展名。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span><span class="badge smart">SMART</span></div></article>
|
||||
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="high"><div class="command"><code>dws drive +delete</code></div><div class="row-main"><p>将确认过的节点移入回收站,要求 success=true 终态证据。</p><small>文件生命周期</small></div><div class="badges"><span class="badge high">HIGH · CONFIRM</span></div></article>
|
||||
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +recycle-restore</code></div><div class="row-main"><p>按回收项 ID 恢复,并读回恢复后的节点。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span><span class="badge smart">SMART</span></div></article>
|
||||
@@ -266,10 +267,10 @@
|
||||
</div>
|
||||
<div class="timeline">
|
||||
<div class="event"><b>创建与发现</b><span>创建两个隔离目录并读回;+list 命中真实节点,由此发现并修复 dentryId 与 32 字符 fileId 混用。</span></div>
|
||||
<div class="event"><b>上传与下载</b><span>真实 OSS 上传、远端元数据读回、下载、no-clobber 二次路径、大小与 SHA-256 一致性全部通过。</span></div>
|
||||
<div class="event"><b>上传与下载</b><span>真实 OSS 上传后按提交节点精确读回,核对 ID、完整文件名与远端大小;下载、no-clobber 二次路径、大小与 SHA-256 一致性全部通过。</span></div>
|
||||
<div class="event"><b>检查与个人状态</b><span>+inspect(含 stats / publish / cover)、+stats、+cover、收藏 add→list→remove 通过。</span></div>
|
||||
<div class="event"><b>版本闭环</b><span>覆盖文件产生两个版本;history/get/download/revert 通过,回滚后最新字节与原始内容一致。</span></div>
|
||||
<div class="event"><b>复制、移动与命名</b><span>在线文档 copy 通过;普通文件 .dlink 被修正为预检拒绝;move 往返、rename 扩展名规范化通过。</span></div>
|
||||
<div class="event"><b>复制、移动与命名</b><span>在线文档 copy 通过;普通文件 .dlink 被修正为预检拒绝;move A→B→A 每一步均核对请求节点与目标位置,rename 扩展名规范化通过。</span></div>
|
||||
<div class="event"><b>删除与恢复</b><span>delete→recycle-list→recycle-restore 通过,真实回收响应字段已按后端形态修正。</span></div>
|
||||
<div class="event"><b>平台负向证据</b><span>publish-set 对普通文件和在线文档均明确返回不支持,因此保持 unavailable;没有把失败改写成空对象成功。</span></div>
|
||||
<div class="event"><b>清理</b><span>隔离目录进入回收站,根目录残留计数为零;本地下载产物删除。</span></div>
|
||||
@@ -289,7 +290,7 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer><div class="wrap">依据:DWS 最终 Shortcut catalog / Schema、Drive 实现与测试、真实账号 E2E、lark-cli Drive registrations 与实现。范围仅含 Drive Shortcut 及必要跨产品路由;不包含原子命令总表。所有业务标识、凭证、签名 URL、用户信息和正文均已脱敏。</div></footer>
|
||||
<footer><div class="wrap">依据:DWS 最终 Shortcut catalog / Schema、Drive 实现与测试、真实账号 E2E、lark-cli Drive registrations 与实现;最终读回修复已随 PR #963 合入 main。范围仅含 Drive Shortcut 及必要跨产品路由;不包含原子命令总表。所有业务标识、凭证、签名 URL、用户信息和正文均已脱敏。</div></footer>
|
||||
<script>
|
||||
const q = document.querySelector('#q');
|
||||
const domain = document.querySelector('#domain');
|
||||
|
||||
@@ -54,7 +54,7 @@ while [ "$#" -gt 0 ]; do
|
||||
done
|
||||
|
||||
TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/dws-package-verify-XXXXXX")"
|
||||
HOME_SPECIFIC_AGENT_PARENTS="
|
||||
HOME_AGENT_PARENTS="
|
||||
.claude
|
||||
.cursor
|
||||
.qoder
|
||||
@@ -72,7 +72,8 @@ HOME_SPECIFIC_AGENT_PARENTS="
|
||||
.openclaw
|
||||
.hermes
|
||||
"
|
||||
HOME_SPECIFIC_SKILL_BASES="
|
||||
HOME_SKILL_BASES="
|
||||
.agents/skills
|
||||
.claude/skills
|
||||
.cursor/skills
|
||||
.qoder/skills
|
||||
@@ -101,55 +102,43 @@ cleanup() {
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
seed_specific_agent_homes() {
|
||||
seed_agent_homes() {
|
||||
home_root="$1"
|
||||
for parent in $HOME_SPECIFIC_AGENT_PARENTS; do
|
||||
for parent in $HOME_AGENT_PARENTS; do
|
||||
mkdir -p "$home_root/$parent"
|
||||
done
|
||||
}
|
||||
|
||||
verify_skill_base() {
|
||||
home_root="$1"
|
||||
base="$2"
|
||||
need_file "$home_root/$base/dingtalk-shared/SKILL.md"
|
||||
need_file "$home_root/$base/dingtalk-misc/SKILL.md"
|
||||
[ ! -e "$home_root/$base/dws" ] || err "unexpected mono Skill layout found in $home_root/$base/dws"
|
||||
}
|
||||
|
||||
verify_skill_targets() {
|
||||
home_root="$1"
|
||||
found_specific=0
|
||||
for base in $HOME_SPECIFIC_SKILL_BASES; do
|
||||
parent="${base%/skills}"
|
||||
if [ -d "$home_root/$parent" ]; then
|
||||
verify_skill_base "$home_root" "$base"
|
||||
found_specific=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$found_specific" -eq 0 ]; then
|
||||
verify_skill_base "$home_root" ".agents/skills"
|
||||
return
|
||||
fi
|
||||
|
||||
for name in dingtalk-shared dingtalk-misc dws; do
|
||||
[ ! -e "$home_root/.agents/skills/$name" ] || \
|
||||
err "unexpected generic Skill copy found in $home_root/.agents/skills/$name"
|
||||
for base in $HOME_SKILL_BASES; do
|
||||
need_file "$home_root/$base/dingtalk-shared/SKILL.md"
|
||||
need_file "$home_root/$base/dingtalk-misc/SKILL.md"
|
||||
[ ! -e "$home_root/$base/dws" ] || err "unexpected mono Skill layout found in $home_root/$base/dws"
|
||||
done
|
||||
}
|
||||
|
||||
verify_npm_install() {
|
||||
tarball_path="$1"
|
||||
scenario="$2"
|
||||
npm_home="$TMP_ROOT/npm-home-$scenario"
|
||||
npm_prefix="$TMP_ROOT/npm-prefix-$scenario"
|
||||
npm_cache="$TMP_ROOT/npm-cache-$scenario"
|
||||
verify_npm() {
|
||||
need_cmd npm
|
||||
need_cmd node
|
||||
need_cmd tar
|
||||
need_cmd unzip
|
||||
need_file "$NPM_STAGE_DIR/package.json"
|
||||
|
||||
npm_home="$TMP_ROOT/npm-home"
|
||||
npm_prefix="$TMP_ROOT/npm-prefix"
|
||||
npm_cache="$TMP_ROOT/npm-cache"
|
||||
mkdir -p "$npm_home" "$npm_prefix" "$npm_cache"
|
||||
if [ "$scenario" = "specific-agent-roots" ]; then
|
||||
seed_specific_agent_homes "$npm_home"
|
||||
fi
|
||||
seed_agent_homes "$npm_home"
|
||||
|
||||
say "==> verifying npm package install"
|
||||
tarball_name="$(
|
||||
cd "$NPM_STAGE_DIR"
|
||||
HOME="$npm_home" npm_config_cache="$npm_cache" npm pack --silent
|
||||
)"
|
||||
tarball_path="$NPM_STAGE_DIR/$tarball_name"
|
||||
need_file "$tarball_path"
|
||||
|
||||
say "==> verifying npm package install ($scenario)"
|
||||
HOME="$npm_home" npm_config_cache="$npm_cache" npm_config_prefix="$npm_prefix" \
|
||||
npm install -g "$tarball_path" >/dev/null
|
||||
|
||||
@@ -169,24 +158,6 @@ verify_npm_install() {
|
||||
|
||||
HOME="$npm_home" npm_config_cache="$npm_cache" npm_config_prefix="$npm_prefix" \
|
||||
npm uninstall -g dingtalk-workspace-cli >/dev/null
|
||||
}
|
||||
|
||||
verify_npm() {
|
||||
need_cmd npm
|
||||
need_cmd node
|
||||
need_cmd tar
|
||||
need_cmd unzip
|
||||
need_file "$NPM_STAGE_DIR/package.json"
|
||||
|
||||
tarball_name="$(
|
||||
cd "$NPM_STAGE_DIR"
|
||||
HOME="$TMP_ROOT/npm-pack-home" npm_config_cache="$TMP_ROOT/npm-pack-cache" npm pack --silent
|
||||
)"
|
||||
tarball_path="$NPM_STAGE_DIR/$tarball_name"
|
||||
need_file "$tarball_path"
|
||||
|
||||
verify_npm_install "$tarball_path" "specific-agent-roots"
|
||||
verify_npm_install "$tarball_path" "generic-fallback"
|
||||
|
||||
rm -f "$tarball_path"
|
||||
}
|
||||
|
||||
@@ -1019,47 +1019,12 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) {
|
||||
}
|
||||
|
||||
admission := readWorkflow(".github/workflows/ci.yml")
|
||||
policyStart := strings.Index(admission, "\n policy:\n")
|
||||
policyEnd := strings.Index(admission, "\n interface-integrity:\n")
|
||||
if policyStart < 0 || policyEnd <= policyStart {
|
||||
t.Fatal("Code Admission workflow missing Policy job boundaries")
|
||||
}
|
||||
policyJob := admission[policyStart:policyEnd]
|
||||
requirePolicyEnv := func(step, nextStep string) {
|
||||
t.Helper()
|
||||
start := strings.Index(policyJob, " - name: "+step+"\n")
|
||||
end := strings.Index(policyJob[start+1:], " - name: "+nextStep+"\n")
|
||||
if start < 0 || end < 0 {
|
||||
t.Fatalf("Policy job missing %q step boundary", step)
|
||||
}
|
||||
block := policyJob[start : start+1+end]
|
||||
if !strings.Contains(block, "RELEASE_SEAL_ONLY: ${{ needs.lint.outputs.release_seal_only }}") {
|
||||
t.Errorf("Policy %q step must receive release-seal classification", step)
|
||||
}
|
||||
}
|
||||
requirePolicyEnv("Validate changed CHANGELOG content", "Validate release fragment lifecycle")
|
||||
requirePolicyEnv("Validate trusted main metadata-only push", "Record CHANGELOG-only fast path")
|
||||
pushStart := strings.Index(admission, "} else if (context.eventName === 'push') {")
|
||||
pushEnd := strings.Index(admission, "\n core.setOutput('changelog_only'")
|
||||
if pushStart < 0 || pushEnd <= pushStart {
|
||||
t.Fatal("Code Admission workflow missing push classification boundaries")
|
||||
}
|
||||
pushClassification := admission[pushStart:pushEnd]
|
||||
if strings.Contains(pushClassification, "const exactReleaseSealDiff = isExactReleaseSeal(files)") {
|
||||
t.Error("push release-seal fast path must not trust an unguarded compare file list")
|
||||
}
|
||||
for _, want := range []string{
|
||||
"name: CI",
|
||||
"files.length === 1",
|
||||
"files[0].filename === 'CHANGELOG.md'",
|
||||
"files[0].status === 'modified'",
|
||||
"!files[0].previous_filename",
|
||||
"const isExactReleaseSeal",
|
||||
"file.status !== 'renamed'",
|
||||
"file.additions !== 0",
|
||||
"file.deletions !== 0",
|
||||
"releaseSealOnly = isExactReleaseSeal(files)",
|
||||
"release_seal_only: ${{ steps.classify.outputs.release_seal_only }}",
|
||||
"pre-classification",
|
||||
"post-classification",
|
||||
"before.changed_files !== files.length",
|
||||
@@ -1083,8 +1048,7 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) {
|
||||
"mode=--fast-path",
|
||||
`"$mode" "$PR_BASE_SHA" HEAD`,
|
||||
`test "$(git rev-parse HEAD)" = "$PUSH_AFTER_SHA"`,
|
||||
`"$mode" "$PUSH_BEFORE_SHA" "$PUSH_AFTER_SHA"`,
|
||||
`./scripts/policy/check-release-fragments.sh \`,
|
||||
`--fast-path "$PUSH_BEFORE_SHA" "$PUSH_AFTER_SHA"`,
|
||||
"needs.lint.outputs.platform_sensitive == 'true'",
|
||||
`COVERAGE_TARGET: "100"`,
|
||||
`COVERAGE_ENFORCE_OVERALL: "false"`,
|
||||
@@ -1096,9 +1060,7 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) {
|
||||
`package_output="$(./scripts/ci/test-packages.sh list`,
|
||||
"docs_only: ${{ steps.classify.outputs.docs_only }}",
|
||||
"full_suite: ${{ steps.classify.outputs.full_suite }}",
|
||||
"const pushFilesComplete = files.length < 300",
|
||||
"classifyFiles(pushFilesComplete)",
|
||||
"pushFilesComplete && isExactReleaseSeal(files)",
|
||||
"classifyFiles(files.length < 300)",
|
||||
"platformSensitive =",
|
||||
"files.some(isNativeGoChange)",
|
||||
"paths.some(isEditionSensitive)",
|
||||
|
||||
@@ -70,12 +70,10 @@ func TestPackageManagerVersionVerificationReadsRawBinary(t *testing.T) {
|
||||
t.Fatal("package-manager verifier still requires the version marker to occupy a strings(1) line")
|
||||
}
|
||||
for _, want := range []string{
|
||||
"HOME_SPECIFIC_SKILL_BASES=",
|
||||
"HOME_SKILL_BASES=",
|
||||
`$base/dingtalk-shared/SKILL.md`,
|
||||
`$base/dingtalk-misc/SKILL.md`,
|
||||
"unexpected mono Skill layout",
|
||||
`verify_npm_install "$tarball_path" "specific-agent-roots"`,
|
||||
`verify_npm_install "$tarball_path" "generic-fallback"`,
|
||||
} {
|
||||
if !strings.Contains(script, want) {
|
||||
t.Errorf("package-manager verifier is missing multi-layout contract %q", want)
|
||||
@@ -86,60 +84,6 @@ func TestPackageManagerVersionVerificationReadsRawBinary(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageManagerVerifierCoversSpecificAndFallbackSkillRoots(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
postGoreleaserPath, err := filepath.Abs(filepath.Join("..", "..", "scripts", "release", "post-goreleaser.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("Abs(post-goreleaser.sh) error = %v", err)
|
||||
}
|
||||
verifierPath, err := filepath.Abs(filepath.Join("..", "..", "scripts", "release", "verify-package-managers.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("Abs(verify-package-managers.sh) error = %v", err)
|
||||
}
|
||||
|
||||
distDir := filepath.Join(t.TempDir(), "dist")
|
||||
targets := []string{
|
||||
"dws-darwin-amd64.tar.gz",
|
||||
"dws-darwin-arm64.tar.gz",
|
||||
"dws-linux-amd64.tar.gz",
|
||||
"dws-linux-arm64.tar.gz",
|
||||
}
|
||||
hostArchive := "dws-" + runtime.GOOS + "-" + runtime.GOARCH + ".tar.gz"
|
||||
if runtime.GOOS == "windows" {
|
||||
hostArchive = "dws-" + runtime.GOOS + "-" + runtime.GOARCH + ".zip"
|
||||
}
|
||||
foundHost := false
|
||||
for _, target := range targets {
|
||||
if target == hostArchive {
|
||||
foundHost = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundHost {
|
||||
targets = append(targets, hostArchive)
|
||||
}
|
||||
seedDistArtifacts(t, distDir, targets)
|
||||
|
||||
packageCmd := exec.Command("sh", postGoreleaserPath)
|
||||
packageCmd.Env = postGoreleaserEnv(t, distDir, "v0.0.0-test", "https://downloads.example.com/dws/releases/v0.0.0-test")
|
||||
if output, err := packageCmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("post-goreleaser.sh error = %v\noutput:\n%s", err, output)
|
||||
}
|
||||
|
||||
verifyCmd := exec.Command("sh", verifierPath, "--npm-only")
|
||||
verifyCmd.Env = append(os.Environ(), "DWS_PACKAGE_DIST_DIR="+distDir)
|
||||
output, err := verifyCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("verify-package-managers.sh error = %v\noutput:\n%s", err, output)
|
||||
}
|
||||
for _, scenario := range []string{"specific-agent-roots", "generic-fallback"} {
|
||||
if !strings.Contains(string(output), "verifying npm package install ("+scenario+")") {
|
||||
t.Errorf("verifier output is missing %s scenario:\n%s", scenario, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func seedDistArchive(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
file, err := os.Create(path)
|
||||
|
||||
Reference in New Issue
Block a user