fix: 将工作区契约漂移降级为告警

This commit is contained in:
root
2026-07-12 16:45:49 +02:00
parent 74225d535f
commit bca4db113f
2 changed files with 23 additions and 15 deletions
+21 -13
View File
@@ -53,11 +53,11 @@ export function validateCreateRun(input: unknown): CreateRunInput {
validateBackendSecretScope(backendProfile, executionPolicy);
const workspaceRef = validateWorkspaceRef(requiredRecord(record, "workspaceRef"));
const resourceBundleRef = validateResourceBundleRef(record.resourceBundleRef);
validatePrimaryWorkspaceContract(workspaceRef, resourceBundleRef);
const normalizedWorkspaceRef = validatePrimaryWorkspaceContract(workspaceRef, resourceBundleRef);
return {
tenantId,
projectId: requiredString(record, "projectId"),
workspaceRef,
workspaceRef: normalizedWorkspaceRef,
sessionRef: validateSessionRef(record.sessionRef),
resourceBundleRef,
providerId: requiredString(record, "providerId"),
@@ -152,19 +152,27 @@ export function validateWorkspaceRef(record: JsonRecord): WorkspaceRef {
return record as WorkspaceRef;
}
export function validatePrimaryWorkspaceContract(workspaceRef: WorkspaceRef | null, resourceBundleRef: ResourceBundleRef | null): void {
if (!resourceBundleRef) return;
if (!workspaceRef) throw new AgentRunError("schema-invalid", "resourceBundleRef requires workspaceRef", { httpStatus: 400 });
if (workspaceRef.kind !== "opaque") {
throw new AgentRunError("schema-invalid", "gitbundle primary workspace requires workspaceRef.kind=opaque so the resource bundle remains the only source authority", { httpStatus: 400 });
export function validatePrimaryWorkspaceContract(workspaceRef: WorkspaceRef | null, resourceBundleRef: ResourceBundleRef | null): WorkspaceRef {
if (!resourceBundleRef) {
if (!workspaceRef) throw new AgentRunError("schema-invalid", "workspaceRef is required", { httpStatus: 400 });
return workspaceRef;
}
if (workspaceRef.path !== ".") {
throw new AgentRunError("schema-invalid", "gitbundle primary workspace requires workspaceRef.path=. as its declared targetPath", { httpStatus: 400 });
}
const rejectedKeys = Object.keys(workspaceRef).filter((key) => key !== "kind" && key !== "path");
if (rejectedKeys.length > 0) {
throw new AgentRunError("schema-invalid", "gitbundle primary workspaceRef only supports kind and path; resourceBundleRef is the source authority", { httpStatus: 400, details: { rejectedKeys: rejectedKeys.sort(), valuesPrinted: false } });
const normalized: WorkspaceRef = { kind: "opaque", path: "." };
const received = workspaceRef ?? ({} as WorkspaceRef);
const ignoredKeys = Object.keys(received).filter((key) => key !== "kind" && key !== "path").sort();
if (received.kind !== normalized.kind || received.path !== normalized.path || ignoredKeys.length > 0) {
console.warn(JSON.stringify({
event: "agentrun.contract.warning",
code: "gitbundle-workspace-ref-normalized",
receivedKind: typeof received.kind === "string" ? received.kind : null,
receivedPath: typeof received.path === "string" ? received.path : null,
ignoredKeys,
action: "continue-with-canonical-workspace-ref",
blocking: false,
valuesPrinted: false,
}));
}
return normalized;
}
function validateCommitId(commitId: string, fieldName: string): void {
+2 -2
View File
@@ -187,8 +187,8 @@ const selfTest: SelfTestCase = async (context) => {
const primaryContractBundle = validateResourceBundleRef({ kind: "gitbundle", repoUrl: "git@github.com:pikasTech/unidesk.git", ref: "master", bundles: [{ subpath: "tools", targetPath: "tools" }] });
assert.ok(primaryContractBundle);
assert.doesNotThrow(() => validatePrimaryWorkspaceContract({ kind: "opaque", path: "." }, primaryContractBundle));
assert.throws(() => validatePrimaryWorkspaceContract({ kind: "opaque", path: ".", repo: "pikasTech/unidesk" }, primaryContractBundle), /only supports kind and path/u);
assert.throws(() => validatePrimaryWorkspaceContract({ kind: "opaque", path: ".", branch: "master" }, primaryContractBundle), /only supports kind and path/u);
assert.deepEqual(validatePrimaryWorkspaceContract({ kind: "opaque", path: ".", repo: "pikasTech/unidesk" }, primaryContractBundle), { kind: "opaque", path: "." });
assert.deepEqual(validatePrimaryWorkspaceContract({ kind: "opaque", branch: "master" }, primaryContractBundle), { kind: "opaque", path: "." });
const submitPlan = await runCliJson(context, server.baseUrl, ["queue", "submit", "--aipod", "Artificer", "--prompt", "处理 pikasTech/unidesk#245", "--model", "gpt-5.4", "--reasoning-effort", "high", "--idempotency-key", "selftest-aipod-cli", "--dry-run"]);
assert.equal(submitPlan.ok, true);