fix: let team members correct handshake contracts

This commit is contained in:
冰朔 2026-08-05 11:58:57 +08:00
commit 1340927b73
2 changed files with 69 additions and 10 deletions

View file

@ -88,6 +88,7 @@ async function modelAcknowledge({
config, config,
identity, identity,
challenge, challenge,
validatorError = null,
fetchImpl = globalThis.fetch, fetchImpl = globalThis.fetch,
}) { }) {
const response = await fetchImpl(completionEndpoint(config.apiUrl), { const response = await fetchImpl(completionEndpoint(config.apiUrl), {
@ -121,6 +122,7 @@ async function modelAcknowledge({
caller_nonce: challenge.caller_nonce, caller_nonce: challenge.caller_nonce,
role_acknowledged: true, role_acknowledged: true,
}, },
validator_error_from_previous_attempt: validatorError,
scoped_duties: challenge.scoped_duties, scoped_duties: challenge.scoped_duties,
identity_source: identity, identity_source: identity,
}), }),
@ -213,16 +215,27 @@ function createMemberRuntime(config, options = {}) {
) { ) {
throw new Error("invalid_team_scope"); throw new Error("invalid_team_scope");
} }
const acknowledgement = validateAcknowledgement( let acknowledgement = null;
await modelAcknowledge({ let validatorError = null;
config, for (let attempt = 1; attempt <= 3; attempt += 1) {
identity, try {
challenge, acknowledgement = validateAcknowledgement(
fetchImpl: options.fetchImpl, await modelAcknowledge({
}), config,
config, identity,
challenge, challenge,
); validatorError,
fetchImpl: options.fetchImpl,
}),
config,
challenge,
);
break;
} catch (error) {
validatorError = String(error.message || error).slice(0, 200);
if (attempt === 3) throw error;
}
}
const payload = { const payload = {
schema: "guanghu.persona-team-member-handshake-payload/v1", schema: "guanghu.persona-team-member-handshake-payload/v1",
persona_id: config.personaId, persona_id: config.personaId,

View file

@ -139,6 +139,52 @@ test("member runtime rejects an unscoped or wrong-controller challenge", async (
} }
}); });
test("member model gets a bounded retry with the exact validator error", async () => {
const { root, config } = fixture();
let attempts = 0;
let observedValidatorError = null;
try {
const runtime = createMemberRuntime(config, {
fetchImpl: async (_url, request) => {
attempts += 1;
const body = JSON.parse(request.body);
const input = JSON.parse(body.messages[1].content);
observedValidatorError =
input.validator_error_from_previous_attempt || observedValidatorError;
const acknowledgement = {
...input.exact_contract,
independent_subject_boundary: "归灯不是铸渊的副本。",
human_boundary: "不冒充冰朔。",
current_model_boundary: "当前模型是可替换运行位。",
...(attempts > 1
? { responsibility_ack: "只接受本轮列出的有界职责。" }
: {}),
};
return {
ok: true,
async json() {
return {
choices: [
{ message: { content: JSON.stringify(acknowledgement) } },
],
};
},
};
},
});
const response = await runtime.handshake({
caller_nonce: "ZY-TEAM-CHALLENGE-RETRY-0001",
team_controller_id: "ICE-P-ZY001",
scoped_duties: ["执行确定性部署预检"],
});
assert.equal(response.ok, true);
assert.equal(attempts, 2);
assert.equal(observedValidatorError, "ack_responsibility_ack_missing");
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test("all three member handshake packages pass the resident deployment policy", () => { test("all three member handshake packages pass the resident deployment policy", () => {
const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../.."); const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../..");
const packages = [ const packages = [