refactor: align long-task gate lock evidence aliases

This commit is contained in:
Eve
2026-04-23 10:00:58 +08:00
parent 10722fbba5
commit b75e034ca0

View File

@@ -1,6 +1,42 @@
#!/usr/bin/env node #!/usr/bin/env node
import fs from 'fs'; import fs from 'fs';
const EVIDENCE_FIELDS = Object.freeze({
externalizedCheckpoint: Object.freeze([
'externalizedCheckpointPath',
'externalizedTrigger',
'checkpointPath',
]),
concreteNextAction: Object.freeze([
'nextStep',
'requiredNextAction',
'concreteNextAction',
]),
buttonPathMode: Object.freeze([
'handoffMode',
'handoff.mode',
'replyClosureMode',
]),
});
const GATE_REQUIREMENTS = Object.freeze({
externalizedCheckpoint: Object.freeze({
evidenceKey: 'externalizedCheckpoint',
acceptedFields: EVIDENCE_FIELDS.externalizedCheckpoint,
requiredValue: 'non-empty string',
}),
concreteNextAction: Object.freeze({
evidenceKey: 'concreteNextAction',
acceptedFields: EVIDENCE_FIELDS.concreteNextAction,
requiredValue: 'non-empty string',
}),
buttonPathMode: Object.freeze({
evidenceKey: 'buttonPathMode',
acceptedFields: EVIDENCE_FIELDS.buttonPathMode,
requiredValue: 'button_path',
}),
});
function fail(code, message) { function fail(code, message) {
process.stderr.write(`${code}: ${message}\n`); process.stderr.write(`${code}: ${message}\n`);
process.exit(1); process.exit(1);
@@ -41,18 +77,39 @@ function isLongTask(input) {
return input.classification === 'long_task'; return input.classification === 'long_task';
} }
function hasNonEmptyString(value) {
return typeof value === 'string' && value.trim().length > 0;
}
function getPathValue(input, path) {
return path.split('.').reduce((current, key) => {
if (current === null || current === undefined) return undefined;
return current[key];
}, input);
}
function hasAnyNonEmptyString(input, fieldPaths) {
return fieldPaths.some((fieldPath) => hasNonEmptyString(getPathValue(input, fieldPath)));
}
function hasAcceptedValue(input, fieldPaths, acceptedValue) {
return fieldPaths.some((fieldPath) => getPathValue(input, fieldPath) === acceptedValue);
}
function describeRequirement(requirement) {
return {
evidenceKey: requirement.evidenceKey,
acceptedFields: [...requirement.acceptedFields],
requiredValue: requirement.requiredValue,
};
}
function hasExternalizedCheckpointPath(input) { function hasExternalizedCheckpointPath(input) {
if (typeof input.externalizedCheckpointPath === 'string' && input.externalizedCheckpointPath.trim()) return true; return hasAnyNonEmptyString(input, EVIDENCE_FIELDS.externalizedCheckpoint);
if (typeof input.externalizedTrigger === 'string' && input.externalizedTrigger.trim()) return true;
if (typeof input.checkpointPath === 'string' && input.checkpointPath.trim()) return true;
return false;
} }
function hasConcreteNextAction(input) { function hasConcreteNextAction(input) {
if (typeof input.nextStep === 'string' && input.nextStep.trim()) return true; return hasAnyNonEmptyString(input, EVIDENCE_FIELDS.concreteNextAction);
if (typeof input.requiredNextAction === 'string' && input.requiredNextAction.trim()) return true;
if (typeof input.concreteNextAction === 'string' && input.concreteNextAction.trim()) return true;
return false;
} }
function wantsSilentContinuation(input) { function wantsSilentContinuation(input) {
@@ -76,10 +133,7 @@ function needsOwnerDecision(input) {
} }
function usesButtonPath(input) { function usesButtonPath(input) {
if (typeof input.handoffMode === 'string') return input.handoffMode === 'button_path'; return hasAcceptedValue(input, EVIDENCE_FIELDS.buttonPathMode, 'button_path');
if (input.handoff && typeof input.handoff.mode === 'string') return input.handoff.mode === 'button_path';
if (typeof input.replyClosureMode === 'string') return input.replyClosureMode === 'button_path';
return false;
} }
function evaluateGate(input) { function evaluateGate(input) {
@@ -103,21 +157,21 @@ function evaluateGate(input) {
if (wantsSilentContinuation(input) && !hasExternalizedCheckpointPath(input)) { if (wantsSilentContinuation(input) && !hasExternalizedCheckpointPath(input)) {
failed = true; failed = true;
reasons.push('silent long-task cannot continue without externalized checkpoint path'); reasons.push('silent long-task cannot continue without externalized checkpoint path');
requiredEvidence.push('externalizedCheckpointPath'); requiredEvidence.push(describeRequirement(GATE_REQUIREMENTS.externalizedCheckpoint));
allowedResponseModes.push('non_silent_follow_up'); allowedResponseModes.push('non_silent_follow_up');
} }
if (claimsExecution(input) && !hasConcreteNextAction(input)) { if (claimsExecution(input) && !hasConcreteNextAction(input)) {
failed = true; failed = true;
reasons.push('claimed execution requires evidence of a concrete next action'); reasons.push('claimed execution requires evidence of a concrete next action');
requiredEvidence.push('nextStep'); requiredEvidence.push(describeRequirement(GATE_REQUIREMENTS.concreteNextAction));
allowedResponseModes.push('checkpoint_only'); allowedResponseModes.push('checkpoint_only');
} }
if (needsOwnerDecision(input) && !usesButtonPath(input)) { if (needsOwnerDecision(input) && !usesButtonPath(input)) {
failed = true; failed = true;
reasons.push('owner decision flow must end in button-path, not plain text'); reasons.push('owner decision flow must end in button-path, not plain text');
requiredEvidence.push('handoff.mode=button_path'); requiredEvidence.push(describeRequirement(GATE_REQUIREMENTS.buttonPathMode));
allowedResponseModes.push('button_path'); allowedResponseModes.push('button_path');
} }