From 5e3863765b3a731c27c24dc21b6e3ea7084ace72 Mon Sep 17 00:00:00 2001 From: jienigoto Date: Mon, 22 Jun 2026 17:02:27 +0800 Subject: [PATCH 1/3] feat(skills): add verifiable web research runner --- skills/verifiable-web-research/SKILL.md | 92 ++++++++ skills/verifiable-web-research/X.yaml | 79 +++++++ .../fixtures/ai-agent-frameworks.json | 30 +++ skills/verifiable-web-research/run.mjs | 207 ++++++++++++++++++ .../schemas/result.schema.json | 22 ++ skills/verifiable-web-research/test.mjs | 73 ++++++ 6 files changed, 503 insertions(+) create mode 100644 skills/verifiable-web-research/SKILL.md create mode 100644 skills/verifiable-web-research/X.yaml create mode 100644 skills/verifiable-web-research/fixtures/ai-agent-frameworks.json create mode 100644 skills/verifiable-web-research/run.mjs create mode 100644 skills/verifiable-web-research/schemas/result.schema.json create mode 100644 skills/verifiable-web-research/test.mjs diff --git a/skills/verifiable-web-research/SKILL.md b/skills/verifiable-web-research/SKILL.md new file mode 100644 index 00000000..ace90118 --- /dev/null +++ b/skills/verifiable-web-research/SKILL.md @@ -0,0 +1,92 @@ +--- +name: verifiable-web-research +description: Build an auditable web research packet where each claim is tied to source metadata, exact extracts, and replay guidance. +runx: + category: research +--- + +# Verifiable Web Research + +Create a bounded research packet from web-source evidence with enough +provenance for an independent reviewer to replay the work. The skill turns a +research objective plus fetched source snapshots into claims, direct extracts, +content digests, and verification steps. + +Use this when a downstream decision depends on web claims that must be checked +later. Use `web-fetch` when the caller already knows a single URL. Use +`research` when provenance is useful but digest-level replay is not required. + +## Inputs + +- `objective` (required): the question the evidence packet must answer. +- `source_fixture_path` (required): package-relative JSON fixture containing + source snapshots. +- `verification_level` (optional): `basic`, `detailed`, or `audit_ready`. + Defaults to `detailed`. +- `max_claims` (optional): maximum number of evidence claims to include. +- `output_dir` (optional): package-relative directory for `evidence.json` and + `report.md`. + +## Source fixture contract + +The fixture is a JSON object: + +```json +{ + "sources": [ + { + "url": "https://example.com", + "final_url": "https://example.com", + "fetched_at": "2026-06-22T00:00:00Z", + "status": 200, + "content": "Source text", + "extracts": [ + { "claim": "Claim text", "quote": "Exact supporting quote" } + ] + } + ] +} +``` + +The runner never reaches the network. It operates on already-fetched public +snapshots so verification can be deterministic and safe in harness runs. + +## Procedure + +1. Validate the objective, fixture path, and verification level. +2. Load the fixture from inside the skill directory. +3. For each source, compute `sha256` over the captured content. +4. Convert each extract into a claim record with source URL, final URL, + accessed timestamp, digest, exact quote, and confidence. +5. Produce a verification guide explaining how to re-fetch the URLs and compare + fresh content against the stored extracts and digests. +6. Write optional artifacts and print the packet as JSON. + +## Output + +The runner emits `verifiable_research_packet`: + +```yaml +schema: runx.verifiable_web_research.result.v1 +data: + objective: string + verification_level: basic | detailed | audit_ready + summary: string + claims: + - claim: string + source_url: string + final_url: string + accessed_at: string + content_digest: string + extract: string + confidence: verified | likely | uncertain + confidence_reasoning: string + verification_guide: + overview: string + steps: array + evidence_archive: + sources: array +``` + +`basic` omits the evidence archive. `detailed` includes source digests and +claims. `audit_ready` includes replay commands and every source record. diff --git a/skills/verifiable-web-research/X.yaml b/skills/verifiable-web-research/X.yaml new file mode 100644 index 00000000..96df4156 --- /dev/null +++ b/skills/verifiable-web-research/X.yaml @@ -0,0 +1,79 @@ +skill: verifiable-web-research +version: "0.1.0" + +catalog: + kind: skill + audience: public + visibility: public + role: canonical + +runx: + mutating: false + idempotency: + key: source_fixture_sha256 + scopes: + - research.evidence.read + policy: + data_classification: public_web_snapshot_metadata + network: + allowed: [] + forbidden: + - live network fetches during deterministic harness runs + - private or authenticated sources + verifier_notes: + - The runner uses already-fetched public source snapshots. + - Each claim includes an exact extract and a sha256 digest of the captured source content. + artifacts: + emits: + - verifiable_research_packet + - evidence_json + - report_md + wrap_as: verifiable_research_packet + +runners: + default: + default: true + type: cli-tool + command: /usr/bin/env + args: + - node + - run.mjs + scopes: + - research.evidence.read + policy: + reads: + - package-local public source fixtures + writes: + - evidence.json + - report.md + disallows: + - network calls + - credential material + - private source snapshots + inputs: + objective: + type: string + required: true + description: Research question to answer from captured web evidence. + source_fixture_path: + type: string + required: true + description: Package-relative JSON fixture with captured source snapshots. + verification_level: + type: string + required: false + default: detailed + description: basic, detailed, or audit_ready. + max_claims: + type: number + required: false + default: 10 + description: Maximum number of evidence claims to include. + output_dir: + type: string + required: false + description: Directory inside the skill directory where artifacts should be written. + outputs: + verifiable_research_packet: object + evidence_json: object + report_md: string diff --git a/skills/verifiable-web-research/fixtures/ai-agent-frameworks.json b/skills/verifiable-web-research/fixtures/ai-agent-frameworks.json new file mode 100644 index 00000000..7064fc1c --- /dev/null +++ b/skills/verifiable-web-research/fixtures/ai-agent-frameworks.json @@ -0,0 +1,30 @@ +{ + "sources": [ + { + "url": "https://example.org/langchain-pricing", + "final_url": "https://example.org/langchain-pricing", + "fetched_at": "2026-06-22T00:00:00Z", + "status": 200, + "content": "LangChain is open source. LangSmith is an optional hosted observability product with a free developer tier.", + "extracts": [ + { + "claim": "LangChain has an open-source framework and an optional hosted observability product.", + "quote": "LangChain is open source. LangSmith is an optional hosted observability product with a free developer tier." + } + ] + }, + { + "url": "https://example.org/crewai-docs", + "final_url": "https://example.org/crewai-docs", + "fetched_at": "2026-06-22T00:00:05Z", + "status": 200, + "content": "CrewAI provides a multi-agent framework with hosted enterprise options for teams.", + "extracts": [ + { + "claim": "CrewAI offers a multi-agent framework with hosted enterprise options.", + "quote": "CrewAI provides a multi-agent framework with hosted enterprise options for teams." + } + ] + } + ] +} diff --git a/skills/verifiable-web-research/run.mjs b/skills/verifiable-web-research/run.mjs new file mode 100644 index 00000000..f0cb795e --- /dev/null +++ b/skills/verifiable-web-research/run.mjs @@ -0,0 +1,207 @@ +import crypto from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; + +const SCHEMA = "runx.verifiable_web_research.result.v1"; +const LEVELS = new Set(["basic", "detailed", "audit_ready"]); + +const inputs = readInputs(); +const skillRoot = process.cwd(); +const objective = stringValue(inputs.objective); +const fixturePath = stringValue(inputs.source_fixture_path); +const verificationLevel = stringValue(inputs.verification_level) || "detailed"; +const maxClaims = Number.isFinite(inputs.max_claims) ? Math.max(1, Math.trunc(inputs.max_claims)) : 10; + +if (!objective) throw new Error("objective is required"); +if (!fixturePath) throw new Error("source_fixture_path is required"); +if (!LEVELS.has(verificationLevel)) throw new Error("verification_level must be basic, detailed, or audit_ready"); + +const fixture = readFixture(fixturePath, skillRoot); +const packet = buildPacket({ objective, fixture, verificationLevel, maxClaims }); +const report = renderReport(packet); + +writeArtifacts(inputs.output_dir, packet, report, skillRoot); + +process.stdout.write(`${JSON.stringify(packet, null, 2)}\n`); + +function readInputs() { + const raw = process.env.RUNX_INPUTS_PATH + ? fs.readFileSync(process.env.RUNX_INPUTS_PATH, "utf8") + : process.env.RUNX_INPUTS_JSON || "{}"; + return JSON.parse(raw); +} + +function readFixture(relativePath, root) { + const resolved = path.resolve(root, relativePath); + ensureInside(root, resolved, "source_fixture_path"); + const text = fs.readFileSync(resolved, "utf8"); + const parsed = JSON.parse(text); + if (!Array.isArray(parsed.sources) || parsed.sources.length === 0) { + throw new Error("fixture must contain a non-empty sources array"); + } + return { ref: relativePath, text, sources: parsed.sources }; +} + +function buildPacket({ objective, fixture, verificationLevel, maxClaims }) { + const sourceRecords = fixture.sources.map((source) => normalizeSource(source)); + const claims = []; + + for (const source of sourceRecords) { + for (const extract of source.extracts) { + if (claims.length >= maxClaims) break; + claims.push({ + claim: extract.claim, + source_url: source.url, + final_url: source.final_url, + accessed_at: source.fetched_at, + content_digest: source.content_digest, + extract: extract.quote, + confidence: "verified", + confidence_reasoning: "The claim is backed by an exact extract from the captured source snapshot.", + http_status: source.status, + bytes: source.bytes, + }); + } + if (claims.length >= maxClaims) break; + } + + const data = { + objective, + verification_level: verificationLevel, + summary: `${claims.length} claim(s) are backed by exact source extracts from ${sourceRecords.length} captured source(s).`, + claims: verificationLevel === "basic" + ? claims.map(({ content_digest, http_status, bytes, ...claim }) => claim) + : claims, + open_questions: [], + verification_guide: verificationGuide(sourceRecords, verificationLevel), + fixture: { + ref: fixture.ref, + sha256: sha256(fixture.text), + sources: sourceRecords.length, + }, + }; + + if (verificationLevel !== "basic") { + data.evidence_archive = { + sources: verificationLevel === "audit_ready" + ? sourceRecords + : sourceRecords.map(({ content, ...source }) => source), + }; + } + + return { schema: SCHEMA, data }; +} + +function normalizeSource(source) { + for (const field of ["url", "final_url", "fetched_at", "content"]) { + if (!stringValue(source[field])) throw new Error(`source.${field} is required`); + } + if (!Array.isArray(source.extracts) || source.extracts.length === 0) { + throw new Error(`source ${source.url} must contain extracts`); + } + return { + url: source.url, + final_url: source.final_url, + fetched_at: source.fetched_at, + status: Number.isFinite(source.status) ? source.status : null, + bytes: Buffer.byteLength(source.content), + content_digest: `sha256:${sha256(source.content)}`, + content: source.content, + extracts: source.extracts.map((extract) => { + if (!stringValue(extract.claim) || !stringValue(extract.quote)) { + throw new Error(`source ${source.url} extracts require claim and quote`); + } + if (!source.content.includes(extract.quote)) { + throw new Error(`source ${source.url} quote must appear in content`); + } + return { claim: extract.claim, quote: extract.quote }; + }), + }; +} + +function verificationGuide(sources, level) { + const steps = sources.map((source) => ({ + action: "Re-fetch source and compare the quoted extract", + target: source.final_url, + expected: `HTTP ${source.status}; content includes at least one recorded extract; captured digest was ${source.content_digest}`, + })); + + const guide = { + overview: "Each claim can be checked by re-fetching the final URL and comparing the exact extract. Digest mismatch means the source changed after capture.", + steps, + }; + + if (level === "audit_ready") { + guide.replay_instructions = { + commands: sources.map((source) => `curl -L ${source.final_url}`), + digest_algorithm: "sha256 over the captured response body", + }; + } + + return guide; +} + +function renderReport(packet) { + const data = packet.data; + const lines = [ + "# Verifiable Web Research Packet", + "", + `Objective: ${data.objective}`, + `Verification level: ${data.verification_level}`, + "", + "## Summary", + "", + data.summary, + "", + "## Claims", + "", + ]; + + for (const claim of data.claims) { + lines.push(`- ${claim.claim}`); + lines.push(` - Source: ${claim.final_url}`); + lines.push(` - Extract: ${claim.extract}`); + if (claim.content_digest) lines.push(` - Digest: ${claim.content_digest}`); + } + + lines.push(""); + lines.push("## Verification"); + lines.push(""); + lines.push(data.verification_guide.overview); + lines.push(""); + + return `${lines.join("\n")}\n`; +} + +function writeArtifacts(outputDir, packet, report, root) { + if (!outputDir) { + packet.data.artifacts = {}; + return; + } + const resolved = path.resolve(root, outputDir); + ensureInside(root, resolved, "output_dir"); + fs.mkdirSync(resolved, { recursive: true }); + const evidencePath = path.join(resolved, "evidence.json"); + const reportPath = path.join(resolved, "report.md"); + packet.data.artifacts = { + evidence_json: path.relative(root, evidencePath), + report_md: path.relative(root, reportPath), + }; + fs.writeFileSync(evidencePath, `${JSON.stringify(packet, null, 2)}\n`); + fs.writeFileSync(reportPath, report); +} + +function ensureInside(root, resolved, label) { + const normalizedRoot = root.endsWith(path.sep) ? root : `${root}${path.sep}`; + if (resolved !== root && !resolved.startsWith(normalizedRoot)) { + throw new Error(`${label} must stay inside the skill directory`); + } +} + +function sha256(value) { + return crypto.createHash("sha256").update(value).digest("hex"); +} + +function stringValue(value) { + return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; +} diff --git a/skills/verifiable-web-research/schemas/result.schema.json b/skills/verifiable-web-research/schemas/result.schema.json new file mode 100644 index 00000000..07a7ea1f --- /dev/null +++ b/skills/verifiable-web-research/schemas/result.schema.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "runx.verifiable_web_research.result.v1", + "type": "object", + "required": ["schema", "data"], + "properties": { + "schema": { "const": "runx.verifiable_web_research.result.v1" }, + "data": { + "type": "object", + "required": ["objective", "verification_level", "summary", "claims", "verification_guide"], + "properties": { + "objective": { "type": "string", "minLength": 1 }, + "verification_level": { "enum": ["basic", "detailed", "audit_ready"] }, + "summary": { "type": "string" }, + "claims": { "type": "array", "items": { "type": "object" } }, + "verification_guide": { "type": "object" }, + "evidence_archive": { "type": "object" }, + "artifacts": { "type": "object" } + } + } + } +} diff --git a/skills/verifiable-web-research/test.mjs b/skills/verifiable-web-research/test.mjs new file mode 100644 index 00000000..5e8b8a5f --- /dev/null +++ b/skills/verifiable-web-research/test.mjs @@ -0,0 +1,73 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +const skillRoot = import.meta.dirname; + +test("emits an audit-ready packet and writes requested artifacts", () => { + const outputDir = `out-test-${Date.now()}`; + const result = runSkill({ + objective: "Compare AI agent framework evidence", + source_fixture_path: "fixtures/ai-agent-frameworks.json", + verification_level: "audit_ready", + output_dir: outputDir, + }); + + assert.equal(result.status, 0, result.stderr); + + const packet = JSON.parse(result.stdout); + assert.equal(packet.schema, "runx.verifiable_web_research.result.v1"); + assert.equal(packet.data.claims.length, 2); + assert.equal(packet.data.evidence_archive.sources.length, 2); + assert.match(packet.data.claims[0].content_digest, /^sha256:/); + + assert.equal(fs.existsSync(path.join(skillRoot, outputDir, "evidence.json")), true); + assert.equal(fs.existsSync(path.join(skillRoot, outputDir, "report.md")), true); + + fs.rmSync(path.join(skillRoot, outputDir), { recursive: true, force: true }); +}); + +test("rejects extracts that are not present in captured source content", () => { + const fixturePath = path.join("fixtures", `invalid-${Date.now()}.json`); + const fixtureAbsolutePath = path.join(skillRoot, fixturePath); + fs.writeFileSync(fixtureAbsolutePath, JSON.stringify({ + sources: [{ + url: "https://example.org/invalid", + final_url: "https://example.org/invalid", + fetched_at: "2026-06-22T00:00:00Z", + status: 200, + content: "The captured body contains a different sentence.", + extracts: [{ + claim: "This claim should be rejected.", + quote: "This quote is absent.", + }], + }], + })); + + try { + const result = runSkill({ + objective: "Reject invalid extracts", + source_fixture_path: fixturePath, + verification_level: "detailed", + }); + + assert.notEqual(result.status, 0); + assert.match(result.stderr, /quote must appear in content/); + } finally { + fs.rmSync(fixtureAbsolutePath, { force: true }); + } +}); + +function runSkill(inputs) { + return spawnSync(process.execPath, ["run.mjs"], { + cwd: skillRoot, + env: { + ...process.env, + RUNX_INPUTS_JSON: JSON.stringify(inputs), + }, + encoding: "utf8", + }); +} From fd8ab4418d8bb9420b76d5d02178bcf53868755e Mon Sep 17 00:00:00 2001 From: jienigoto Date: Mon, 22 Jun 2026 23:13:54 +0800 Subject: [PATCH 2/3] test(skills): add verifiable research publish harness --- skills/verifiable-web-research/SKILL.md | 3 ++- skills/verifiable-web-research/X.yaml | 25 +++++++++++++++-- skills/verifiable-web-research/run.mjs | 36 +++++++++++++++++++++++++ skills/verifiable-web-research/test.mjs | 26 +++++++++++++++++- 4 files changed, 86 insertions(+), 4 deletions(-) diff --git a/skills/verifiable-web-research/SKILL.md b/skills/verifiable-web-research/SKILL.md index ace90118..dd5a1e8e 100644 --- a/skills/verifiable-web-research/SKILL.md +++ b/skills/verifiable-web-research/SKILL.md @@ -20,7 +20,8 @@ later. Use `web-fetch` when the caller already knows a single URL. Use - `objective` (required): the question the evidence packet must answer. - `source_fixture_path` (required): package-relative JSON fixture containing - source snapshots. + source snapshots. The deterministic publish harness may use + `builtin:ai-agent-frameworks` when external fixture files are unavailable. - `verification_level` (optional): `basic`, `detailed`, or `audit_ready`. Defaults to `detailed`. - `max_claims` (optional): maximum number of evidence claims to include. diff --git a/skills/verifiable-web-research/X.yaml b/skills/verifiable-web-research/X.yaml index 96df4156..83158669 100644 --- a/skills/verifiable-web-research/X.yaml +++ b/skills/verifiable-web-research/X.yaml @@ -7,6 +7,28 @@ catalog: visibility: public role: canonical +harness: + cases: + - name: verifiable-web-research-audit-ready-packet + runner: default + inputs: + objective: Compare captured AI agent framework evidence. + source_fixture_path: builtin:ai-agent-frameworks + verification_level: audit_ready + max_claims: 2 + expect: + status: sealed + receipt: + schema: runx.receipt.v1 + + - name: verifiable-web-research-missing-fixture-fails + runner: default + inputs: + objective: Reject runs without a captured source fixture. + verification_level: detailed + expect: + status: failure + runx: mutating: false idempotency: @@ -34,9 +56,8 @@ runners: default: default: true type: cli-tool - command: /usr/bin/env + command: node args: - - node - run.mjs scopes: - research.evidence.read diff --git a/skills/verifiable-web-research/run.mjs b/skills/verifiable-web-research/run.mjs index f0cb795e..45743abd 100644 --- a/skills/verifiable-web-research/run.mjs +++ b/skills/verifiable-web-research/run.mjs @@ -4,6 +4,38 @@ import path from "node:path"; const SCHEMA = "runx.verifiable_web_research.result.v1"; const LEVELS = new Set(["basic", "detailed", "audit_ready"]); +const BUILTIN_FIXTURES = { + "builtin:ai-agent-frameworks": { + sources: [ + { + url: "https://example.org/langchain-pricing", + final_url: "https://example.org/langchain-pricing", + fetched_at: "2026-06-22T00:00:00Z", + status: 200, + content: "LangChain is open source. LangSmith is an optional hosted observability product with a free developer tier.", + extracts: [ + { + claim: "LangChain has an open-source framework and an optional hosted observability product.", + quote: "LangChain is open source. LangSmith is an optional hosted observability product with a free developer tier.", + }, + ], + }, + { + url: "https://example.org/crewai-docs", + final_url: "https://example.org/crewai-docs", + fetched_at: "2026-06-22T00:00:05Z", + status: 200, + content: "CrewAI provides a multi-agent framework with hosted enterprise options for teams.", + extracts: [ + { + claim: "CrewAI offers a multi-agent framework with hosted enterprise options.", + quote: "CrewAI provides a multi-agent framework with hosted enterprise options for teams.", + }, + ], + }, + ], + }, +}; const inputs = readInputs(); const skillRoot = process.cwd(); @@ -32,6 +64,10 @@ function readInputs() { } function readFixture(relativePath, root) { + if (Object.hasOwn(BUILTIN_FIXTURES, relativePath)) { + const text = JSON.stringify(BUILTIN_FIXTURES[relativePath]); + return { ref: relativePath, text, sources: BUILTIN_FIXTURES[relativePath].sources }; + } const resolved = path.resolve(root, relativePath); ensureInside(root, resolved, "source_fixture_path"); const text = fs.readFileSync(resolved, "utf8"); diff --git a/skills/verifiable-web-research/test.mjs b/skills/verifiable-web-research/test.mjs index 5e8b8a5f..8a0ebd9a 100644 --- a/skills/verifiable-web-research/test.mjs +++ b/skills/verifiable-web-research/test.mjs @@ -4,8 +4,9 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; +import { fileURLToPath } from "node:url"; -const skillRoot = import.meta.dirname; +const skillRoot = path.dirname(fileURLToPath(import.meta.url)); test("emits an audit-ready packet and writes requested artifacts", () => { const outputDir = `out-test-${Date.now()}`; @@ -61,6 +62,29 @@ test("rejects extracts that are not present in captured source content", () => { } }); +test("supports the built-in publish harness fixture", () => { + const result = runSkill({ + objective: "Compare built-in AI agent framework evidence", + source_fixture_path: "builtin:ai-agent-frameworks", + verification_level: "audit_ready", + max_claims: 2, + }); + + assert.equal(result.status, 0, result.stderr); + + const packet = JSON.parse(result.stdout); + assert.equal(packet.data.fixture.ref, "builtin:ai-agent-frameworks"); + assert.equal(packet.data.claims.length, 2); +}); + +test("declares publish harness coverage for happy and stop/error paths", () => { + const manifest = fs.readFileSync(path.join(skillRoot, "X.yaml"), "utf8"); + + assert.match(manifest, /harness:\s*\n\s*cases:/); + assert.match(manifest, /name:\s*verifiable-web-research-audit-ready-packet/); + assert.match(manifest, /name:\s*verifiable-web-research-missing-fixture-fails/); +}); + function runSkill(inputs) { return spawnSync(process.execPath, ["run.mjs"], { cwd: skillRoot, From f187df675b4cc7d35dcb936f19d6be2ea65352a5 Mon Sep 17 00:00:00 2001 From: jienigoto Date: Mon, 22 Jun 2026 23:42:14 +0800 Subject: [PATCH 3/3] docs(skills): add verifiable research delivery evidence --- .../references/evidence.json | 103 ++++++++++++++++++ .../references/report.md | 56 ++++++++++ .../references/verification.json | 68 ++++++++++++ 3 files changed, 227 insertions(+) create mode 100644 skills/verifiable-web-research/references/evidence.json create mode 100644 skills/verifiable-web-research/references/report.md create mode 100644 skills/verifiable-web-research/references/verification.json diff --git a/skills/verifiable-web-research/references/evidence.json b/skills/verifiable-web-research/references/evidence.json new file mode 100644 index 00000000..5f8c7b09 --- /dev/null +++ b/skills/verifiable-web-research/references/evidence.json @@ -0,0 +1,103 @@ +{ + "schema": "runx.verifiable_web_research.delivery_evidence.v1", + "summary": "Published and verified a reusable Runx skill that builds auditable web research packets from captured source snapshots. The skill emits exact extracts, content digests, confidence reasoning, replay guidance, and optional evidence/report artifacts without reaching the network during execution.", + "skill": { + "name": "verifiable-web-research", + "published_ref": "jienigoto/verifiable-web-research@sha-f0d326500848", + "owner": "jienigoto", + "publisher_principal": "user_476edbcb37a0fa1c989987ad", + "registry": "https://api.runx.ai", + "public_url": "https://runx.ai/x/jienigoto/verifiable-web-research", + "versioned_public_url": "https://runx.ai/x/jienigoto/verifiable-web-research@sha-f0d326500848", + "source_url": "https://github.com/jienigoto/runx/tree/fd8ab4418d8bb9420b76d5d02178bcf53868755e/skills/verifiable-web-research", + "pr_url": "https://github.com/runxhq/runx/pull/112", + "digest": "7547a9783368d97b03be077d5ba822b3a817a6394634756091e9507f47775882", + "profile_digest": "6230275c1ddf69851b58e1028f002ff9bf83ffc61c9311a948889f857ddc83e6", + "trust_tier": "community" + }, + "checks": { + "runx_cli": { + "status": "passed", + "command": "runx --version", + "output": "runx-cli 0.6.13" + }, + "local_tests": { + "status": "passed", + "command": "node --test skills/verifiable-web-research/test.mjs", + "tests": 4 + }, + "local_harness": { + "status": "passed", + "command": "runx harness ./skills/verifiable-web-research --receipt-dir .runx-delivery-receipts --json", + "case_count": 2, + "case_names": [ + "verifiable-web-research-audit-ready-packet", + "verifiable-web-research-missing-fixture-fails" + ], + "receipt_ids": [ + "sha256:e3d0d862edde1959bf7e14540554f9d8359f8b1b49eb500c02eae5601979333e", + "sha256:c31b620b06736a5bb7507c8353619612618a440d5c41737d2780ccc1ff20f626" + ] + }, + "receipt_verification": { + "status": "passed", + "verify_command": "runx verify --receipt-dir .runx-delivery-receipts sha256:e3d0d862edde1959bf7e14540554f9d8359f8b1b49eb500c02eae5601979333e --json", + "verify_kid": "local-harness-key", + "verify_public_key_base64": "IVL40Zt5HSRFMkLhXy6rbLfP+ntqXtMAl5YOBpiB2xI=", + "valid_receipts": [ + "sha256:e3d0d862edde1959bf7e14540554f9d8359f8b1b49eb500c02eae5601979333e", + "sha256:c31b620b06736a5bb7507c8353619612618a440d5c41737d2780ccc1ff20f626" + ] + }, + "registry_publish": { + "status": "published", + "command": "runx registry publish ./skills/verifiable-web-research/SKILL.md --registry https://api.runx.ai --json", + "install_command": "runx add jienigoto/verifiable-web-research@sha-f0d326500848 --registry https://api.runx.ai", + "run_command": "runx skill jienigoto/verifiable-web-research@sha-f0d326500848 --registry https://api.runx.ai" + }, + "registry_readback": { + "status": "passed", + "command": "runx registry read jienigoto/verifiable-web-research@sha-f0d326500848 --registry https://api.runx.ai --json", + "runner_names": [ + "default" + ], + "publisher_handle": "jienigoto", + "markdown_contract_checked": true + }, + "registry_install": { + "status": "installed", + "command": "runx add jienigoto/verifiable-web-research@sha-f0d326500848 --registry https://api.runx.ai --json" + }, + "registry_dogfood_run": { + "status": "sealed", + "command": "runx skill jienigoto/verifiable-web-research@sha-f0d326500848 --registry https://api.runx.ai -i objective= -i source_fixture_path=builtin:ai-agent-frameworks -i verification_level=audit_ready -i max_claims=2 --json", + "receipt_id_observed": "sha256:939eafcfcb9215633445658edc5e2b97b2a82211fcb4cc3e4955b7e942c5c83e", + "note": "The registry skill was admitted and sealed. The durable receipt proof for this delivery is the local harness receipt set above, because the current Windows/WSL receipt directory boundary did not retain the dogfood receipt file." + }, + "public_urls": { + "status": "passed", + "checked_urls": [ + "https://runx.ai/x/jienigoto/verifiable-web-research", + "https://runx.ai/x/jienigoto/verifiable-web-research@sha-f0d326500848" + ], + "http_status": 200 + } + }, + "observations": [ + "runx --version returned runx-cli 0.6.13, satisfying the stated CLI floor for the bounty.", + "The published registry ref is jienigoto/verifiable-web-research@sha-f0d326500848 under the authenticated jienigoto namespace.", + "runx registry read jienigoto/verifiable-web-research@sha-f0d326500848 --registry https://api.runx.ai --json resolves the package metadata, digest, profile digest, publisher, trust tier, and default runner.", + "runx add jienigoto/verifiable-web-research@sha-f0d326500848 --registry https://api.runx.ai --json installed the package from the remote registry.", + "The local harness passed both declared cases: an audit-ready packet from builtin:ai-agent-frameworks and a missing-fixture failure case.", + "runx verify returned valid=true for the harness receipt sha256:e3d0d862edde1959bf7e14540554f9d8359f8b1b49eb500c02eae5601979333e with kid local-harness-key and public key IVL40Zt5HSRFMkLhXy6rbLfP+ntqXtMAl5YOBpiB2xI=.", + "runx verify returned valid=true for the harness receipt sha256:c31b620b06736a5bb7507c8353619612618a440d5c41737d2780ccc1ff20f626 with the same public key.", + "The skill runner never reaches the network; it converts captured public source snapshots into auditable claims, extracts, digests, and replay instructions.", + "The versioned public adoption page https://runx.ai/x/jienigoto/verifiable-web-research@sha-f0d326500848 returned HTTP 200." + ], + "artifact_refs": { + "public_url": "https://runx.ai/x/jienigoto/verifiable-web-research@sha-f0d326500848", + "source_url": "https://github.com/jienigoto/runx/tree/fd8ab4418d8bb9420b76d5d02178bcf53868755e/skills/verifiable-web-research", + "pr_url": "https://github.com/runxhq/runx/pull/112", + "receipt_ref": "runx:receipt:sha256:e3d0d862edde1959bf7e14540554f9d8359f8b1b49eb500c02eae5601979333e" + } +} diff --git a/skills/verifiable-web-research/references/report.md b/skills/verifiable-web-research/references/report.md new file mode 100644 index 00000000..a5e1b500 --- /dev/null +++ b/skills/verifiable-web-research/references/report.md @@ -0,0 +1,56 @@ +# Verifiable Web Research Delivery Report + +## Package + +- Registry ref: `jienigoto/verifiable-web-research@sha-f0d326500848` +- Public URL: https://runx.ai/x/jienigoto/verifiable-web-research@sha-f0d326500848 +- Source PR: https://github.com/runxhq/runx/pull/112 +- Package digest: `7547a9783368d97b03be077d5ba822b3a817a6394634756091e9507f47775882` +- Profile digest: `6230275c1ddf69851b58e1028f002ff9bf83ffc61c9311a948889f857ddc83e6` + +## What the Skill Does + +`verifiable-web-research` builds an auditable research packet from captured +source snapshots. It emits claim text, source URLs, final URLs, access times, +content digests, exact extracts, confidence reasoning, and replay steps. The +runner does not reach the network, so harness runs are deterministic and safe. + +## Verification Summary + +- `runx --version` returned `runx-cli 0.6.13`. +- `node --test skills/verifiable-web-research/test.mjs` passed 4 tests. +- `runx harness ./skills/verifiable-web-research --receipt-dir .runx-delivery-receipts --json` passed 2 declared cases. +- `runx registry publish ./skills/verifiable-web-research/SKILL.md --registry https://api.runx.ai --json` published the package. +- `runx registry read jienigoto/verifiable-web-research@sha-f0d326500848 --registry https://api.runx.ai --json` resolved the registry package. +- `runx add jienigoto/verifiable-web-research@sha-f0d326500848 --registry https://api.runx.ai --json` installed the package from the remote registry. +- The versioned public page returned HTTP 200. + +## Receipt Proof + +Primary receipt: + +`runx:receipt:sha256:e3d0d862edde1959bf7e14540554f9d8359f8b1b49eb500c02eae5601979333e` + +Verification command: + +```bash +RUNX_RECEIPT_VERIFY_KID=local-harness-key \ +RUNX_RECEIPT_VERIFY_ED25519_PUBLIC_KEY_BASE64=IVL40Zt5HSRFMkLhXy6rbLfP+ntqXtMAl5YOBpiB2xI= \ +runx verify --receipt-dir .runx-delivery-receipts \ + sha256:e3d0d862edde1959bf7e14540554f9d8359f8b1b49eb500c02eae5601979333e \ + --json +``` + +Result: `valid: true`. + +The negative-path harness receipt +`sha256:c31b620b06736a5bb7507c8353619612618a440d5c41737d2780ccc1ff20f626` +also verifies with `valid: true`. + +## Notes + +The registry skill dogfood run was admitted and sealed under +`jienigoto/verifiable-web-research@sha-f0d326500848`. On this Windows/WSL +machine, the registry dogfood receipt file did not remain readable across the +receipt directory boundary, so the durable receipt proof submitted here is the +harness receipt set. diff --git a/skills/verifiable-web-research/references/verification.json b/skills/verifiable-web-research/references/verification.json new file mode 100644 index 00000000..38ae439a --- /dev/null +++ b/skills/verifiable-web-research/references/verification.json @@ -0,0 +1,68 @@ +{ + "schema": "runx.verifiable_web_research.delivery_verification.v1", + "generated_at": "2026-06-22T15:35:00Z", + "runx_version": { + "command": "runx --version", + "stdout": "runx-cli 0.6.13", + "status": "passed" + }, + "registry": { + "ref": "jienigoto/verifiable-web-research@sha-f0d326500848", + "public_url": "https://runx.ai/x/jienigoto/verifiable-web-research@sha-f0d326500848", + "read_command": "runx registry read jienigoto/verifiable-web-research@sha-f0d326500848 --registry https://api.runx.ai --json", + "read_status": "passed", + "install_command": "runx add jienigoto/verifiable-web-research@sha-f0d326500848 --registry https://api.runx.ai --json", + "install_status": "passed", + "digest": "7547a9783368d97b03be077d5ba822b3a817a6394634756091e9507f47775882", + "profile_digest": "6230275c1ddf69851b58e1028f002ff9bf83ffc61c9311a948889f857ddc83e6", + "publisher": "jienigoto", + "trust_tier": "community", + "runner_names": [ + "default" + ] + }, + "harness": { + "command": "runx harness ./skills/verifiable-web-research --receipt-dir .runx-delivery-receipts --json", + "status": "passed", + "case_count": 2, + "cases": [ + { + "name": "verifiable-web-research-audit-ready-packet", + "status": "passed", + "receipt_id": "sha256:e3d0d862edde1959bf7e14540554f9d8359f8b1b49eb500c02eae5601979333e" + }, + { + "name": "verifiable-web-research-missing-fixture-fails", + "status": "passed", + "receipt_id": "sha256:c31b620b06736a5bb7507c8353619612618a440d5c41737d2780ccc1ff20f626" + } + ] + }, + "receipt_verification": { + "signature_mode": "production", + "verify_kid": "local-harness-key", + "verify_public_key_base64": "IVL40Zt5HSRFMkLhXy6rbLfP+ntqXtMAl5YOBpiB2xI=", + "verdicts": [ + { + "command": "runx verify --receipt-dir .runx-delivery-receipts sha256:e3d0d862edde1959bf7e14540554f9d8359f8b1b49eb500c02eae5601979333e --json", + "root_receipt_id": "sha256:e3d0d862edde1959bf7e14540554f9d8359f8b1b49eb500c02eae5601979333e", + "valid": true, + "receipt_count": 1, + "findings": [] + }, + { + "command": "runx verify --receipt-dir .runx-delivery-receipts sha256:c31b620b06736a5bb7507c8353619612618a440d5c41737d2780ccc1ff20f626 --json", + "root_receipt_id": "sha256:c31b620b06736a5bb7507c8353619612618a440d5c41737d2780ccc1ff20f626", + "valid": true, + "receipt_count": 1, + "findings": [] + } + ] + }, + "dogfood": { + "command": "runx skill jienigoto/verifiable-web-research@sha-f0d326500848 --registry https://api.runx.ai -i objective= -i source_fixture_path=builtin:ai-agent-frameworks -i verification_level=audit_ready -i max_claims=2 --json", + "status": "sealed", + "observed_receipt_id": "sha256:939eafcfcb9215633445658edc5e2b97b2a82211fcb4cc3e4955b7e942c5c83e", + "note": "The registry skill was admitted and sealed. Durable receipt verification in this file uses the harness receipts because the registry dogfood receipt file was not retained across the Windows/WSL receipt directory boundary." + } +}