fix: render bounded webhook credential blockers

This commit is contained in:
Codex
2026-07-13 12:44:06 +02:00
parent 21aed20f62
commit a539a4ff05
4 changed files with 97 additions and 2 deletions
@@ -29,11 +29,12 @@
## Target 原入口证据
- 已通过 `trans NC01:/root/.worktrees/unidesk/selfmedia-repo-github-authority` 执行全部源码、Git 和验证操作。
- `bun test scripts/src/platform-infra-gitea-config.test.ts scripts/src/platform-infra-gitea-disclosure.test.ts`9 项通过,0 项失败。
- `bun test scripts/src/platform-infra-gitea-config.test.ts scripts/src/platform-infra-gitea-disclosure.test.ts`11 项通过,0 项失败。
- `bun scripts/cli.ts platform-infra gitea plan --target NC01`policy 为 `ok`
- `git diff --check`、Node 语法、Python `py_compile`、Shell `sh -n`:通过。
- `mirror bootstrap --target NC01 --repo selfmedia-nc01 --dry-run``OK=false`blocker 为 `github-upstream:selfmedia-nc01`,符合 fail-closed。
- `mirror webhook status --target NC01 --repo selfmedia-nc01 --full`:在本地 credential blocker 处早返回,包含 `sourceRef``present=false``fingerprint=null``permissionResult=blocked-missing-credential` 与有界错误码;未访问运行面,JSON/stderr 均无 stack 或绝对 worktree 路径。
- 默认 `mirror webhook status` compact 输出使用短表,按 `error.blockers` 精确匹配每个 credential/repository;带或不带 `--repo` 均只显示正确的 selfmedia blocker`--full`/`--raw` 保持结构化。
- `mirror bootstrap --target JD01 --dry-run``OK=true`,证明 JD01 不依赖 NC01 override。
- 专用凭据只读状态:`sourceRef=pikainc-selfmedia-gh-token.txt``presence=false``fingerprint=null``permissionResult=blocked-missing-credential`
- 因 credential material 缺失,未执行权限 API 探测;不得声称 Contents、Metadata 或 Webhooks 权限已实际授予。
@@ -83,4 +83,59 @@ describe("platform-infra Gitea repository GitHub credentials", () => {
expect(stdout).not.toContain('"stack"');
expect(result.stderr.toString()).not.toContain("platform-infra-gitea.ts:");
});
test("renders missing selfmedia webhook status as compact text by default", () => {
const result = Bun.spawnSync({
cmd: [
"bun",
"scripts/cli.ts",
"platform-infra",
"gitea",
"mirror",
"webhook",
"status",
"--target",
"NC01",
"--repo",
"selfmedia-nc01",
],
stdout: "pipe",
stderr: "pipe",
});
const stdout = result.stdout.toString();
expect(stdout).toContain("PLATFORM-INFRA GITEA MIRROR WEBHOOK STATUS");
expect(stdout).toContain("SOURCE_REF");
expect(stdout).toContain("pikainc-selfmedia-gh-token.txt");
expect(stdout).toContain("PERMISSION_RESULT");
expect(stdout).toContain("github-upstream:selfmedia-nc01");
expect(stdout.trimStart()).not.toStartWith("{");
expect(stdout).not.toContain('"stack"');
expect(result.stderr.toString()).not.toContain("platform-infra-gitea.ts:");
});
test("matches compact blocker rows to their repository without --repo", () => {
const result = Bun.spawnSync({
cmd: [
"bun",
"scripts/cli.ts",
"platform-infra",
"gitea",
"mirror",
"webhook",
"status",
"--target",
"NC01",
],
stdout: "pipe",
stderr: "pipe",
});
const stdout = result.stdout.toString();
expect(stdout).toContain("pikainc/selfmedia");
expect(stdout).toContain("pikainc-selfmedia-gh-token.txt");
expect(stdout).toContain("github-upstream:selfmedia-nc01");
expect(stdout).not.toContain("pikasTech/agentrun");
expect(stdout.trimStart()).not.toStartWith("{");
});
});
@@ -107,6 +107,44 @@ export function renderMirrorWebhookApply(result: Record<string, unknown>): Rende
]);
}
export function renderMirrorWebhookCredentialBlocked(result: Record<string, unknown>): RenderedCliResult {
const target = record(result.target);
const error = record(result.error);
const blockerIds = Array.isArray(error.blockers) ? error.blockers.map(String) : [];
const repositories = arrayRecords(result.repositories);
const credentials = arrayRecords(result.credentials);
const rows = blockerIds.map((blocker) => {
const credential = credentials.find((item) => stringValue(item.id) === blocker) ?? {};
const permissionResult = record(credential.permissionResult);
const permissions = record(permissionResult.permissions);
const repoKey = blocker.includes(":") ? blocker.slice(blocker.indexOf(":") + 1) : "";
const repository = repositories.find((item) => stringValue(item.key) === repoKey) ?? {};
const permissionText = [
`status=${stringValue(permissionResult.status)}`,
`contents=${stringValue(permissions.contents)}`,
`metadata=${stringValue(permissions.metadata)}`,
`webhooks=${stringValue(permissions.webhooks)}`,
].join(",");
return [
stringValue(target.id),
stringValue(credential.repository, stringValue(repository.upstreamRepository)),
stringValue(credential.sourceRef),
boolText(credential.present),
stringValue(credential.fingerprint),
permissionText,
blocker,
boolText(credential.valuesPrinted),
];
});
const next = record(result.next);
return rendered(result, "platform-infra gitea mirror webhook status", [
"PLATFORM-INFRA GITEA MIRROR WEBHOOK STATUS",
...(rows.length === 0 ? ["-"] : table(["TARGET", "REPOSITORY", "SOURCE_REF", "PRESENT", "FINGERPRINT", "PERMISSION_RESULT", "BLOCKER", "VALUES_PRINTED"], rows)),
"",
`NEXT ${stringValue(next.webhookStatusFull)}`,
]);
}
export function renderMirrorWebhookStatus(result: Record<string, unknown>, disclosure: Record<string, unknown>): RenderedCliResult {
const repos = arrayRecords(result.repositories).map((repo) => {
const delivery = record(repo.selectedPushDelivery);
+2 -1
View File
@@ -30,6 +30,7 @@ import {
renderMirrorStatus,
renderMirrorSync,
renderMirrorWebhookApply,
renderMirrorWebhookCredentialBlocked,
renderMirrorWebhookStatus,
renderPlan,
renderStatus,
@@ -298,7 +299,7 @@ async function mirrorWebhookCommand(config: UniDeskConfig, args: string[]): Prom
if (action === "status") {
const options = parseMirrorWebhookStatusOptions(args.slice(1));
const result = await mirrorWebhookStatus(config, options);
if (result.credentialBlocked === true) return result;
if (result.credentialBlocked === true) return options.full || options.raw ? result : renderMirrorWebhookCredentialBlocked(result);
if (options.raw) return rawMirrorWebhookStatus(result);
const disclosure = buildMirrorWebhookStatusDisclosure(result, options);
return options.full ? disclosure : renderMirrorWebhookStatus(result, disclosure);