Merge pull request #3583 from Tivonsico/fix/skill-run-timestamp-type-20261014

fix(skill-tracker): reject non-string execution timestamps
This commit is contained in:
haelyra authored and GitHub committed 2026-10-25 19:16:34 -04:00
commit f390400944
2 files changed
+92 -1

No files matched your search

+1 -1
View File
@@ -79,7 +79,7 @@ function normalizeExecutionRecord(input, options = {}) {
throw new Error('failure_reason must be a string or null');
}
if (Number.isNaN(Date.parse(recordedAt))) {
if (typeof recordedAt !== 'string' || Number.isNaN(Date.parse(recordedAt))) {
throw new Error('recorded_at must be an ISO timestamp');
}
@@ -0,0 +1,91 @@
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const tracker = require('../../scripts/lib/skill-evolution/tracker');
const execution = {
skill_id: 'demo',
skill_version: 'v1',
task_description: 'Run a temporary task',
outcome: 'success',
};
const timestamp = '2026-03-15T12:32:00.000Z';
const invalidTimestamps = [2026, [timestamp], new Date(timestamp), {}];
function withRunsFile(fn) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-tracker-timestamps-'));
const runsFilePath = path.join(root, 'runs.jsonl');
try {
fn({ runsFilePath });
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
}
test('rejects non-string execution timestamps before creating storage', () => {
withRunsFile(options => {
for (const recorded_at of invalidTimestamps) {
assert.throws(
() => tracker.recordSkillExecution({ ...execution, recorded_at }, options),
/recorded_at must be an ISO timestamp/
);
assert.equal(fs.existsSync(options.runsFilePath), false);
}
});
});
test('rejects invalid clock overrides without changing existing records', () => {
withRunsFile(options => {
tracker.recordSkillExecution({ ...execution, recorded_at: timestamp }, options);
const previousBytes = fs.readFileSync(options.runsFilePath, 'utf8');
for (const now of invalidTimestamps) {
assert.throws(
() => tracker.recordSkillExecution(execution, { ...options, now }),
/recorded_at must be an ISO timestamp/
);
assert.equal(fs.readFileSync(options.runsFilePath, 'utf8'), previousBytes);
}
assert.equal(tracker.readSkillExecutionRecords(options).length, 1);
});
});
test('retains explicit and clock-supplied string timestamps on readback', () => {
withRunsFile(options => {
const offsetTimestamp = '2026-03-15T14:32:00+02:00';
tracker.recordSkillExecution({ ...execution, recorded_at: offsetTimestamp }, { ...options, now: 2026 });
tracker.recordSkillExecution(execution, { ...options, now: timestamp });
assert.deepEqual(
tracker.readSkillExecutionRecords(options).map(record => record.recorded_at),
[offsetTimestamp, timestamp]
);
tracker.recordSkillExecution(execution, options);
const records = tracker.readSkillExecutionRecords(options);
assert.equal(records.length, 3);
assert.equal(typeof records[2].recorded_at, 'string');
assert.ok(Number.isFinite(Date.parse(records[2].recorded_at)));
});
});
test('rejects invalid timestamps before submitting to a state store', () => {
withRunsFile(options => {
let writes = 0;
const stateStore = {
recordSkillExecution() {
writes += 1;
return {};
},
};
for (const recorded_at of invalidTimestamps) {
assert.throws(
() => tracker.recordSkillExecution({ ...execution, recorded_at }, { ...options, stateStore }),
/recorded_at must be an ISO timestamp/
);
assert.equal(writes, 0);
assert.equal(fs.existsSync(options.runsFilePath), false);
}
});
});