Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1ba6797
test: add issue-liveness regression suite (14 confirmed-live issues)
Bojan131 Jun 11, 2026
7917fb6
test: reproducing tests for all 25 HIGH / pre-mainnet issues
Bojan131 Jun 11, 2026
aa23cef
Merge remote-tracking branch 'origin/main' into test/issue-liveness-s…
Bojan131 Jun 12, 2026
2d5d74a
test(issue-liveness): real CI repros for #462 #936 #1013 #1078 #1091;…
Bojan131 Jun 12, 2026
1d20474
test(issue-liveness): CI root-cause repro for #1124 (host-mode drops …
Bojan131 Jun 12, 2026
f26bbde
test(issue-liveness): address Codex review on PR #1129; remove the doc
Bojan131 Jun 12, 2026
635dc2b
test(issue-liveness): gate CI repros behind RUN_ISSUE_LIVENESS; fix #…
Bojan131 Jun 12, 2026
144e30b
chore(turbo): pass RUN_ISSUE_LIVENESS through to test tasks
Bojan131 Jun 12, 2026
96758c1
test(issue-liveness): address Codex round-3 on PR #1129
Bojan131 Jun 12, 2026
d33cf88
test(issue-liveness): address Codex round-4 (env parsing, evm-module …
Bojan131 Jun 12, 2026
6389eb4
test(issue-liveness): address Codex round-5 (devnet publisher probe +…
Bojan131 Jun 12, 2026
ac8b652
test(issue-liveness): address Codex round-6 (downgrade #1124, tighten…
Bojan131 Jun 12, 2026
f522d89
test(issue-liveness): address Codex round-7 (devnet false-positive + …
Bojan131 Jun 12, 2026
f64a69e
test(issue-liveness): address Codex round-8 (snapshot-isolate #1091, …
Bojan131 Jun 12, 2026
da1a87d
chore: restore localhost_contracts.json to main (deploy artifact churn)
Bojan131 Jun 12, 2026
ed5761f
ci: run issue-liveness repros in CI (red = live bug) + fix stale Shar…
Bojan131 Jun 12, 2026
a266a81
test(evm): fix stale EpochStorage version assertion (10.0.2 → 10.0.3)
Bojan131 Jun 12, 2026
597e56a
test: run issue regression tests in the standard package lanes + devn…
Bojan131 Jun 12, 2026
d5e07dd
Merge remote-tracking branch 'origin/fix/high-pre-mainnet-issues' int…
Bojan131 Jun 18, 2026
f8fd474
test: fix two stale liveness tests whose fixes already landed (#184/#…
Bojan131 Jun 18, 2026
fea04c4
test: fix #1095 devnet liveness assertion (false negative vs real impl)
Bojan131 Jun 18, 2026
85ceac0
Merge remote-tracking branch 'origin/main' into test/issue-liveness-s…
Bojan131 Jun 19, 2026
19bd01d
test(#1091): refresh grindable-seed repro for the PR #1226 1-arg prev…
Bojan131 Jun 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 212 additions & 0 deletions devnet/issue-liveness/automated.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/**
* Multi-node issue-liveness regression suite (live devnet).
*
* Reproduces confirmed-live cross-node bugs from the rc.17 QA sweep. Each is an
* `it.fails` repro: the assertion of CORRECT behaviour fails today (bug live),
* so the suite is green while the bugs persist and turns RED when one is fixed —
* signalling that the linked GitHub issue can close.
*
* #705 / #923 — assertion lifecycle metadata (`dkg:state`, the `_meta`
* lifecycle record) is written ONLY on the authoring node and is
* not replicated, so a peer that received the SWM data cannot
* resolve the assertion's lifecycle state / descriptor.
* https://github.com/OriginTrail/dkg/issues/705
* https://github.com/OriginTrail/dkg/issues/923
*
* #872 — imported Markdown SOURCE bytes are not replicated to peers of a
* public/open CG; a peer that synced the structural triples cannot
* fetch the original document via import-artifact/read-markdown.
* https://github.com/OriginTrail/dkg/issues/872
*
* Preconditions:
* ./scripts/devnet.sh clean && ./scripts/devnet.sh start 6
* node devnet/_bootstrap/bootstrap.cjs
* Run: pnpm test:devnet:issue-liveness
*/
import { describe, it, expect, beforeAll } from 'vitest';
import { readFileSync, existsSync } from 'node:fs';
import { join, resolve } from 'node:path';
import * as http from 'node:http';

const REPO_ROOT = resolve(__dirname, '../..');
Comment thread
Bojan131 marked this conversation as resolved.
const DEVNET_DIR = join(REPO_ROOT, '.devnet');
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

interface DevnetNode {
num: number;
apiPort: number;
home: string;
authToken: string;
}

function readNode(num: number): DevnetNode {
const home = join(DEVNET_DIR, `node${num}`);
if (!existsSync(home)) {
throw new Error(`Devnet node${num} home missing — run ./scripts/devnet.sh start 6 first`);
}
const config = JSON.parse(readFileSync(join(home, 'config.json'), 'utf8'));
let authToken = '';
if (existsSync(join(home, 'auth.token'))) {
authToken =
readFileSync(join(home, 'auth.token'), 'utf8')
.split('\n')
.map((l) => l.trim())
.find((l) => l.length > 0 && !l.startsWith('#')) ?? '';
}
return { num, apiPort: config.apiPort, home, authToken };
}

function request(
method: 'GET' | 'POST',
url: string,
token: string,
body?: unknown,
contentType = 'application/json',
): Promise<{ status: number; body: any }> {
return new Promise((resolveP, rejectP) => {
const u = new URL(url);
const data =
body === undefined ? '' : contentType === 'application/json' ? JSON.stringify(body) : (body as string);
const req = http.request(
{
host: u.hostname,
port: u.port,
method,
path: u.pathname + u.search,
headers: {
Authorization: `Bearer ${token}`,
...(data ? { 'Content-Type': contentType, 'Content-Length': Buffer.byteLength(data) } : {}),
},
},
(res) => {
let buf = '';
res.on('data', (c) => (buf += c));
res.on('end', () => {
try {
resolveP({ status: res.statusCode ?? 0, body: JSON.parse(buf) });
} catch {
resolveP({ status: res.statusCode ?? 0, body: buf });
}
});
},
);
req.on('error', rejectP);
if (data) req.write(data);
req.end();
});
}

const api = (n: DevnetNode) => `http://127.0.0.1:${n.apiPort}`;
const get = (n: DevnetNode, p: string) => request('GET', api(n) + p, n.authToken);
const post = (n: DevnetNode, p: string, b: unknown) => request('POST', api(n) + p, n.authToken, b);

const STAMP = Date.now();
const CG = `issue-liveness-${STAMP}`;
const KA = `liveness-ka-${STAMP}`;
const ENTITY = `https://example.org/liveness/${STAMP}`;

let node1: DevnetNode;
let node2: DevnetNode;
let assertionUri = '';
let importFileHash = '';
let importAssertionUri = '';

describe('multi-node issue liveness', () => {
beforeAll(async () => {
node1 = readNode(1);
node2 = readNode(2);

// node1: public CG, registered on-chain.
await post(node1, '/api/context-graph/create', { id: CG, name: `Liveness ${STAMP}`, accessPolicy: 0 });
await post(node1, '/api/context-graph/register', { id: CG });

// node1: create → write → finalize → share a named assertion.
await post(node1, '/api/knowledge-assets', { contextGraphId: CG, name: KA });
await post(node1, `/api/knowledge-assets/${KA}/wm/write`, {
contextGraphId: CG,
quads: [{ subject: ENTITY, predicate: 'https://schema.org/name', object: '"LivenessEntity"' }],
});
await post(node1, `/api/knowledge-assets/${KA}/wm/finalize`, { contextGraphId: CG });
const share = await post(node1, `/api/knowledge-assets/${KA}/swm/share`, { contextGraphId: CG, entities: 'all' });
expect(share.status).toBe(200);

// node1: import a Markdown file + promote (for #872).
const md = `# Liveness Doc ${STAMP}\n\nSection A — replicated bytes check.\n`;
const boundary = '----dkgLiveness' + STAMP;
const multipart =
`--${boundary}\r\nContent-Disposition: form-data; name="contextGraphId"\r\n\r\n${CG}\r\n` +
`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="liveness-${STAMP}.md"\r\n` +
`Content-Type: text/markdown\r\n\r\n${md}\r\n--${boundary}--\r\n`;
const imp = await request(
'POST',
`${api(node1)}/api/knowledge-assets/liveness-import-${STAMP}/wm/import-file`,
node1.authToken,
multipart,
`multipart/form-data; boundary=${boundary}`,
);
if (imp.status === 200 && imp.body) {
importFileHash = imp.body.fileHash ?? '';
importAssertionUri = imp.body.assertionUri ?? '';
await post(node1, `/api/knowledge-assets/liveness-import-${STAMP}/swm/share`, {
contextGraphId: CG,
entities: 'all',
});
}

assertionUri = `did:dkg:context-graph:${CG}/assertion/`;

// node2: subscribe + give catch-up time to replicate the SWM data.
await post(node2, '/api/context-graph/subscribe', { contextGraphId: CG });
for (let i = 0; i < 24; i++) {
const r = await post(node2, '/api/query', {
sparql: `SELECT ?o WHERE { ?s <https://schema.org/name> ?o }`,
contextGraphId: CG,
view: 'shared-working-memory',
});
const rows = r.body?.result?.bindings?.length ?? 0;
if (rows > 0) break;
await sleep(5000);
}
}, 240_000);

it('CONTROL: node2 replicated the SWM data for the peer-authored assertion', async () => {
const r = await post(node2, '/api/query', {
sparql: `SELECT ?o WHERE { ?s <https://schema.org/name> ?o }`,
contextGraphId: CG,
view: 'shared-working-memory',
});
const names = (r.body?.result?.bindings ?? []).map((b: any) => b.o);
expect(names.some((n: string) => String(n).includes('LivenessEntity'))).toBe(true);
});

it.fails(
'GH #705/#923: node2 can resolve lifecycle state for the peer-authored assertion',
async () => {
// The lifecycle record lives only in node1's non-replicated `_meta`, so
// node2 sees zero lifecycle rows for the assertion it received via SWM.
const r = await post(node2, '/api/query', {
sparql:
'PREFIX dkg: <http://dkg.io/ontology/> SELECT ?a ?state WHERE { ?a a dkg:Assertion ; dkg:state ?state }',
Comment thread
Bojan131 marked this conversation as resolved.
Outdated
contextGraphId: CG,
graphSuffix: '_meta',
});
const rows = r.body?.result?.bindings?.length ?? 0;
expect(rows).toBeGreaterThan(0);
},
);

it.fails(
'GH #872: node2 (public-CG peer) can fetch the imported Markdown source bytes',
async () => {
// Skip cleanly if the import didn't land (keeps the assertion meaningful).
expect(importFileHash).not.toBe('');
Comment thread
Bojan131 marked this conversation as resolved.
Outdated
Comment thread
Bojan131 marked this conversation as resolved.
Outdated
const r = await post(node2, '/api/knowledge-assets/import-artifact/read-markdown', {
contextGraphId: CG,
assertionUri: importAssertionUri,
fileHash: importFileHash,
});
expect(r.status).toBe(200);
expect(String(r.body?.markdown ?? r.body)).toContain('Liveness Doc');
},
);
});
15 changes: 15 additions & 0 deletions devnet/issue-liveness/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "@origintrail-official/dkg-devnet-issue-liveness",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"test:devnet": "vitest run --config vitest.config.ts"
},
"dependencies": {
"ethers": "^6.16.0"
},
"devDependencies": {
"vitest": "^4.0.18"
}
}
28 changes: 28 additions & 0 deletions devnet/issue-liveness/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { defineConfig } from 'vitest/config';
import { resolve } from 'node:path';

/**
* Multi-node issue-liveness regression suite against a live devnet.
*
* Encodes confirmed-live cross-node bugs from the rc.17 QA sweep as `it.fails`
* repros — each fails today (bug live) and flips RED when fixed, signalling the
* linked GitHub issue can close. Manual-run (needs a live devnet), like the
* sibling devnet suites.
*
* Preconditions:
* pnpm run build
* ./scripts/devnet.sh clean && ./scripts/devnet.sh start 6
* node devnet/_bootstrap/bootstrap.cjs
*
* Run: pnpm test:devnet:issue-liveness
*/
export default defineConfig({
test: {
include: [resolve(__dirname, 'automated.test.ts')],
Comment thread
Bojan131 marked this conversation as resolved.
Outdated
testTimeout: 240_000,
hookTimeout: 240_000,
pool: 'forks',
fileParallelism: false,
reporters: ['verbose'],
},
});
74 changes: 74 additions & 0 deletions docs/testing/ISSUE_LIVENESS_TESTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Issue-liveness regression tests

A suite that encodes confirmed-live GitHub issues as runnable tests, so we can
(a) prove which issues are still real and (b) get a self-closing signal when one
gets fixed.

## How it works (the `it.fails` / `test.fixme` convention)

Each test asserts the **correct** behaviour the issue asks for. While the bug is
live, that assertion throws — so the test is wrapped in vitest's `it.fails`
(`test.fixme` for Playwright). That means:

- **Bug live →** assertion fails → `it.fails` reports **pass** → CI stays green.
- **Bug fixed →** assertion passes → `it.fails` reports **fail** ("expected to
fail but passed") → CI goes **red**, telling the fixer to remove `.fails`,
make it a plain `it(...)`, and close the issue.

So a red `it.fails` is the cue that an issue can be closed. Every test names its
issue in the title and links it in the file header.

All tests were written against — and confirmed failing-as-expected on — a real
node / live devnet during the 2026-06-11 QA sweep. Zero mocks for chain or
network behaviour.

## What's covered (14 issues)

### Tier 1 — single-node, runs in the normal `turbo test` CI lanes

| Issue | Test file | Asserts (correct behaviour) |
|---|---|---|
| [#1125](https://github.com/OriginTrail/dkg/issues/1125) | `packages/cli/test/skill-md-dynamic-section.test.ts` | served skill.md has no literal `(dynamic)` placeholders |
| [#675](https://github.com/OriginTrail/dkg/issues/675) | `packages/query/test/subgraph-view-scoping.test.ts` | WM-view query includes sub-graph data |
| [#184](https://github.com/OriginTrail/dkg/issues/184) | `packages/query/test/subgraph-view-scoping.test.ts` | `view` + `subGraphName` scopes instead of throwing |
| [#416](https://github.com/OriginTrail/dkg/issues/416) | `packages/core/test/escape-rdf-literal-control-chars.test.ts` | escaper UCHAR-encodes NUL/VT/DEL control bytes |
| [#709](https://github.com/OriginTrail/dkg/issues/709) | `packages/epcis/test/event-type-container-filter.test.ts` | events query excludes the `EPCISDocument` container |
| [#15](https://github.com/OriginTrail/dkg/issues/15) | `packages/cli/test/rdf-parser-jsonld.test.ts` | `.jsonld` with `@context` parses (or isn't advertised) |
| [#787](https://github.com/OriginTrail/dkg/issues/787) | `packages/cli/test/issue-liveness-daemon-routes.test.ts` | SWM write of string quads → 4xx, not 500 |
| [#306](https://github.com/OriginTrail/dkg/issues/306) | `packages/cli/test/issue-liveness-daemon-routes.test.ts` | KA wm/write of string quads → 4xx, not 500 |
| [#158](https://github.com/OriginTrail/dkg/issues/158) | `packages/cli/test/issue-liveness-daemon-routes.test.ts` | ccl/eval not-found (real CG) → 4xx, not 500 |
| [#309](https://github.com/OriginTrail/dkg/issues/309) | `packages/cli/test/issue-liveness-daemon-routes.test.ts` | `/api/status` exposes `defaultAgentAddress` |
| [#757](https://github.com/OriginTrail/dkg/issues/757) | `packages/cli/test/issue-liveness-daemon-routes.test.ts` | non-curator token is 403'd from `/join-requests` |

The daemon-route file spins one real edge daemon against the shared Hardhat node
(zero chain mocks), same harness as `daemon-http-behavior-extra.test.ts`.

### Tier 2 — multi-node, manual-run devnet suite

`devnet/issue-liveness/automated.test.ts` (run: `pnpm test:devnet:issue-liveness`
after `./scripts/devnet.sh start 6` + bootstrap). A `CONTROL` test proves the SWM
data actually replicated to the peer, so the `it.fails` repros can't pass for the
wrong reason.

| Issue | Asserts (correct behaviour) |
|---|---|
| [#705](https://github.com/OriginTrail/dkg/issues/705) / [#923](https://github.com/OriginTrail/dkg/issues/923) | a peer can resolve lifecycle state for a peer-authored assertion |
| [#872](https://github.com/OriginTrail/dkg/issues/872) | a public-CG peer can fetch imported Markdown source bytes |

## Deferred (confirmed live, automated test intentionally NOT written)

Writing a test for these would either fabricate signal or need fixtures too heavy
for CI. They stay tracked as live issues; a test should come with the fix PR.

- **#614** (conviction-NFT sweep into a closed epoch) — money-path **contract**
bug, "Steps to reproduce: N/A". Needs a contracts engineer to model the exact
billing-window math; a speculative hardhat test risks passing for the wrong
reason.
- **#1091** (grindable RandomSampling seed) — un-grindability is a **design
property** (commit-reveal / VRF), not a single-assertion unit fact.
- **#1112 / #1113 / #1015** (UI count caps) — need a >50k-triple fixture to
exercise the display cap; too heavy for the Playwright lane.
- **#966** (no single-root publish UI path on a multi-root CG) — needs a
multi-root SWM UI fixture; reachable but low value (low/post-mainnet).
- **#467 / #703 / #998** — environment-specific (markitdown install fidelity,
live OpenClaw runtime) that a CI box can't fake.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"test:devnet:edge-update-flow": "vitest run --config devnet/edge-update-flow/vitest.config.ts",
"test:devnet:greenfield-10min": "vitest run --config devnet/greenfield-10min/vitest.config.ts",
"test:devnet:rich-scenario": "vitest run --config devnet/rich-scenario/vitest.config.ts",
"test:devnet:issue-liveness": "vitest run --config devnet/issue-liveness/vitest.config.ts",
"test:all": "pnpm test && pnpm test:evm"
},
"devDependencies": {
Expand Down
Loading
Loading