mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-16 13:15:36 +02:00
fix(story-lifecycle): reject unsafe numeric allocation IDs
A real CLI fixture reproduced an infinite allocation loop from an existing unsafe numeric ID. Reject unsafe IDs and overflow without writing markers; 26 workflow fixtures, changed-code coverage and validators pass.
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
"id": "hand-seeded",
|
||||
"source": "manual-curation-pending-regeneration"
|
||||
},
|
||||
"registryDigest": "abd597fc032cfab681b4083339491070034f86c947503194499b82771daada8a",
|
||||
"registryDigest": "6a6a4fd5cc80ecda18184f6b35625c99ccc1bb0036c5e30316fa179ae81e451f",
|
||||
"schemaVersion": 1,
|
||||
"triggers": {
|
||||
"skill:agent-runtime-gateway-smoke-test": [
|
||||
|
||||
@@ -232,10 +232,15 @@ function allocate(root, dir, epic, count) {
|
||||
fs.mkdirSync(reg, { recursive: true });
|
||||
const taken = [...fs.readdirSync(dir), ...fs.readdirSync(reg).map((f) => `${f}.md`)]
|
||||
.map((f) => f.match(STORY_FILE)).filter((m) => m && m[1] === epic).map((m) => Number(m[2]));
|
||||
if (taken.some((n) => !Number.isSafeInteger(n))) {
|
||||
throw new LedgerError('existing story ID is not a safe integer');
|
||||
}
|
||||
let n = Math.max(0, ...taken);
|
||||
const ids = [];
|
||||
while (ids.length < count) {
|
||||
const id = `${epic}-${++n}`;
|
||||
n += 1;
|
||||
if (!Number.isSafeInteger(n)) throw new LedgerError('story ID exceeds safe integer range');
|
||||
const id = `${epic}-${n}`;
|
||||
try {
|
||||
fs.writeFileSync(path.join(reg, id), '', { flag: 'wx' });
|
||||
ids.push(id);
|
||||
|
||||
@@ -380,6 +380,22 @@ fs.readFileSync = function (p, ...a) { const r = orig.call(fs, p, ...a);
|
||||
}
|
||||
}],
|
||||
|
||||
['allocation rejects unsafe existing numeric IDs instead of looping forever', () => {
|
||||
const root = newRepo();
|
||||
const registry = path.join(root, '.git', 'story-lifecycle', 'ids');
|
||||
fs.mkdirSync(registry, { recursive: true });
|
||||
for (const n of ['9007199254740992', '9007199254740991']) {
|
||||
const marker = path.join(registry, `auth-flow-${n}`);
|
||||
fs.writeFileSync(marker, '');
|
||||
const result = spawnSync(process.execPath, [LEDGER, 'allocate', '--root', root,
|
||||
'--epic', 'auth-flow', '--count', '2'], { encoding: 'utf8', timeout: 2000 });
|
||||
assert.strictEqual(result.status, 2, result.stderr || result.error?.message);
|
||||
assert.match(result.stderr, /safe integer/);
|
||||
assert.deepStrictEqual(fs.readdirSync(registry), [`auth-flow-${n}`]);
|
||||
fs.rmSync(marker);
|
||||
}
|
||||
}],
|
||||
|
||||
['ledger paths are confined to .stories/ file shapes', () => {
|
||||
const dir = path.join(os.tmpdir(), '.stories');
|
||||
for (const bad of ['../x-1.md', 'epics/../../x.md', 'Auth-1.md', 'a/b-1.md', 'epics/a.txt', 'sprints/sprint-0.md', '/abs-1.md']) {
|
||||
|
||||
Reference in New Issue
Block a user