From b75e034ca04d0eaa1c4757df42df4c4bdfe53469 Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 23 Apr 2026 10:00:58 +0800 Subject: [PATCH] refactor: align long-task gate lock evidence aliases --- scripts/long_task_gate_lock.mjs | 84 +++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/scripts/long_task_gate_lock.mjs b/scripts/long_task_gate_lock.mjs index 24f9524..e9e680b 100644 --- a/scripts/long_task_gate_lock.mjs +++ b/scripts/long_task_gate_lock.mjs @@ -1,6 +1,42 @@ #!/usr/bin/env node 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) { process.stderr.write(`${code}: ${message}\n`); process.exit(1); @@ -41,18 +77,39 @@ function isLongTask(input) { 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) { - if (typeof input.externalizedCheckpointPath === 'string' && input.externalizedCheckpointPath.trim()) return true; - if (typeof input.externalizedTrigger === 'string' && input.externalizedTrigger.trim()) return true; - if (typeof input.checkpointPath === 'string' && input.checkpointPath.trim()) return true; - return false; + return hasAnyNonEmptyString(input, EVIDENCE_FIELDS.externalizedCheckpoint); } function hasConcreteNextAction(input) { - if (typeof input.nextStep === 'string' && input.nextStep.trim()) return true; - if (typeof input.requiredNextAction === 'string' && input.requiredNextAction.trim()) return true; - if (typeof input.concreteNextAction === 'string' && input.concreteNextAction.trim()) return true; - return false; + return hasAnyNonEmptyString(input, EVIDENCE_FIELDS.concreteNextAction); } function wantsSilentContinuation(input) { @@ -76,10 +133,7 @@ function needsOwnerDecision(input) { } function usesButtonPath(input) { - if (typeof input.handoffMode === 'string') return input.handoffMode === '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; + return hasAcceptedValue(input, EVIDENCE_FIELDS.buttonPathMode, 'button_path'); } function evaluateGate(input) { @@ -103,21 +157,21 @@ function evaluateGate(input) { if (wantsSilentContinuation(input) && !hasExternalizedCheckpointPath(input)) { failed = true; 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'); } if (claimsExecution(input) && !hasConcreteNextAction(input)) { failed = true; reasons.push('claimed execution requires evidence of a concrete next action'); - requiredEvidence.push('nextStep'); + requiredEvidence.push(describeRequirement(GATE_REQUIREMENTS.concreteNextAction)); allowedResponseModes.push('checkpoint_only'); } if (needsOwnerDecision(input) && !usesButtonPath(input)) { failed = true; 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'); }