Files
wehub-resource-sync 23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:02:19 +08:00

301 lines
11 KiB
JavaScript

/**
* Witness library — shared logic for regenerating, verifying, and
* querying cryptographically-signed fix manifests with temporal
* history (ADR-103).
*
* Pure functions + small I/O helpers; no project-specific assumptions.
* CLI wrappers in this directory configure paths and surface output.
*
* @module plugins/ruflo-core/scripts/witness/lib
*/
import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'node:fs';
import { join } from 'node:path';
import { execSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { createRequire } from 'node:module';
import { platform } from 'node:os';
/**
* Map Node's `process.platform` value (linux/darwin/win32) to the
* directory name used under `verification/`. Lets the caller's path
* default to the right OS subfolder without hardcoding.
*/
export function osDir(p = platform()) {
if (p === 'darwin') return 'macos';
if (p === 'win32') return 'windows';
return 'linux'; // linux + anything unknown
}
/**
* Try to load the binary RVF backend (@ruvector/rvf-node) from the
* caller's node_modules. Returns the RvfDatabase class or null if the
* package isn't installed — callers fall back to JSONL.
*/
function loadRvfNode(probeRoots) {
const expanded = [
...probeRoots,
...probeRoots.flatMap(r => [
join(r, 'v3/@claude-flow/cli'),
join(r, 'v3/@claude-flow/memory'),
]),
];
for (const root of expanded) {
try {
const req = createRequire(join(root, 'noop.js'));
const mod = req('@ruvector/rvf-node');
if (mod && mod.RvfDatabase) return mod.RvfDatabase;
} catch { /* try next */ }
}
return null;
}
// ─── ed25519 lazy load ─────────────────────────────────────────────
// Locate @noble/ed25519 from the caller's project (it lives in their
// node_modules, not bundled with this script). We probe a few likely
// install roots so the toolkit works in ruflo's monorepo layout *and*
// a user's flat node_modules.
function loadEd25519(probeRoots) {
// Expand caller-supplied roots with workspace-package locations so
// pnpm's isolated layout (where transitive deps don't hoist to the
// workspace root) still resolves @noble/ed25519. Callers don't need
// to know about this — the function just probes more places.
const expanded = [
...probeRoots,
...probeRoots.flatMap(r => [
join(r, 'v3/@claude-flow/cli'),
join(r, 'v3/@claude-flow/plugin-agent-federation'),
]),
];
let lastErr;
for (const root of expanded) {
try {
const req = createRequire(join(root, 'noop.js'));
const ed = req('@noble/ed25519');
ed.etc.sha512Sync = (...m) => {
const h = createHash('sha512');
for (const x of m) h.update(x);
return h.digest();
};
return ed;
} catch (e) { lastErr = e; }
}
throw new Error(
"Could not locate '@noble/ed25519'. Install it in your project " +
"(npm i @noble/ed25519) or pass a node_modules root via the " +
"WITNESS_ED25519_ROOT env var. Probed: " + expanded.join(', ') +
". Last error: " + (lastErr?.message ?? '?')
);
}
// ─── manifest helpers ─────────────────────────────────────────────
export function fileSha256(absPath) {
return createHash('sha256').update(readFileSync(absPath)).digest('hex');
}
export function fileContains(absPath, marker) {
return readFileSync(absPath, 'utf8').includes(marker);
}
export function refreshFix(repoRoot, fix) {
const abs = join(repoRoot, fix.file);
if (!existsSync(abs)) {
return { ...fix, sha256: fix.sha256 ?? '', markerVerified: false, _missing: true };
}
const sha256 = fileSha256(abs);
const markerVerified = fileContains(abs, fix.marker);
return { id: fix.id, desc: fix.desc, file: fix.file, sha256, marker: fix.marker, markerVerified };
}
// ─── regenerate ───────────────────────────────────────────────────
/**
* Regenerate a signed witness manifest.
*
* @param {object} opts
* @param {string} opts.repoRoot Absolute path to the project root.
* @param {string} opts.manifestPath Where to write verification.md.json.
* @param {Array} opts.newFixes New fix entries to register.
* @param {object} [opts.releases] Map of pkg → version for the manifest.
* @param {string[]} [opts.ed25519Roots] Probe roots for @noble/ed25519.
* @returns {{witness: object, summary: string}}
*/
export function regenerate(opts) {
const { repoRoot, manifestPath, newFixes = [], releases = {}, ed25519Roots = [] } = opts;
const ed = loadEd25519(ed25519Roots.length ? ed25519Roots : [repoRoot, join(repoRoot, 'v3')]);
const existing = existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, 'utf8')) : null;
const oldFixes = existing?.manifest?.fixes ?? [];
const oldIds = new Set(oldFixes.map(f => f.id));
const merged = [
...oldFixes.map(f => refreshFix(repoRoot, f)),
...newFixes.filter(f => !oldIds.has(f.id)).map(f => refreshFix(repoRoot, f)),
];
const gitCommit = execSync('git rev-parse HEAD', { cwd: repoRoot }).toString().trim();
const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: repoRoot }).toString().trim();
const issuedAt = new Date().toISOString();
const verifiedCount = merged.filter(f => f.markerVerified).length;
const missingCount = merged.filter(f => f._missing).length;
const manifest = {
schema: existing?.manifest?.schema ?? 'ruflo-witness/v1',
issuedAt,
gitCommit,
branch,
os: osDir(),
releases,
summary: { totalFixes: merged.length, verified: verifiedCount, missing: missingCount },
fixes: merged.map(f => { const { _missing, ...c } = f; return c; }),
};
const manifestCanonical = JSON.stringify(manifest);
const manifestHash = createHash('sha256').update(manifestCanonical).digest('hex');
const seed = createHash('sha256').update(gitCommit + ':ruflo-witness/v1').digest();
const publicKey = ed.getPublicKey(seed);
const signature = ed.sign(Buffer.from(manifestHash, 'hex'), seed);
const witness = {
manifest,
integrity: {
manifestHashAlgo: 'sha256',
manifestHash,
signatureAlgo: 'ed25519',
publicKey: Buffer.from(publicKey).toString('hex'),
signature: Buffer.from(signature).toString('hex'),
seedDerivation: "sha256(gitCommit + ':ruflo-witness/v1')",
},
};
const newIds = newFixes.filter(f => !oldIds.has(f.id)).map(f => f.id);
const summary =
`gitCommit: ${gitCommit.slice(0,12)}…\n` +
`branch: ${branch}\n` +
`issuedAt: ${issuedAt}\n` +
`total fixes: ${merged.length} (was ${oldFixes.length})\n` +
`verified: ${verifiedCount}\n` +
`missing: ${missingCount}\n` +
`new entries: ${newIds.join(', ') || '(none)'}\n` +
`releases: ${JSON.stringify(releases)}`;
return { witness, summary, manifest, gitCommit, manifestHash };
}
// ─── temporal history ─────────────────────────────────────────────
/**
* Append a compact snapshot of `manifest` to a JSONL history file
* (one entry per line — git-diff-friendly, append-cheap).
*
* The history lives inside an RVF-style cognitive container layout
* (`verification/<os>/history.jsonl`). RVF's binary format optimises
* for vector similarity search; the witness use case is structured
* key-value retrieval (snapshot N's commit + fixes), so JSONL is the
* better format inside the container. The container concept — per-OS
* folder bundling manifest + history + metadata — is what we adopt
* from RVF, not the file format itself.
*
* @param {string} historyPath Path to history.jsonl.
* @param {object} manifest The manifest object just signed.
* @param {string} manifestHash The signed hash (for fast lookup).
*/
export function appendHistory(historyPath, manifest, manifestHash) {
const fixesIndex = {};
for (const f of manifest.fixes) {
fixesIndex[f.id] = { sha256: f.sha256, markerVerified: f.markerVerified };
}
const entry = {
v: 1,
commit: manifest.gitCommit,
issuedAt: manifest.issuedAt,
branch: manifest.branch,
os: manifest.os,
manifestHash,
summary: manifest.summary,
fixes: fixesIndex,
};
appendFileSync(historyPath, JSON.stringify(entry) + '\n');
return entry;
}
/**
* Load the JSONL history into memory (chronological order).
*/
export function loadHistory(historyPath) {
if (!existsSync(historyPath)) return [];
const lines = readFileSync(historyPath, 'utf8').split('\n').filter(Boolean);
return lines.map(l => JSON.parse(l));
}
/**
* For each fix that is currently `regressed` (markerVerified=false in the
* latest entry), walk backwards to find the most recent entry where it
* was `pass` (markerVerified=true). The next entry after that is where
* the regression was introduced.
*
* @returns {Array<{id, lastPassCommit, regressedAtCommit, atIssuedAt}>}
*/
export function findRegressionIntroductions(history) {
if (history.length === 0) return [];
const latest = history[history.length - 1];
const out = [];
for (const [id, state] of Object.entries(latest.fixes)) {
if (state.markerVerified) continue;
let lastPass = null;
let regressedAt = latest;
for (let i = history.length - 2; i >= 0; i--) {
const e = history[i];
const s = e.fixes[id];
if (s && s.markerVerified) { lastPass = e; break; }
regressedAt = e;
}
out.push({
id,
lastPassCommit: lastPass?.commit ?? null,
lastPassIssuedAt: lastPass?.issuedAt ?? null,
regressedAtCommit: regressedAt.commit,
regressedAtIssuedAt: regressedAt.issuedAt,
});
}
return out;
}
/**
* Build a status timeline for a single fix across all history entries.
*
* @returns {Array<{commit, issuedAt, status}>}
* status ∈ { 'pass', 'regressed', 'absent' }
*/
export function fixTimeline(history, fixId) {
return history.map(e => ({
commit: e.commit,
issuedAt: e.issuedAt,
status: e.fixes[fixId]
? (e.fixes[fixId].markerVerified ? 'pass' : 'regressed')
: 'absent',
}));
}
/**
* Compare the latest entry to the previous entry and report transitions.
*
* @returns {{newlyRegressed: string[], newlyPassing: string[], added: string[], removed: string[]}}
*/
export function diffLatest(history) {
if (history.length < 2) return { newlyRegressed: [], newlyPassing: [], added: [], removed: [] };
const [prev, curr] = [history[history.length - 2], history[history.length - 1]];
const newlyRegressed = [];
const newlyPassing = [];
const added = [];
const removed = [];
for (const id of Object.keys(curr.fixes)) {
if (!(id in prev.fixes)) added.push(id);
else if (prev.fixes[id].markerVerified && !curr.fixes[id].markerVerified) newlyRegressed.push(id);
else if (!prev.fixes[id].markerVerified && curr.fixes[id].markerVerified) newlyPassing.push(id);
}
for (const id of Object.keys(prev.fixes)) {
if (!(id in curr.fixes)) removed.push(id);
}
return { newlyRegressed, newlyPassing, added, removed };
}