refactor: align long-task gate lock evidence aliases
This commit is contained in:
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user