mirror of
https://github.com/openprose/prose.git
synced 2026-09-19 05:55:05 +08:00
9248d949e7
The open-prose skill ships as a plain folder, so everything under skills/open-prose/examples/ reaches every install. Each runtime example shipped a committed replay/ state-dir (around 480 binary world-model files), a generator, and a test suite wired to the workspace through a dist alias with no local package.json. That machinery is internal validation, not the template a user lifts: it cannot run from a copied folder, and it shipped generated artifacts nobody should copy. Keep the shipped examples to what a user actually adapts, and move the validation into the repo's test tree: - Move each example's tests, generator, and committed replay/ into tests/open-prose/examples/<name>/. The shipped example is now its .prose contracts under src/, a README, and a .gitignore. - Re-point the vitest include and the test scripts; the byte-deterministic offline coverage runs unchanged, just rooted under tests/. - Rewrite the example READMEs to point at the reactor CLI for producing and replaying a run, and drop the stale committed-fixture sections. - Update the examples-gate workflow description to match the new layout. No language, CLI, or runtime behavior changes, and the example contracts are untouched. Verified with the offline gate (REACTOR_OFFLINE=1): the conformance suite, the relocated example tests, and the eval harness all pass.
153 lines
5.4 KiB
TypeScript
153 lines
5.4 KiB
TypeScript
// The basic-unit-suite OPTIONAL key-gated LIVE check: a reliability
|
|
// smoke that drives the gateway→summary edge with a REAL model at the
|
|
// `asyncMounts` seam (createAgentRender) instead of the deterministic fake.
|
|
//
|
|
// It honors REACTOR_OFFLINE and is gated `{ skip: hasOpenRouterKey() ? false :
|
|
// "…" }` exactly like every other live test in the repo: a keyless /
|
|
// REACTOR_OFFLINE=1 run reports a PASSING SKIPPED body and never touches the
|
|
// network, so the offline commit gate is unaffected by this file.
|
|
//
|
|
// The reliability property (the live half of the substrate): with a real render,
|
|
// a cold start renders the gateway and propagates to the count summary, and a
|
|
// NO-CHANGE re-wake memo-SKIPS the gateway at ZERO model calls: the memo key
|
|
// gates render even when the render is a model session. The deterministic sibling
|
|
// (basic-unit-suite.test.ts) is the green bar that gates the commit; this only
|
|
// kicks the tires with a model when a key is set.
|
|
|
|
import { strict as assert } from "node:assert";
|
|
import { describe, it } from "vitest";
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
import {
|
|
mountDag,
|
|
createFileSystemReceiptLedger,
|
|
createReplaySession,
|
|
} from "@openprose/reactor";
|
|
import type { ReconcilerTopology } from "@openprose/reactor/internals";
|
|
import { createFileSystemStorageAdapter } from "@openprose/reactor";
|
|
import {
|
|
createAgentRender,
|
|
createOpenRouterProvider,
|
|
hasOpenRouterKey,
|
|
isOfflineForced,
|
|
} from "@openprose/reactor/agents";
|
|
|
|
import { GATEWAY, COUNT_SUMMARY, COUNTS_FACET } from "./generate";
|
|
|
|
// The single gate: a key present AND not offline-forced. Otherwise the body is a
|
|
// passing skipped no-op.
|
|
const LIVE = hasOpenRouterKey() && !isOfflineForced();
|
|
const skip = LIVE
|
|
? false
|
|
: "no OPENROUTER_API_KEY (or REACTOR_OFFLINE=1) — live render skipped";
|
|
|
|
describe("basic-unit-suite — LIVE reliability (key-gated)", () => {
|
|
it(
|
|
"cold render propagates to the count summary; a no-change re-wake memo-skips at zero model calls",
|
|
{ skip },
|
|
async () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "basic-unit-suite-live-"));
|
|
try {
|
|
const storage = createFileSystemStorageAdapter({ directory: dir });
|
|
const ledger = createFileSystemReceiptLedger({ storage });
|
|
|
|
const topology: ReconcilerTopology = {
|
|
topology: {
|
|
nodes: [
|
|
{
|
|
node: GATEWAY,
|
|
contract_fingerprint: "fp-gateway",
|
|
wake_source: "external",
|
|
},
|
|
{
|
|
node: COUNT_SUMMARY,
|
|
contract_fingerprint: "fp-summary",
|
|
wake_source: "input",
|
|
},
|
|
],
|
|
edges: [
|
|
{
|
|
subscriber: COUNT_SUMMARY,
|
|
producer: GATEWAY,
|
|
facet: COUNTS_FACET,
|
|
},
|
|
],
|
|
entry_points: [GATEWAY],
|
|
acyclic: true,
|
|
},
|
|
contract_fingerprints: {
|
|
[GATEWAY]: "fp-gateway",
|
|
[COUNT_SUMMARY]: "fp-summary",
|
|
},
|
|
};
|
|
|
|
const provider = createOpenRouterProvider();
|
|
const liveRender = (instructions: string) =>
|
|
createAgentRender({ provider, instructions });
|
|
|
|
const noop = (cause: "external" | "input") => () => ({
|
|
world_model: {},
|
|
cost: {
|
|
provider: "none",
|
|
model: "fake",
|
|
tokens: { fresh: 0, reused: 0 },
|
|
surprise_cause: cause,
|
|
},
|
|
});
|
|
|
|
const dag = mountDag({
|
|
topology,
|
|
mounts: {
|
|
[GATEWAY]: { render: noop("external") },
|
|
[COUNT_SUMMARY]: { render: noop("input") },
|
|
},
|
|
asyncMounts: {
|
|
[GATEWAY]: {
|
|
render: liveRender(
|
|
"You are the Counter Events gateway. Maintain a short deterministic `counts` truth summarizing one accepted event: total=1, by_kind={alpha:1}. Keep it terse.",
|
|
),
|
|
},
|
|
[COUNT_SUMMARY]: {
|
|
render: liveRender(
|
|
"You are the Count Summary responsibility. Read the upstream counts and maintain a one-line summary: total and whether it crossed a threshold of 3. Keep it short and deterministic.",
|
|
),
|
|
},
|
|
},
|
|
ledger,
|
|
});
|
|
|
|
const cold = await dag.ingestAsync(GATEWAY);
|
|
assert.ok(
|
|
cold.some((r) => r.node === GATEWAY && r.disposition === "rendered"),
|
|
"the gateway rendered cold",
|
|
);
|
|
assert.ok(
|
|
cold.some(
|
|
(r) => r.node === COUNT_SUMMARY && r.disposition === "rendered",
|
|
),
|
|
"the moved counts facet propagated to the count summary",
|
|
);
|
|
|
|
const freshAfterCold = createReplaySession({ ledger }).costRollup.total
|
|
.fresh;
|
|
|
|
const quiet = await dag.ingestAsync(GATEWAY);
|
|
assert.deepEqual(
|
|
quiet.map((r) => `${r.node}:${r.disposition}`),
|
|
[`${GATEWAY}:skipped`],
|
|
"a no-change re-wake memo-skips the gateway (no propagation, zero model calls)",
|
|
);
|
|
assert.equal(
|
|
createReplaySession({ ledger }).costRollup.total.fresh,
|
|
freshAfterCold,
|
|
"the quiet re-wake spent zero additional fresh — the memo key gates render, even live",
|
|
);
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
},
|
|
);
|
|
});
|