feat: release Dever 0.1.4 with enforceable project guidance
This commit is contained in:
1 parent
201bdee372
commit
89629c2197
86 files changed
+2798
-338
No files matched your search
+4
-2
@@ -68,11 +68,13 @@ python3 -B test/ci/run.py run --stage sandbox
|
||||
python3 -B test/ci/run.py run --stage bounded-perf
|
||||
```
|
||||
|
||||
以上命令由同一受限托管进程串行执行。若 author 改变输入,应在作者准备完成后再跑正式静态/default/all-feature门;较早输入版本的证据会显示过期。每阶段写入新的独占时间目录,开始即记录 running;缺输入记录 blocked,失败命令保留 exit/count/日志与原始异常。`summary` 与 inventory 消费者只接受最新一次尝试的当前源码/输入证据,不能越过较新的失败或中断回用旧 pass;所有必选阶段通过才返回0。源码摘要覆盖未提交文件,不只看 Git HEAD。
|
||||
以上命令由同一受限托管进程串行执行。若 author 改变输入,应在作者准备完成后再跑正式静态/default/all-feature门;较早输入版本的证据会显示过期。每阶段写入新的独占时间目录,开始即记录 running;缺输入记录 blocked,失败命令保留 exit/count/日志与原始异常。`summary` 与 inventory 消费者只接受最新一次尝试的当前源码/输入证据,不能越过较新的失败或中断回用旧 pass;所有必选阶段通过才返回0。源码摘要覆盖未提交文件及 `skills/` 的指引、模板和示例,不只看 Git HEAD;修改或删除这些文件会使旧结果失效。
|
||||
|
||||
default/all-features 由实际 Cargo metadata 注册 target 和已解析 feature 形成串行矩阵,覆盖 lib/bin/integration/doctest;default 显式保留 workspace 解析的 package features,最小 feature 阶段另按单 package 验证。只有显式启用 `remove_test_executables` 时才删除 Cargo 本次返回、`profile.test=true` 且位于 target 下 `deps/` 的测试可执行文件,失败阶段同样清理;不删除作者工具、静态 init、依赖 archive 或整个 target。删除对象在日志 JSON 中可审计,重跑 Cargo 可恢复。inventory 按 package/target/name 三元组核对完整性,跨 target 同名用例不能代替缺失项。
|
||||
|
||||
Python 阶段包含 `test/ci`、`test/performance`、`test/ecosystem-release` 三个目录;后两者必须已配置 `performance.network_peer/live_peer`,真实自有 loopback检查由主代理执行。三 SDK 路径全部必需,三个明确成功标志全部出现才通过。
|
||||
Python 阶段包含 `test/ci`、`test/performance`、`test/ecosystem-release` 三个目录,并执行 `test/skill_examples.py`,检查 skill 安装后的本地链接、文件预算、标准库签名和教程代码与源码的一致性。教程代码块须链接到受检查项目中的唯一源码片段;文档或源码漂移都会失败。performance 测试必须已配置 `performance.network_peer/live_peer`,真实自有 loopback 检查由主代理执行。三 SDK 路径全部必需,三个明确成功标志全部出现才通过。
|
||||
|
||||
只验证本次 CI 或 skill 改动时,可运行 `python3 -B test/ci/test_gate.py -v` 和 `python3 -B test/skill_examples.py -v`,不需要启动完整 CI、数据库或压力测试。
|
||||
|
||||
PostgreSQL 只接受显式 loopback端口和恰好一个 `{case}` 库名模板,拒绝 query 覆盖 host/dbname 等。增加 `performance.linux_ci.owned_postgres: {"data_directory":"/absolute/owned-cluster","port":51234}`,由托管者创建所需基库并在 finally 停止该自有实例;gate复用现有用例的 schema/tenant 清理,不替用户启动或连接既有业务数据库。
|
||||
|
||||
|
||||
+3
-2
@@ -20,7 +20,7 @@ STAGES = ("static", "default", "all-features", "features", "python", "sdk",
|
||||
"inventory", "author", "native", "postgres", "sandbox", "bounded-perf",
|
||||
"cms-64", "cms-128", "http2-64", "http2-128", "recovery-64", "recovery-128",
|
||||
"live-64", "live-128")
|
||||
SOURCE_ROOTS = ("crates", "test", "sdk", "library", "examples", ".cargo")
|
||||
SOURCE_ROOTS = ("crates", "test", "sdk", "library", "examples", "skills", ".cargo")
|
||||
|
||||
|
||||
class MissingInput(Exception):
|
||||
@@ -129,7 +129,8 @@ def command_plan(stage, config):
|
||||
python = str(absolute_file(tools.get("python"), "performance.linux_ci.tools.python"))
|
||||
if stage == "python":
|
||||
return [[python, "-B", "-m", "unittest", "discover", "-s", directory, "-p", "test_*.py", "-v"]
|
||||
for directory in ("test/ci", "test/performance", "test/ecosystem-release")]
|
||||
for directory in ("test/ci", "test/performance", "test/ecosystem-release")] + [
|
||||
[python, "-B", "test/skill_examples.py", "-v"]]
|
||||
if stage == "sdk":
|
||||
return [[python, "-B", "test/external-sdk/check.py", "--python", python,
|
||||
"--node", str(absolute_file(tools.get("node"), "performance.linux_ci.tools.node")),
|
||||
|
||||
@@ -5,6 +5,7 @@ import contextlib
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
@@ -177,6 +178,81 @@ class GateTests(unittest.TestCase):
|
||||
(root / "Cargo.lock").write_text("changed immutable input")
|
||||
self.assertNotEqual(author_before, gate.input_fingerprint({}, config, "author"))
|
||||
|
||||
def test_skill_sources_invalidate_evidence_but_generated_caches_do_not(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
for name in ("Cargo.toml", "Cargo.lock"):
|
||||
(root / name).write_text(name)
|
||||
sources = ("skills/assets/template/AGENTS.md", "skills/references/development.md",
|
||||
"skills/assets/examples/jobs/module/report/archive/app.dever")
|
||||
for name in sources:
|
||||
path = root / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text("original")
|
||||
original = gate.source_fingerprint(root)
|
||||
for name in sources:
|
||||
path = root / name
|
||||
with self.subTest(source=name):
|
||||
path.write_text("changed")
|
||||
self.assertNotEqual(original, gate.source_fingerprint(root))
|
||||
path.unlink()
|
||||
self.assertNotEqual(original, gate.source_fingerprint(root))
|
||||
path.write_text("original")
|
||||
self.assertEqual(original, gate.source_fingerprint(root))
|
||||
for cache in ("__pycache__", "target", ".git"):
|
||||
path = root / "skills/assets/examples/jobs" / cache / "generated"
|
||||
path.parent.mkdir()
|
||||
path.write_text("generated bytes")
|
||||
self.assertEqual(original, gate.source_fingerprint(root))
|
||||
path.write_text("changed generated bytes")
|
||||
self.assertEqual(original, gate.source_fingerprint(root))
|
||||
path.unlink()
|
||||
self.assertEqual(original, gate.source_fingerprint(root))
|
||||
|
||||
def test_python_plan_runs_real_skill_checks_and_rejects_each_failure(self):
|
||||
config = {"tools": {"cargo": sys.executable, "python": sys.executable}}
|
||||
commands = gate.command_plan("python", config)
|
||||
skill_commands = [command for command in commands if "test/skill_examples.py" in command]
|
||||
self.assertEqual(len(skill_commands), 1)
|
||||
command = skill_commands[0]
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
(root / "test").mkdir()
|
||||
shutil.copyfile(gate.ROOT / "test/skill_examples.py", root / "test/skill_examples.py")
|
||||
for name in ("skills", "library"):
|
||||
shutil.copytree(gate.ROOT / name, root / name)
|
||||
logs = root / "logs"
|
||||
logs.mkdir()
|
||||
with patch.object(gate, "ROOT", root):
|
||||
record, content = gate.run_command(command, logs, 0, 10)
|
||||
gate.check_output("python", record, content)
|
||||
test_count = record["python_tests"]
|
||||
self.assertGreater(test_count, 0)
|
||||
for number, (relative, broken, case) in enumerate((
|
||||
("skills/SKILL.md", "[broken](missing.md)",
|
||||
"test_relocated_skill_links_and_release_file_limits"),
|
||||
("skills/references/signatures.md", "```signatures\ndever.io.missing() ()\n```\n",
|
||||
"test_signature_index_matches_public_library_declarations"),
|
||||
("skills/references/language.md",
|
||||
(root / "skills/references/language.md").read_text().replace(
|
||||
'"Hello, "', '"Incorrect tutorial, "', 1
|
||||
),
|
||||
"test_tutorial_code_matches_relocated_checked_sources"),
|
||||
), 1):
|
||||
path = root / relative
|
||||
original = path.read_bytes()
|
||||
path.write_text(broken)
|
||||
with self.subTest(check=case), self.assertRaisesRegex(RuntimeError, "command exited 1"):
|
||||
gate.run_command(command, logs, number, 10)
|
||||
result = json.loads((logs / f"{number:03d}.command.json").read_text())
|
||||
self.assertEqual(result["exit_code"], 1)
|
||||
self.assertEqual(result["python_tests"], test_count)
|
||||
log = (logs / f"{number:03d}.log").read_text()
|
||||
self.assertTrue(
|
||||
f"FAIL: {case}" in log or f"ERROR: {case}" in log, log
|
||||
)
|
||||
path.write_bytes(original)
|
||||
|
||||
def test_cleanup_cannot_delete_author_binary(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
|
||||
Reference in new issue
Block a user