From bca4db113f8210dd39ec2e62846a3f0b620f310f Mon Sep 17 00:00:00 2001 From: root Date: Sun, 12 Jul 2026 16:45:49 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=B0=86=E5=B7=A5=E4=BD=9C=E5=8C=BA?= =?UTF-8?q?=E5=A5=91=E7=BA=A6=E6=BC=82=E7=A7=BB=E9=99=8D=E7=BA=A7=E4=B8=BA?= =?UTF-8?q?=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/validation.ts | 34 ++++++++++++++++++----------- src/selftest/cases/76-aipod-spec.ts | 4 ++-- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/common/validation.ts b/src/common/validation.ts index 04fb178..559f537 100644 --- a/src/common/validation.ts +++ b/src/common/validation.ts @@ -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 { diff --git a/src/selftest/cases/76-aipod-spec.ts b/src/selftest/cases/76-aipod-spec.ts index 48fe86a..889597f 100644 --- a/src/selftest/cases/76-aipod-spec.ts +++ b/src/selftest/cases/76-aipod-spec.ts @@ -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);