Merge remote-tracking branch 'web/master' into feat/five_min_design

冲突两处,均为两侧独立新增:
- provider_tool_plan.rs:本分支的 force_autonomous_owner_artifact_delivery 与
  master 的 force_root_goal_contract 是各自独立的 let 绑定,两者都保留。
- provider_request_builders.rs:本分支新增测试
  full_dag_pre_code_owner_requests_do_not_advertise_manual_verification,
  master 把紧随其后的 trusted_root_supervisor_receives_dynamic_goal_control_tools
  改名为 trusted_root_supervisor_first_turn_only_receives_goal_contract_tool。
  保留本分支新增的测试,共享的那个测试采用 master 的新名(其函数体已随
  master 自动合并)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 08:28:21 +00:00
60 changed files with 1917 additions and 401 deletions
+64 -12
View File
@@ -19,7 +19,14 @@ import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const repoRoot = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'..',
);
const bashExecutable =
process.platform === 'win32'
? 'C:\\Program Files\\Git\\bin\\bash.exe'
: 'bash';
const failures = [];
const requestedFiles = [];
@@ -48,7 +55,10 @@ function validateDefaultPage(filePath) {
}
for (const [pattern, label] of [
[/(?:今天|今晚|明天|昨天|昨日)/u, '相对日期'],
[/(?:20\d{2}[-/.年]\d{1,2}(?:[-/.月]\d{1,2}日?)?|\d{1,2}月\d{1,2}日)/u, '具体日期'],
[
/(?:20\d{2}[-/.年]\d{1,2}(?:[-/.月]\d{1,2}日?)?|\d{1,2}月\d{1,2}日)/u,
'具体日期',
],
[/(?:[01]?\d|2[0-3]):[0-5]\d/u, '具体维护时间'],
]) {
if (pattern.test(source)) {
@@ -58,15 +68,48 @@ function validateDefaultPage(filePath) {
}
function runScript(scriptPath, args, env) {
return spawnSync('bash', [scriptPath, ...args], {
const bashArgs = args.map((arg) =>
path.isAbsolute(arg) ? toBashPath(arg) : arg,
);
const bashEnv = Object.fromEntries(
Object.entries(env).map(([key, value]) => [
key,
path.isAbsolute(value) ? toBashPath(value) : value,
]),
);
const envAssignments = Object.entries(bashEnv).map(
([key, value]) => `${key}=${value}`,
);
const commandArgs = [
...envAssignments,
'bash',
toBashPath(scriptPath),
...bashArgs,
];
const command = `exec env ${commandArgs.map(shellQuote).join(' ')}`;
return spawnSync(bashExecutable, ['-c', command], {
cwd: repoRoot,
env: { ...process.env, ...env },
env: process.env,
encoding: 'utf8',
});
}
function shellQuote(value) {
return `'${String(value).replaceAll("'", `'"'"'`)}'`;
}
function toBashPath(filePath) {
const windowsDrive = /^([A-Za-z]):[\\/](.*)$/u.exec(filePath);
if (windowsDrive) {
return `/${windowsDrive[1].toLowerCase()}/${windowsDrive[2].replaceAll('\\', '/')}`;
}
return filePath;
}
function validateRuntimePageLifecycle() {
const tempRoot = mkdtempSync(path.join(os.tmpdir(), 'genarrative-maintenance-'));
const tempRoot = mkdtempSync(
path.join(os.tmpdir(), 'genarrative-maintenance-'),
);
const markerFile = path.join(tempRoot, 'state', 'enabled');
const runtimePageFile = path.join(tempRoot, 'state', 'page.html');
const sourcePageFile = path.join(tempRoot, 'announcement.html');
@@ -81,6 +124,9 @@ function validateRuntimePageLifecycle() {
if (!onScriptSource.includes('replace_file_atomically')) {
fail('maintenance-on 必须通过统一 helper 原子替换公告页和 marker。');
}
if (!onScriptSource.includes('install -m 0644')) {
fail('maintenance-on 安装运行态公告页时必须显式设置 0644 权限。');
}
if (/\bmv\s+-[^\s]*T\b/u.test(onScriptSource)) {
fail('maintenance-on 不得使用 GNU mv 专属的 -T 参数。');
}
@@ -95,7 +141,9 @@ function validateRuntimePageLifecycle() {
env,
);
if (enable.status !== 0) {
fail(`maintenance-on --page-file 执行失败: ${enable.stderr || enable.stdout}`);
fail(
`maintenance-on --page-file 执行失败: ${enable.stderr || enable.stdout}`,
);
return;
}
if (!existsSync(markerFile)) {
@@ -107,7 +155,10 @@ function validateRuntimePageLifecycle() {
if (readFileSync(runtimePageFile, 'utf8') !== announcement) {
fail('运行态公告页内容与输入文件不一致。');
}
if ((statSync(runtimePageFile).mode & 0o777) !== 0o644) {
if (
process.platform !== 'win32' &&
(statSync(runtimePageFile).mode & 0o777) !== 0o644
) {
fail('运行态公告页权限必须为 0644。');
}
}
@@ -130,7 +181,9 @@ function validateRuntimePageLifecycle() {
chmodSync(runtimePageFile, 0o644);
const genericEnable = runScript(onScript, ['generic maintenance'], env);
if (genericEnable.status !== 0) {
fail(`通用 maintenance-on 执行失败: ${genericEnable.stderr || genericEnable.stdout}`);
fail(
`通用 maintenance-on 执行失败: ${genericEnable.stderr || genericEnable.stdout}`,
);
}
if (existsSync(runtimePageFile)) {
fail('新维护窗口未提供 --page-file 时必须清理残留公告页。');
@@ -265,10 +318,9 @@ function validateGatewayConfiguration() {
}
}
for (const filePath of
requestedFiles.length > 0
? requestedFiles
: [path.join(repoRoot, 'public/maintenance.html')]) {
for (const filePath of requestedFiles.length > 0
? requestedFiles
: [path.join(repoRoot, 'public/maintenance.html')]) {
validateDefaultPage(filePath);
}
+66 -38
View File
@@ -145,7 +145,7 @@ test('pre-commit hook fixes staged imports and formatting without swallowing uns
'const original = { value: 1 };\nconst keep = 2;\n',
);
assert.equal(
readFileSync(partialPath, 'utf8'),
readFileSync(partialPath, 'utf8').replaceAll('\r\n', '\n'),
'const original = { value: 1 };\nconst keep={unstaged:true}\n',
);
assert.equal(
@@ -160,53 +160,73 @@ test('pre-commit hook fixes staged imports and formatting without swallowing uns
test('pre-push runs repository parity only for master updates', () => {
const tempDir = mkdtempSync(join(tmpdir(), 'genarrative-pre-push-'));
try {
const binDir = join(tempDir, 'bin');
mkdirSync(binDir);
const npmLog = join(tempDir, 'npm.log');
const fakeNpm = join(binDir, 'npm');
const fakeGit = join(binDir, 'git');
writeFileSync(
fakeNpm,
`#!/usr/bin/env bash\nprintf '%s\\n' "$*" >> "${npmLog}"\n`,
const npmLog = join(tempDir, 'repo', 'npm.log');
const tempRepo = join(tempDir, 'repo');
mkdirSync(tempRepo);
git(tempRepo, 'init', '--quiet');
git(tempRepo, 'config', 'user.email', 'git-hooks-test@example.invalid');
git(tempRepo, 'config', 'user.name', 'Git Hooks Test');
writeFileSync(join(tempRepo, 'tracked.txt'), 'baseline\n');
git(tempRepo, 'add', 'tracked.txt');
git(
tempRepo,
'-c',
'commit.gpgsign=false',
'commit',
'--quiet',
'-m',
'baseline',
);
chmodSync(fakeNpm, 0o755);
const localSha = git(tempRepo, 'rev-parse', 'HEAD').trim();
writeFileSync(
fakeGit,
'#!/usr/bin/env bash\n' +
'if [[ "$1" == "rev-parse" && "$2" == "HEAD" ]]; then\n' +
' printf "%s\\n" "1111111111111111111111111111111111111111"\n' +
' exit 0\n' +
'fi\n' +
'if [[ "$1" == "diff" ]]; then exit 0; fi\n' +
'exit 1\n',
join(tempRepo, 'git'),
`#!/usr/bin/env bash
if [[ "$1" == 'rev-parse' && "$2" == 'HEAD' ]]; then
printf '%s\\n' '${localSha}'
exit 0
fi
if [[ "$1" == 'diff' ]]; then exit 0; fi
exit 1
`,
);
chmodSync(fakeGit, 0o755);
const env = {
...process.env,
PATH: `${binDir}${delimiter}${process.env.PATH ?? ''}`,
chmodSync(join(tempRepo, 'git'), 0o755);
writeFileSync(
join(tempRepo, 'pre-push-master.sh'),
readFileSync(join(repoRoot, 'scripts', 'pre-push-master.sh'), 'utf8'),
);
writeFileSync(
join(tempRepo, 'npm'),
'#!/usr/bin/env bash\nprintf \'%s\\n\' "$*" >> npm.log\n',
);
chmodSync(join(tempRepo, 'npm'), 0o755);
const spawnHook = (input) => {
writeFileSync(join(tempRepo, 'push.input'), input);
return spawnSync(
'bash',
[
'-c',
'PATH="$PWD:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; export PATH; source ./pre-push-master.sh origin example.invalid < push.input',
],
{
cwd: tempRepo,
encoding: 'utf8',
env: process.env,
},
);
};
const hook = join(repoRoot, 'scripts', 'pre-push-master.sh');
const featurePush = spawnSync('bash', [hook, 'origin', 'example.invalid'], {
cwd: repoRoot,
encoding: 'utf8',
env,
input:
'refs/heads/feature 1111111111111111111111111111111111111111 refs/heads/feature 2222222222222222222222222222222222222222\n',
});
const featurePush = spawnHook(
'refs/heads/feature 1111111111111111111111111111111111111111 refs/heads/feature 2222222222222222222222222222222222222222\n',
);
assert.equal(featurePush.status, 0, featurePush.stderr);
assert.equal(readFileOrEmpty(npmLog), '');
const masterPush = spawnSync('bash', [hook, 'origin', 'example.invalid'], {
cwd: repoRoot,
encoding: 'utf8',
env,
input:
'refs/heads/master 1111111111111111111111111111111111111111 refs/heads/master 2222222222222222222222222222222222222222\n',
});
const masterPush = spawnHook(
`refs/heads/master ${localSha} refs/heads/master 2222222222222222222222222222222222222222\n`,
);
assert.equal(masterPush.status, 0, masterPush.stderr);
assert.equal(
readFileSync(npmLog, 'utf8'),
'run check:repository-ci -- 2222222222222222222222222222222222222222 1111111111111111111111111111111111111111\n',
`run check:repository-ci -- 2222222222222222222222222222222222222222 ${localSha}\n`,
);
} finally {
rmSync(tempDir, { force: true, recursive: true });
@@ -254,6 +274,14 @@ function readFileOrEmpty(path) {
}
}
function toBashPath(path) {
const windowsDrive = /^([A-Za-z]):[\\/](.*)$/u.exec(path);
if (windowsDrive) {
return `/mnt/${windowsDrive[1].toLowerCase()}/${windowsDrive[2].replaceAll('\\', '/')}`;
}
return path;
}
function git(cwd, ...args) {
return execFileSync('git', args, { cwd, encoding: 'utf8' });
}