@maintainabilityai/research-runner 0.1.23 → 0.1.25
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.
- package/dist/runner/skills.js +68 -0
- package/package.json +1 -1
package/dist/runner/skills.js
CHANGED
|
@@ -793,6 +793,73 @@ const handleAuditEmitEvent = async (input) => {
|
|
|
793
793
|
return { ok: false, reason: 'audit-write-failed-after-retries' };
|
|
794
794
|
};
|
|
795
795
|
// ─────────────────────────────────────────────────────────────────────
|
|
796
|
+
// Audit verify-chain — CI defense against forged audit logs
|
|
797
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
798
|
+
const AuditVerifyInput = zod_1.z.object({
|
|
799
|
+
okrId: zod_1.z.string().min(1),
|
|
800
|
+
runId: zod_1.z.string().min(1),
|
|
801
|
+
});
|
|
802
|
+
/**
|
|
803
|
+
* `audit-verify-chain` — replay the hash chain over an existing audit
|
|
804
|
+
* JSONL, returning `{ok: true, chainHead, eventCount}` if the chain is
|
|
805
|
+
* intact or `{ok: false, reason}` on the first integrity failure.
|
|
806
|
+
*
|
|
807
|
+
* Why this skill exists: an agent that loses access to the runner could
|
|
808
|
+
* (and on PR #105 did) self-write the JSONL with fabricated hashes. The
|
|
809
|
+
* audit-and-drift workflow calls this skill after each run; verdict
|
|
810
|
+
* fails + `chain-forgery-detected` label is applied on `ok:false`. The
|
|
811
|
+
* verification rules are identical to `verifyChain()` in audit-emitter.ts:
|
|
812
|
+
* - first event prev_event_hash === null
|
|
813
|
+
* - each prev_event_hash === preceding event.event_hash
|
|
814
|
+
* - each event_hash === sha256(canonicalStringify(event-with-empty-hash))
|
|
815
|
+
* - event_id is monotonic from 1
|
|
816
|
+
*/
|
|
817
|
+
const handleAuditVerifyChain = async (input) => {
|
|
818
|
+
const parsed = AuditVerifyInput.safeParse(input);
|
|
819
|
+
if (!parsed.success) {
|
|
820
|
+
return { ok: false, reason: `bad-input: ${parsed.error.message}` };
|
|
821
|
+
}
|
|
822
|
+
const { okrId, runId } = parsed.data;
|
|
823
|
+
const filePath = path.join(meshPath(), 'okrs', okrId, 'audit', 'events', `${runId}.jsonl`);
|
|
824
|
+
if (!fs.existsSync(filePath)) {
|
|
825
|
+
return { ok: false, reason: `audit-jsonl-missing: ${filePath}` };
|
|
826
|
+
}
|
|
827
|
+
let lines;
|
|
828
|
+
try {
|
|
829
|
+
lines = fs.readFileSync(filePath, 'utf8').split('\n').filter(l => l.trim().length > 0);
|
|
830
|
+
}
|
|
831
|
+
catch (err) {
|
|
832
|
+
return { ok: false, reason: `read-failed: ${err.message}` };
|
|
833
|
+
}
|
|
834
|
+
let prev = null;
|
|
835
|
+
for (let i = 0; i < lines.length; i++) {
|
|
836
|
+
let event;
|
|
837
|
+
try {
|
|
838
|
+
event = JSON.parse(lines[i]);
|
|
839
|
+
}
|
|
840
|
+
catch (err) {
|
|
841
|
+
return { ok: false, reason: `bad-jsonl-line-${i + 1}: ${err.message}` };
|
|
842
|
+
}
|
|
843
|
+
if (event.event_id !== i + 1) {
|
|
844
|
+
return { ok: false, reason: `event-id-mismatch-line-${i + 1}: expected ${i + 1} got ${event.event_id}` };
|
|
845
|
+
}
|
|
846
|
+
if (event.prev_event_hash !== prev) {
|
|
847
|
+
return { ok: false, reason: `prev-hash-mismatch-line-${i + 1}: expected ${prev ?? 'null'} got ${event.prev_event_hash ?? 'null'}` };
|
|
848
|
+
}
|
|
849
|
+
const recordedHash = event.event_hash;
|
|
850
|
+
if (typeof recordedHash !== 'string') {
|
|
851
|
+
return { ok: false, reason: `missing-event-hash-line-${i + 1}` };
|
|
852
|
+
}
|
|
853
|
+
const draft = { ...event, event_hash: '' };
|
|
854
|
+
const recomputed = sha256(canonicalStringify(draft));
|
|
855
|
+
if (recordedHash !== recomputed) {
|
|
856
|
+
return { ok: false, reason: `forged-hash-line-${i + 1}: recorded=${recordedHash.slice(0, 16)}… recomputed=${recomputed.slice(0, 16)}…` };
|
|
857
|
+
}
|
|
858
|
+
prev = recordedHash;
|
|
859
|
+
}
|
|
860
|
+
return { ok: true, chainHead: prev, eventCount: lines.length };
|
|
861
|
+
};
|
|
862
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
796
863
|
// Registry + dispatcher
|
|
797
864
|
// ─────────────────────────────────────────────────────────────────────
|
|
798
865
|
exports.SKILLS = {
|
|
@@ -809,6 +876,7 @@ exports.SKILLS = {
|
|
|
809
876
|
'dedupe-and-rank': handleDedupeAndRank,
|
|
810
877
|
'format-research-issue-update': handleFormatResearchIssueUpdate,
|
|
811
878
|
'audit-emit-event': handleAuditEmitEvent,
|
|
879
|
+
'audit-verify-chain': handleAuditVerifyChain,
|
|
812
880
|
};
|
|
813
881
|
function isSkillName(name) {
|
|
814
882
|
return Object.prototype.hasOwnProperty.call(exports.SKILLS, name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maintainabilityai/research-runner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
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",
|