@maintainabilityai/research-runner 0.1.46 → 0.1.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1338,15 +1338,27 @@ const handleKnowledgeCode = async (input) => {
1338
1338
  // Workflow gate consumes this to validate cited paths.
1339
1339
  inventory_paths: inventoryPaths,
1340
1340
  };
1341
- // Bug-R / R6 (Codex round-3) — persist inventory to the clone
1342
- // cache so knowledge-code-read can strict-mode validate requested
1343
- // paths against the same list that lands in the audit chain.
1344
- // Without this, the agent could ask knowledge-code-read for
1345
- // arbitrary paths inside the clone that the chain never advertised.
1341
+ // Bug-R / R6 + Bug-S / S6 (Codex round-4 hardening) — persist
1342
+ // inventory to the clone cache so knowledge-code-read can strict-
1343
+ // mode validate requested paths against the same list that lands
1344
+ // in the audit chain. Round-3 caught the inventory-persist write
1345
+ // being non-fatal; round-4 flagged that as fail-open without
1346
+ // a written inventory, knowledge-code-read's path-not-in-inventory
1347
+ // check silently falls through (the file just isn't there to
1348
+ // check against). S6 fix: persist failure is now FATAL. The
1349
+ // brownfield grounding contract requires the inventory to exist;
1350
+ // a skill_call that succeeded without writing it is a coverage lie.
1346
1351
  try {
1347
1352
  fs.writeFileSync(path.join(cloneTarget, '.knowledge-code-inventory.json'), JSON.stringify({ inventory_paths: inventoryPaths, sha, cachedAt: new Date().toISOString() }), 'utf8');
1348
1353
  }
1349
- catch { /* inventory persist failure is non-fatal — read skill will fall back to cache-only check */ }
1354
+ catch (err) {
1355
+ return {
1356
+ ok: false,
1357
+ reason: `inventory-persist-failed: ${err instanceof Error ? err.message : String(err)}`,
1358
+ repo: repoSlug,
1359
+ remediation: "knowledge-code MUST persist its file inventory so knowledge-code-read can strict-mode validate file reads against it. Check tmpdir permissions and disk space.",
1360
+ };
1361
+ }
1350
1362
  return {
1351
1363
  ok: true,
1352
1364
  mode: 'brownfield',
@@ -1445,27 +1457,47 @@ const handleKnowledgeCodeRead = async (input) => {
1445
1457
  remediation: `Could not access clone for ${repoUrl}. Underlying error: ${cloneResult.error ?? 'unknown'}`,
1446
1458
  };
1447
1459
  }
1448
- // Bug-R / R6 (strict mode part 2) — validate the requested path
1449
- // against the inventory persisted by knowledge-code. Only paths
1450
- // that knowledge-code already advertised in `inventory_paths` are
1451
- // readable closes the gap where the agent could ask for any
1452
- // file inside the clone, including files not visible in the
1453
- // bounded walk. Test mode bypasses (see allowUncached).
1460
+ // Bug-R / R6 + Bug-S / S6 (Codex round-4 hardening) — validate
1461
+ // the requested path against the inventory persisted by knowledge-
1462
+ // code. Only paths knowledge-code advertised in `inventory_paths`
1463
+ // are readable. Round-3 left missing/malformed inventory as a
1464
+ // fall-through (silent pass); round-4 caught that as fail-open.
1465
+ // S6: missing inventory file or malformed JSON is now a HARD
1466
+ // FAILURE in non-test mode. The cache exists (we passed the
1467
+ // earlier no-cache check), so the inventory file MUST exist —
1468
+ // its absence means knowledge-code didn't complete normally.
1454
1469
  if (!allowUncached) {
1455
1470
  const inventoryPath = path.join(cloneResult.path, '.knowledge-code-inventory.json');
1456
- if (fs.existsSync(inventoryPath)) {
1457
- try {
1458
- const inv = JSON.parse(fs.readFileSync(inventoryPath, 'utf8'));
1459
- const allowed = new Set(inv.inventory_paths ?? []);
1460
- if (allowed.size > 0 && !allowed.has(normalized)) {
1461
- return {
1462
- ok: false,
1463
- reason: `path-not-in-inventory: ${normalized} is not in the knowledge-code inventory_paths for ${gh.owner}/${gh.name}. The agent can only read files knowledge-code advertised in the chain.`,
1464
- remediation: "If the file is real but missed by the bounded walk (default maxFiles=200), call knowledge-code with a higher maxFiles before retrying.",
1465
- };
1466
- }
1467
- }
1468
- catch { /* malformed inventory; fall through (cache-only check still applied) */ }
1471
+ if (!fs.existsSync(inventoryPath)) {
1472
+ return {
1473
+ ok: false,
1474
+ reason: `inventory-missing: ${gh.owner}/${gh.name}'s clone exists but .knowledge-code-inventory.json is absent. knowledge-code did not complete normally — re-run it before invoking knowledge-code-read.`,
1475
+ };
1476
+ }
1477
+ let inv;
1478
+ try {
1479
+ inv = JSON.parse(fs.readFileSync(inventoryPath, 'utf8'));
1480
+ }
1481
+ catch (err) {
1482
+ return {
1483
+ ok: false,
1484
+ reason: `inventory-malformed: .knowledge-code-inventory.json could not be parsed: ${err instanceof Error ? err.message : String(err)}`,
1485
+ remediation: "Re-run knowledge-code to regenerate the inventory.",
1486
+ };
1487
+ }
1488
+ const allowed = new Set(inv.inventory_paths ?? []);
1489
+ if (allowed.size === 0) {
1490
+ return {
1491
+ ok: false,
1492
+ reason: `inventory-empty: .knowledge-code-inventory.json carries no inventory_paths. knowledge-code likely walked zero files (empty repo? maxFiles=0?). Cannot validate file reads.`,
1493
+ };
1494
+ }
1495
+ if (!allowed.has(normalized)) {
1496
+ return {
1497
+ ok: false,
1498
+ reason: `path-not-in-inventory: ${normalized} is not in the knowledge-code inventory_paths for ${gh.owner}/${gh.name}. The agent can only read files knowledge-code advertised in the chain.`,
1499
+ remediation: "If the file is real but missed by the bounded walk (default maxFiles=200), call knowledge-code with a higher maxFiles before retrying.",
1500
+ };
1469
1501
  }
1470
1502
  }
1471
1503
  const absPath = path.join(cloneResult.path, normalized);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maintainabilityai/research-runner",
3
- "version": "0.1.46",
3
+ "version": "0.1.47",
4
4
  "description": "Research + PRD agent runner — orchestrates the Archeologist and PRD pipelines for the MaintainabilityAI governance mesh",
5
5
  "license": "MIT",
6
6
  "author": "MaintainabilityAI",