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