@looprun-ai/core 0.2.1 → 0.4.0

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.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The declarative PROOF format + the framework-free runners for the @looprun-ai/core testing kit.
3
+ *
4
+ * A {@link GuardProof} states, for ONE guard kind, the L1 (deterministic check in isolation) and optional
5
+ * L3 (full loop) obligations across positive/negative/neutral cases. This file holds only the pieces that
6
+ * need NO framework backend: the proof TYPES, the collective non-interference whitelist, the L1 runner,
7
+ * and the spec builders (isolated / collective) over the fixture surface. The full-loop runners
8
+ * (runProofLoop / expectedSignal / pickRecord / assertSignal) live in the backend package, since they
9
+ * drive a real conversation.
10
+ */
11
+ import { AgentSpecBase } from '../spec.js';
12
+ import type { AgentSpecConfig, Hook, ToolTarget } from '../spec.js';
13
+ import type { Guard, GuardCtx } from '../rules.js';
14
+ import type { FixturePreset } from './fixture-world.js';
15
+ export type ProofPolarity = 'positive' | 'negative' | 'neutral';
16
+ export type ProofExpect = 'veto' | 'redrive' | 'refusal' | 'pass';
17
+ export type PartialGuardCtx = Partial<GuardCtx>;
18
+ /** One conversation turn's input (structural — the backend's TurnInput shape). */
19
+ type ProofTurn = {
20
+ userText: string;
21
+ attachments?: string[];
22
+ };
23
+ /** One scripted LLM response (structural — the fake model's ScriptStep shape). */
24
+ type ScriptStep = Array<{
25
+ tool: string;
26
+ args: Record<string, unknown>;
27
+ } | {
28
+ text: string;
29
+ }>;
30
+ export interface ProofLoopCase {
31
+ preset: FixturePreset;
32
+ turns: ProofTurn[];
33
+ script: ScriptStep[];
34
+ expect: ProofExpect;
35
+ tool?: string;
36
+ /** Which turnRecord to assert on (default: the last). */
37
+ turn?: number;
38
+ /** Guard kinds that LEGITIMATELY co-fire on this scenario in the COLLECTIVE run (two guards genuinely
39
+ * binding the same violation — e.g. a destructive claim that is also a pending, un-relayed
40
+ * confirmation). Added to that case's non-interference whitelist; keep it minimal and justified. */
41
+ alsoFires?: string[];
42
+ }
43
+ export interface ProofCase {
44
+ name: string;
45
+ polarity: ProofPolarity;
46
+ ctx?: PartialGuardCtx;
47
+ l1: 'fires' | 'silent';
48
+ l3?: ProofLoopCase;
49
+ }
50
+ export interface GuardProof {
51
+ /** MUST equal the guards.ts kind (the ratchet key). */
52
+ guard: string;
53
+ /** Instantiate the guard. Always required in practice (the runners throw without it); `auto` only
54
+ * controls whether buildIsolatedSpec/buildCollectiveSpec ADD it (an auto-installed kind is already on
55
+ * the spec via AgentSpecBase). */
56
+ make?: () => Guard;
57
+ hook: Hook;
58
+ target: ToolTarget;
59
+ /** Rely on AgentSpecBase auto-install (minimal/base layer) instead of addGuard. */
60
+ auto?: 'minimal' | 'base';
61
+ /** Spec config to merge (e.g. destructiveTools / confirmMechanism / lexicon for auto kinds). */
62
+ specTweaks?: Partial<AgentSpecConfig>;
63
+ /** 'skip' excludes this guard from the collective super-agent. Reserved for content-contract reply
64
+ * guards (replyMustMention, replySingleQuestion, replyConfirmsLabels, replyMaxOccurrences): an author
65
+ * binds those to ONE agent's specific reply contract — installing them agent-wide over arbitrary
66
+ * scenarios would fire on every unrelated reply by construction, which is a category error, not an
67
+ * interference finding. They are still fully proven isolated (L1 + L3). */
68
+ collective?: 'include' | 'skip';
69
+ cases: ProofCase[];
70
+ }
71
+ /** The collective non-interference whitelist — the kinds AgentSpecBase auto-installs. */
72
+ export declare const AUTO_LAYER_KINDS: readonly ["noDuplicateCall", "degenerationGuard", "emptyReply", "noFalseFailureClaim", "confirmFirst", "destructiveThrottle"];
73
+ /** Fill a GuardCtx from a partial: empty args, a fresh FixtureWorld('seeded-media') unless a world is
74
+ * given, empty observed, turnIndex 0. */
75
+ export declare function craftCtx(partial?: PartialGuardCtx): GuardCtx;
76
+ /** Instantiate a proof's guard, or throw when `make` is absent (the runners require it). */
77
+ export declare function requireMake(proof: GuardProof): () => Guard;
78
+ /** L1 — run the guard's deterministic check in isolation against a crafted ctx. */
79
+ export declare function runL1(proof: GuardProof, c: ProofCase): Promise<{
80
+ fired: boolean;
81
+ reason: string | null;
82
+ }>;
83
+ /** Build a spec that isolates ONE proof's guard over the fixture surface. */
84
+ export declare function buildIsolatedSpec(proof: GuardProof): AgentSpecBase;
85
+ /** Build ONE spec with EVERY non-auto proof guard installed — the collective non-interference harness.
86
+ * Auto kinds ride AgentSpecBase (destructiveTools + confirmMechanism + lexicon below). Duplicate kinds
87
+ * at different targets are fine; ids are made unique by suffixing `#2`, `#3`, … */
88
+ export declare function buildCollectiveSpec(proofs: GuardProof[]): AgentSpecBase;
89
+ export {};
90
+ //# sourceMappingURL=proof.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../src/testing/proof.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAChE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAClE,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEhD,kFAAkF;AAClF,KAAK,SAAS,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAC9D,kFAAkF;AAClF,KAAK,UAAU,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE5F,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;yGAEqG;IACrG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,aAAa,CAAC;IACxB,GAAG,CAAC,EAAE,eAAe,CAAC;IACtB,EAAE,EAAE,OAAO,GAAG,QAAQ,CAAC;IACvB,EAAE,CAAC,EAAE,aAAa,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;IACd;;uCAEmC;IACnC,IAAI,CAAC,EAAE,MAAM,KAAK,CAAC;IACnB,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,UAAU,CAAC;IACnB,mFAAmF;IACnF,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,gGAAgG;IAChG,UAAU,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACtC;;;;gFAI4E;IAC5E,UAAU,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAChC,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAED,yFAAyF;AACzF,eAAO,MAAM,gBAAgB,+HAOnB,CAAC;AAEX;0CAC0C;AAC1C,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,QAAQ,CAQhE;AAED,4FAA4F;AAC5F,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,KAAK,CAG1D;AAED,mFAAmF;AACnF,wBAAsB,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAI/G;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAclE;AAED;;oFAEoF;AACpF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,aAAa,CA2BvE"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The declarative PROOF format + the framework-free runners for the @looprun-ai/core testing kit.
3
+ *
4
+ * A {@link GuardProof} states, for ONE guard kind, the L1 (deterministic check in isolation) and optional
5
+ * L3 (full loop) obligations across positive/negative/neutral cases. This file holds only the pieces that
6
+ * need NO framework backend: the proof TYPES, the collective non-interference whitelist, the L1 runner,
7
+ * and the spec builders (isolated / collective) over the fixture surface. The full-loop runners
8
+ * (runProofLoop / expectedSignal / pickRecord / assertSignal) live in the backend package, since they
9
+ * drive a real conversation.
10
+ */
11
+ import { AgentSpecBase } from '../spec.js';
12
+ import { FixtureWorld, FIXTURE_TOOL_NAMES, FIXTURE_THEME, FIXTURE_LEXICON } from './fixture-world.js';
13
+ /** The collective non-interference whitelist — the kinds AgentSpecBase auto-installs. */
14
+ export const AUTO_LAYER_KINDS = [
15
+ 'noDuplicateCall',
16
+ 'degenerationGuard',
17
+ 'emptyReply',
18
+ 'noFalseFailureClaim',
19
+ 'confirmFirst',
20
+ 'destructiveThrottle',
21
+ ];
22
+ /** Fill a GuardCtx from a partial: empty args, a fresh FixtureWorld('seeded-media') unless a world is
23
+ * given, empty observed, turnIndex 0. */
24
+ export function craftCtx(partial = {}) {
25
+ return {
26
+ args: {},
27
+ world: new FixtureWorld('seeded-media'),
28
+ observed: [],
29
+ turnIndex: 0,
30
+ ...partial,
31
+ };
32
+ }
33
+ /** Instantiate a proof's guard, or throw when `make` is absent (the runners require it). */
34
+ export function requireMake(proof) {
35
+ if (!proof.make)
36
+ throw new Error(`GuardProof "${proof.guard}": make() is required to instantiate the guard.`);
37
+ return proof.make;
38
+ }
39
+ /** L1 — run the guard's deterministic check in isolation against a crafted ctx. */
40
+ export async function runL1(proof, c) {
41
+ const guard = requireMake(proof)();
42
+ const reason = await guard.check(craftCtx(c.ctx));
43
+ return { fired: reason != null, reason: reason ?? null };
44
+ }
45
+ /** Build a spec that isolates ONE proof's guard over the fixture surface. */
46
+ export function buildIsolatedSpec(proof) {
47
+ const cfg = {
48
+ id: `proof-${proof.guard}`,
49
+ mode: 'PROOF',
50
+ persona: 'You are the proof agent.',
51
+ tools: [...FIXTURE_TOOL_NAMES],
52
+ theme: FIXTURE_THEME,
53
+ ...(proof.specTweaks ?? {}),
54
+ };
55
+ const spec = new AgentSpecBase(cfg);
56
+ if (!proof.auto) {
57
+ spec.addGuard(proof.hook, proof.target, requireMake(proof)(), { id: `agent:${proof.guard}` });
58
+ }
59
+ return spec;
60
+ }
61
+ /** Build ONE spec with EVERY non-auto proof guard installed — the collective non-interference harness.
62
+ * Auto kinds ride AgentSpecBase (destructiveTools + confirmMechanism + lexicon below). Duplicate kinds
63
+ * at different targets are fine; ids are made unique by suffixing `#2`, `#3`, … */
64
+ export function buildCollectiveSpec(proofs) {
65
+ const spec = new AgentSpecBase({
66
+ id: 'proof-collective',
67
+ mode: 'PROOF',
68
+ persona: 'You are the proof agent.',
69
+ tools: [...FIXTURE_TOOL_NAMES],
70
+ theme: FIXTURE_THEME,
71
+ destructiveTools: ['deleteItem', 'purgeAll'],
72
+ confirmMechanism: { purgeAll: 'prior-ask' },
73
+ lexicon: {
74
+ falseFailureClaimRe: FIXTURE_LEXICON.falseFailureClaimRe,
75
+ confirmAskRe: FIXTURE_LEXICON.confirmAskRe,
76
+ },
77
+ });
78
+ const used = new Set();
79
+ for (const proof of proofs) {
80
+ if (proof.auto || proof.collective === 'skip')
81
+ continue;
82
+ let id = `agent:${proof.guard}`;
83
+ let n = 1;
84
+ while (used.has(id)) {
85
+ n += 1;
86
+ id = `agent:${proof.guard}#${n}`;
87
+ }
88
+ used.add(id);
89
+ spec.addGuard(proof.hook, proof.target, requireMake(proof)(), { id });
90
+ }
91
+ return spec;
92
+ }
93
+ //# sourceMappingURL=proof.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proof.js","sourceRoot":"","sources":["../../src/testing/proof.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@looprun-ai/core",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "looprun core: the AgentSpec governance layer for LLM agents — typed deterministic guards (prose+check pairing), the byte-stable scoped trunk renderer, and the backend-agnostic governed-turn machine. Framework-free: zero runtime dependencies; backends (@looprun-ai/mastra, …) supply the loop.",
6
6
  "keywords": [
@@ -28,6 +28,10 @@
28
28
  ".": {
29
29
  "types": "./dist/index.d.ts",
30
30
  "default": "./dist/index.js"
31
+ },
32
+ "./testing": {
33
+ "types": "./dist/testing/index.d.ts",
34
+ "default": "./dist/testing/index.js"
31
35
  }
32
36
  },
33
37
  "files": [
@@ -44,6 +48,7 @@
44
48
  "scripts": {
45
49
  "build": "tsc -p tsconfig.build.json",
46
50
  "typecheck": "tsc --noEmit",
47
- "test": "vitest run"
51
+ "test": "vitest run",
52
+ "test:proofs": "vitest run test/proofs --passWithNoTests"
48
53
  }
49
54
  }