feat: require auto-chain action evidence

This commit is contained in:
Eve
2026-04-23 19:34:24 +08:00
parent 242f7ce463
commit 17dd26cde7
3 changed files with 78 additions and 11 deletions

View File

@@ -30,6 +30,14 @@ const EVIDENCE_FIELDS = Object.freeze({
'verificationEvidence',
'checkpointArtifactEvidence',
]),
autoChainNextAction: Object.freeze([
'autoChainNextAction',
'auto_chain_next_action',
]),
autoChainDispatchEvidence: Object.freeze([
'autoChainDispatchEvidence',
'auto_chain_dispatch_evidence',
]),
progressEvidence: Object.freeze([
'progressEvidence',
'progressEvidence.sessionKey',
@@ -64,6 +72,11 @@ const GATE_REQUIREMENTS = Object.freeze({
acceptedFields: EVIDENCE_FIELDS.executionEvidence,
requiredValue: 'tool call, dispatch, file change, verification output, or checkpoint artifact evidence',
}),
autoChainDispatchEvidence: Object.freeze({
evidenceKey: 'autoChainDispatchEvidence',
acceptedFields: EVIDENCE_FIELDS.autoChainDispatchEvidence,
requiredValue: 'dispatched-action evidence for the explicit auto-chain next action',
}),
progressEvidence: Object.freeze({
evidenceKey: 'progressEvidence',
acceptedFields: EVIDENCE_FIELDS.progressEvidence,
@@ -180,6 +193,29 @@ function hasExecutionEvidence(input) {
});
}
function hasExplicitAutoChainNextAction(input) {
return hasAnyNonEmptyString(input, EVIDENCE_FIELDS.autoChainNextAction);
}
function hasAutoChainDispatchEvidence(input) {
return EVIDENCE_FIELDS.autoChainDispatchEvidence.some((fieldPath) => {
const value = getPathValue(input, fieldPath);
if (hasNonEmptyString(value)) return true;
if (Array.isArray(value)) return value.length > 0;
if (value && typeof value === 'object') return Object.keys(value).length > 0;
return false;
});
}
function requiresAutoChainDispatchEvidence(input) {
if (!hasExplicitAutoChainNextAction(input)) return false;
const nextAction = EVIDENCE_FIELDS.autoChainNextAction
.map((fieldPath) => getPathValue(input, fieldPath))
.find((value) => hasNonEmptyString(value));
if (!hasNonEmptyString(nextAction)) return false;
return /^([a-z]+_)+[a-z]+$/i.test(nextAction.trim());
}
function hasProgressEvidence(input) {
return EVIDENCE_FIELDS.progressEvidence.some((fieldPath) => {
const value = getPathValue(input, fieldPath);
@@ -249,6 +285,13 @@ function evaluateGate(input) {
allowedResponseModes.push('evidence_preserving_follow_up');
}
if (requiresAutoChainDispatchEvidence(input) && !hasAutoChainDispatchEvidence(input)) {
failed = true;
reasons.push('explicit auto-chain next action requires dispatched-action evidence');
requiredEvidence.push(describeRequirement(GATE_REQUIREMENTS.autoChainDispatchEvidence));
allowedResponseModes.push('dispatch_required');
}
if (!failed) {
reasons.push('required long-task gate evidence is present or no gated condition was triggered');
allowedResponseModes.push(needsOwnerDecision(input) ? 'button_path' : 'direct_reply');