jev-layer 0.1.0 → 0.2.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +45 -34
  2. package/CONTRIBUTING.md +146 -146
  3. package/LICENSE +21 -21
  4. package/README.md +123 -126
  5. package/README.ru.md +138 -117
  6. package/README.zh-CN.md +137 -117
  7. package/RELEASE.md +53 -53
  8. package/SECURITY.md +56 -56
  9. package/bin/jev.mjs +213 -213
  10. package/config/codex.mcp.toml +8 -8
  11. package/config/generic-mcp.json +13 -13
  12. package/config/hermes.mcp.yaml +9 -9
  13. package/config/jev.example.json +22 -22
  14. package/config/omp.mcp.json +14 -14
  15. package/config/providers.env.example +13 -13
  16. package/docs/AGENT-IMPLEMENTATION.md +250 -0
  17. package/docs/COMPATIBILITY.md +15 -15
  18. package/docs/HERMES-LOCAL-AGENT-HANDOFF.ru.md +177 -0
  19. package/docs/PROVIDERS.md +67 -0
  20. package/docs/SCHEMA-VERSIONING.md +96 -94
  21. package/docs/architecture.svg +77 -0
  22. package/docs/jev-layer-banner.svg +38 -0
  23. package/examples/capabilities.json +39 -39
  24. package/examples/route-request.json +28 -28
  25. package/integrations/codex/.codex-plugin/plugin.json +19 -19
  26. package/integrations/codex/.mcp.json +11 -11
  27. package/integrations/codex/AGENTS.md +1 -1
  28. package/integrations/codex/run-mcp.mjs +9 -9
  29. package/integrations/codex/skills/jev-route/SKILL.md +17 -17
  30. package/integrations/hermes/__init__.py +150 -51
  31. package/integrations/hermes/plugin.yaml +10 -5
  32. package/integrations/hermes/schemas.py +89 -12
  33. package/integrations/omp/extension.js +105 -105
  34. package/integrations/template/README.md +10 -10
  35. package/integrations/template/adapter.mjs +87 -87
  36. package/package.json +60 -59
  37. package/scripts/benchmark.mjs +35 -35
  38. package/scripts/browser-benchmark.mjs +92 -92
  39. package/scripts/browser-e2e.mjs +117 -117
  40. package/scripts/capability-e2e.mjs +31 -31
  41. package/scripts/clean-install-smoke.mjs +162 -162
  42. package/scripts/codex-mcp-smoke.mjs +119 -119
  43. package/scripts/context-filter-e2e.mjs +45 -45
  44. package/scripts/fail-open-smoke.mjs +120 -120
  45. package/scripts/feature-flags-smoke.mjs +46 -46
  46. package/scripts/mcp-receipt-smoke.mjs +113 -98
  47. package/scripts/model-route-report.mjs +15 -0
  48. package/scripts/openrouter-choice.mjs +59 -59
  49. package/scripts/replay-eval.mjs +124 -124
  50. package/scripts/smoke.mjs +34 -34
  51. package/scripts/supervision-e2e.mjs +138 -109
  52. package/src/browser.mjs +571 -569
  53. package/src/cli.mjs +33 -33
  54. package/src/config.mjs +85 -75
  55. package/src/context-filter.mjs +56 -56
  56. package/src/contract.mjs +72 -72
  57. package/src/discovery.mjs +65 -65
  58. package/src/hermes-adapter.mjs +123 -0
  59. package/src/mcp-server.mjs +267 -210
  60. package/src/model-route-metrics.mjs +89 -0
  61. package/src/model-routing.mjs +87 -0
  62. package/src/providers/demo.mjs +52 -52
  63. package/src/providers/index.mjs +12 -0
  64. package/src/providers/typesafe.mjs +126 -126
  65. package/src/receipts.mjs +227 -226
  66. package/src/registry.mjs +109 -109
  67. package/src/relevance-filter.mjs +99 -99
  68. package/src/route.mjs +219 -221
  69. package/src/shadow-compaction.mjs +170 -0
  70. package/src/supervision.mjs +268 -244
  71. package/test/browser.test.mjs +199 -199
  72. package/test/capability.test.mjs +54 -54
  73. package/test/context-filter.test.mjs +65 -65
  74. package/test/hermes-adapter.test.mjs +62 -0
  75. package/test/model-route-metrics.test.mjs +61 -0
  76. package/test/model-routing.test.mjs +59 -0
  77. package/test/openrouter-provider.test.mjs +55 -55
  78. package/test/provider-transports.test.mjs +205 -0
  79. package/test/providers.test.mjs +126 -0
  80. package/test/receipts.test.mjs +71 -71
  81. package/test/route.test.mjs +74 -74
  82. package/test/shadow-compaction.test.mjs +67 -0
  83. package/test/supervision.test.mjs +117 -99
@@ -1,71 +1,71 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtemp, readFile } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import { test } from "node:test";
6
- import { appendExecutionReceipt, appendRoutingCase, readRoutingCases } from "../src/receipts.mjs";
7
- import { routeRequest } from "../src/route.mjs";
8
-
9
- const request = {
10
- harness: "receipt-test",
11
- intent: "inspect the repository",
12
- context: { workspace: "test" },
13
- actor_permissions: ["read"],
14
- capabilities: [{
15
- id: "read_repo",
16
- kind: "tool",
17
- name: "Read repository",
18
- description: "Read repository metadata without modifying files",
19
- permissions: ["read"],
20
- risk: "low",
21
- }],
22
- };
23
-
24
- test("execution receipt carries one correlation id through host completion", async () => {
25
- const provider = {
26
- name: "receipt-test-provider",
27
- async decide() {
28
- return {
29
- answers: {
30
- tool: {
31
- type: "choice",
32
- choice: "read_repo",
33
- probabilities: { read_repo: 1 },
34
- confidence: 1,
35
- },
36
- },
37
- usage: { cost: 0.000123 },
38
- };
39
- },
40
- };
41
- const decision = await routeRequest(request, { provider });
42
- const path = join(await mkdtemp(join(tmpdir(), "jev-receipts-")), "cases.jsonl");
43
- await appendRoutingCase({ path, request, decision });
44
- const completed = await appendExecutionReceipt({
45
- path,
46
- request,
47
- decision,
48
- host: {
49
- harness: "receipt-test",
50
- capability_id: "read_repo",
51
- status: "completed",
52
- result: { bytes: 42 },
53
- exit_status: 0,
54
- duration_ms: 12.5,
55
- },
56
- });
57
-
58
- assert.equal(decision.correlation_id, decision.receipt.correlation_id);
59
- assert.equal(decision.receipt.cost_usd, 0.000123);
60
- assert.equal(completed.record.correlation_id, decision.correlation_id);
61
- assert.equal(completed.record.jev.cost_usd, 0.000123);
62
- assert.equal(completed.record.host.exit_status, 0);
63
- assert.equal(completed.record.host.duration_ms, 12.5);
64
- assert.deepEqual(completed.record.host.result, { bytes: 42 });
65
-
66
- const routingCases = await readRoutingCases(path);
67
- assert.equal(routingCases.length, 1);
68
- assert.equal(routingCases[0].case_id, decision.correlation_id);
69
- const firstRecord = JSON.parse((await readFile(path, "utf8")).split(/\r?\n/)[0]);
70
- assert.equal(firstRecord.record_type, "routing_case");
71
- });
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, readFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { test } from "node:test";
6
+ import { appendExecutionReceipt, appendRoutingCase, readRoutingCases } from "../src/receipts.mjs";
7
+ import { routeRequest } from "../src/route.mjs";
8
+
9
+ const request = {
10
+ harness: "receipt-test",
11
+ intent: "inspect the repository",
12
+ context: { workspace: "test" },
13
+ actor_permissions: ["read"],
14
+ capabilities: [{
15
+ id: "read_repo",
16
+ kind: "tool",
17
+ name: "Read repository",
18
+ description: "Read repository metadata without modifying files",
19
+ permissions: ["read"],
20
+ risk: "low",
21
+ }],
22
+ };
23
+
24
+ test("execution receipt carries one correlation id through host completion", async () => {
25
+ const provider = {
26
+ name: "receipt-test-provider",
27
+ async decide() {
28
+ return {
29
+ answers: {
30
+ tool: {
31
+ type: "choice",
32
+ choice: "read_repo",
33
+ probabilities: { read_repo: 1 },
34
+ confidence: 1,
35
+ },
36
+ },
37
+ usage: { cost: 0.000123 },
38
+ };
39
+ },
40
+ };
41
+ const decision = await routeRequest(request, { provider });
42
+ const path = join(await mkdtemp(join(tmpdir(), "jev-receipts-")), "cases.jsonl");
43
+ await appendRoutingCase({ path, request, decision });
44
+ const completed = await appendExecutionReceipt({
45
+ path,
46
+ request,
47
+ decision,
48
+ host: {
49
+ harness: "receipt-test",
50
+ capability_id: "read_repo",
51
+ status: "completed",
52
+ result: { bytes: 42 },
53
+ exit_status: 0,
54
+ duration_ms: 12.5,
55
+ },
56
+ });
57
+
58
+ assert.equal(decision.correlation_id, decision.receipt.correlation_id);
59
+ assert.equal(decision.receipt.cost_usd, 0.000123);
60
+ assert.equal(completed.record.correlation_id, decision.correlation_id);
61
+ assert.equal(completed.record.jev.cost_usd, 0.000123);
62
+ assert.equal(completed.record.host.exit_status, 0);
63
+ assert.equal(completed.record.host.duration_ms, 12.5);
64
+ assert.deepEqual(completed.record.host.result, { bytes: 42 });
65
+
66
+ const routingCases = await readRoutingCases(path);
67
+ assert.equal(routingCases.length, 1);
68
+ assert.equal(routingCases[0].case_id, decision.correlation_id);
69
+ const firstRecord = JSON.parse((await readFile(path, "utf8")).split(/\r?\n/)[0]);
70
+ assert.equal(firstRecord.record_type, "routing_case");
71
+ });
@@ -1,74 +1,74 @@
1
- import assert from "node:assert/strict";
2
- import { test } from "node:test";
3
- import { projectState } from "../src/context-filter.mjs";
4
- import { routeRequest } from "../src/route.mjs";
5
-
6
- const candidates = [
7
- { id: "search", kind: "mcp", name: "Repository search", description: "Search repository files and symbols", permissions: ["read"], risk: "low" },
8
- { id: "write", kind: "tool", name: "File writer", description: "Modify repository files", permissions: ["write"], risk: "high" },
9
- ];
10
-
11
- function request(overrides = {}) {
12
- return {
13
- harness: "test",
14
- intent: "Search repository files",
15
- context: { workspace: "test", api_key: "hidden" },
16
- actor_permissions: ["read"],
17
- capabilities: candidates,
18
- ...overrides,
19
- };
20
- }
21
-
22
- test("routes to an eligible capability without executing it", async () => {
23
- const decision = await routeRequest(request(), { provider: "demo" });
24
- assert.equal(decision.status, "selected");
25
- assert.equal(decision.selected, "search");
26
- assert.deepEqual(decision.execution, { enabled: false, status: "not_started" });
27
- assert.equal(decision.candidates.find((candidate) => candidate.id === "write").filtered, true);
28
- });
29
-
30
- test("returns no_decision when every candidate is unavailable or forbidden", async () => {
31
- const decision = await routeRequest(request({ capabilities: [
32
- { ...candidates[0], availability: { available: false, reason: "offline" } },
33
- candidates[1],
34
- ] }), { provider: "demo" });
35
- assert.equal(decision.status, "no_decision");
36
- assert.equal(decision.execution.enabled, false);
37
- });
38
-
39
- test("fails open on provider errors and malformed responses", async () => {
40
- const providerError = await routeRequest(request(), { provider: { name: "test-error", async decide() { throw new Error("down"); } } });
41
- assert.equal(providerError.status, "fallback");
42
- assert.equal(providerError.fallback.type, "provider_error");
43
-
44
- const malformed = await routeRequest(request(), { provider: { name: "test-malformed", async decide() { return { answers: { tool: { type: "score" } } }; } } });
45
- assert.equal(malformed.status, "fallback");
46
- assert.equal(malformed.fallback.type, "malformed_response");
47
- });
48
-
49
- test("falls back when confidence is below the code threshold", async () => {
50
- const decision = await routeRequest(request({ intent: "Do something unrelated", actor_permissions: ["read", "write"] }), { provider: "demo", policy: { min_confidence: 0.99 } });
51
- assert.equal(decision.status, "fallback");
52
- assert.equal(decision.fallback.type, "low_confidence");
53
- });
54
-
55
- test("filters secrets and bounds provider state", () => {
56
- const projection = projectState(request({ context: { api_key: "hidden", nested: { password: "hidden-too" }, note: "safe" } }), candidates, 1_000);
57
- const serialized = JSON.stringify(projection.state);
58
- assert.equal(serialized.includes("hidden"), false);
59
- assert.equal(serialized.includes("safe"), true);
60
- assert.ok(projection.context_bytes <= 1_000);
61
- });
62
-
63
- test("honors explicit disablement with fail-open fallback", async () => {
64
- const previous = process.env.JEV_LAYER_ENABLED;
65
- process.env.JEV_LAYER_ENABLED = "0";
66
- try {
67
- const decision = await routeRequest(request(), { provider: "demo" });
68
- assert.equal(decision.status, "fallback");
69
- assert.equal(decision.fallback.type, "disabled");
70
- } finally {
71
- if (previous === undefined) delete process.env.JEV_LAYER_ENABLED;
72
- else process.env.JEV_LAYER_ENABLED = previous;
73
- }
74
- });
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { projectState } from "../src/context-filter.mjs";
4
+ import { routeRequest } from "../src/route.mjs";
5
+
6
+ const candidates = [
7
+ { id: "search", kind: "mcp", name: "Repository search", description: "Search repository files and symbols", permissions: ["read"], risk: "low" },
8
+ { id: "write", kind: "tool", name: "File writer", description: "Modify repository files", permissions: ["write"], risk: "high" },
9
+ ];
10
+
11
+ function request(overrides = {}) {
12
+ return {
13
+ harness: "test",
14
+ intent: "Search repository files",
15
+ context: { workspace: "test", api_key: "hidden" },
16
+ actor_permissions: ["read"],
17
+ capabilities: candidates,
18
+ ...overrides,
19
+ };
20
+ }
21
+
22
+ test("routes to an eligible capability without executing it", async () => {
23
+ const decision = await routeRequest(request(), { provider: "demo" });
24
+ assert.equal(decision.status, "selected");
25
+ assert.equal(decision.selected, "search");
26
+ assert.deepEqual(decision.execution, { enabled: false, status: "not_started" });
27
+ assert.equal(decision.candidates.find((candidate) => candidate.id === "write").filtered, true);
28
+ });
29
+
30
+ test("returns no_decision when every candidate is unavailable or forbidden", async () => {
31
+ const decision = await routeRequest(request({ capabilities: [
32
+ { ...candidates[0], availability: { available: false, reason: "offline" } },
33
+ candidates[1],
34
+ ] }), { provider: "demo" });
35
+ assert.equal(decision.status, "no_decision");
36
+ assert.equal(decision.execution.enabled, false);
37
+ });
38
+
39
+ test("fails open on provider errors and malformed responses", async () => {
40
+ const providerError = await routeRequest(request(), { provider: { name: "test-error", async decide() { throw new Error("down"); } } });
41
+ assert.equal(providerError.status, "fallback");
42
+ assert.equal(providerError.fallback.type, "provider_error");
43
+
44
+ const malformed = await routeRequest(request(), { provider: { name: "test-malformed", async decide() { return { answers: { tool: { type: "score" } } }; } } });
45
+ assert.equal(malformed.status, "fallback");
46
+ assert.equal(malformed.fallback.type, "malformed_response");
47
+ });
48
+
49
+ test("falls back when confidence is below the code threshold", async () => {
50
+ const decision = await routeRequest(request({ intent: "Do something unrelated", actor_permissions: ["read", "write"] }), { provider: "demo", policy: { min_confidence: 0.99 } });
51
+ assert.equal(decision.status, "fallback");
52
+ assert.equal(decision.fallback.type, "low_confidence");
53
+ });
54
+
55
+ test("filters secrets and bounds provider state", () => {
56
+ const projection = projectState(request({ context: { api_key: "hidden", nested: { password: "hidden-too" }, note: "safe" } }), candidates, 1_000);
57
+ const serialized = JSON.stringify(projection.state);
58
+ assert.equal(serialized.includes("hidden"), false);
59
+ assert.equal(serialized.includes("safe"), true);
60
+ assert.ok(projection.context_bytes <= 1_000);
61
+ });
62
+
63
+ test("honors explicit disablement with fail-open fallback", async () => {
64
+ const previous = process.env.JEV_LAYER_ENABLED;
65
+ process.env.JEV_LAYER_ENABLED = "0";
66
+ try {
67
+ const decision = await routeRequest(request(), { provider: "demo" });
68
+ assert.equal(decision.status, "fallback");
69
+ assert.equal(decision.fallback.type, "disabled");
70
+ } finally {
71
+ if (previous === undefined) delete process.env.JEV_LAYER_ENABLED;
72
+ else process.env.JEV_LAYER_ENABLED = previous;
73
+ }
74
+ });
@@ -0,0 +1,67 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { buildShadowCompactionReport } from "../src/shadow-compaction.mjs";
4
+
5
+ const context = {
6
+ messages: [
7
+ "stale casual discussion",
8
+ "Requirement: preserve /workspace/project/config.json exactly",
9
+ "recent working note",
10
+ ],
11
+ tool_results: [
12
+ "plain old tool output",
13
+ ],
14
+ };
15
+
16
+ test("shadow compaction is report-only, preserves pinned evidence, and exposes candidate decisions", async () => {
17
+ const before = JSON.stringify(context);
18
+ let received;
19
+ const report = await buildShadowCompactionReport({
20
+ intent: "prepare a safe compaction report",
21
+ context,
22
+ provider: {
23
+ name: "shadow-test-provider",
24
+ async evaluate(input) {
25
+ received = input;
26
+ return {
27
+ answers: {
28
+ item_0: { type: "noul", probability: 0.1, confidence: 0.9 },
29
+ item_1: { type: "noul", probability: 0.9, confidence: 0.9 },
30
+ item_2: { type: "noul", probability: 0.4, confidence: 0.2 },
31
+ },
32
+ usage: { cost: 0.0002 },
33
+ };
34
+ },
35
+ },
36
+ });
37
+
38
+ assert.equal(JSON.stringify(context), before);
39
+ assert.equal(report.mode, "jev_shadow");
40
+ assert.equal(report.status, "judged");
41
+ assert.equal(report.changed, false);
42
+ assert.equal(report.candidate_count, 4);
43
+ assert.equal(report.protected_count, 1);
44
+ assert.equal(report.droppable_count, 1);
45
+ assert.equal(report.retained_count, 3);
46
+ assert.ok(received.questions.item_0);
47
+ assert.equal(report.items.find((item) => item.id === "messages[1]").decision, "keep");
48
+ assert.equal(report.items.find((item) => item.id === "messages[1]").reason, "pinned:path,requirement");
49
+ assert.equal(report.items.find((item) => item.id === "messages[0]").decision, "drop_candidate");
50
+ assert.equal(report.items.find((item) => item.id === "tool_results[0]").decision, "keep");
51
+ assert.equal(report.items.find((item) => item.id === "tool_results[0]").reason, "low_confidence");
52
+ });
53
+
54
+ test("shadow compaction fails open and marks all unresolved items as retained", async () => {
55
+ const report = await buildShadowCompactionReport({
56
+ intent: "prepare a safe compaction report",
57
+ context: { messages: ["old note"] },
58
+ provider: { name: "offline", async evaluate() { throw new Error("provider unavailable"); } },
59
+ });
60
+
61
+ assert.equal(report.status, "fallback");
62
+ assert.equal(report.changed, false);
63
+ assert.equal(report.droppable_count, 0);
64
+ assert.equal(report.retained_count, 1);
65
+ assert.equal(report.items[0].decision, "keep");
66
+ assert.equal(report.items[0].reason, "provider_error");
67
+ });
@@ -1,99 +1,117 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtemp, readFile } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import { test } from "node:test";
6
- import {
7
- deterministicSupervisionPolicy,
8
- normalizeAssessment,
9
- superviseWork,
10
- supervisionQuestions,
11
- } from "../src/supervision.mjs";
12
- import { readSupervisionCases } from "../src/receipts.mjs";
13
-
14
- function assessment(overrides = {}) {
15
- return {
16
- requirements_addressed: 0.8,
17
- verification_needed: 0.1,
18
- meaningful_progress: 0.8,
19
- worker_stuck: 0.1,
20
- work_off_track: 0.1,
21
- completion: 0.8,
22
- ...overrides,
23
- };
24
- }
25
-
26
- test("supervision contract exposes bounded dimensions and parses Noul answers", () => {
27
- assert.deepEqual(Object.keys(supervisionQuestions()), [
28
- "requirements_addressed",
29
- "verification_needed",
30
- "meaningful_progress",
31
- "worker_stuck",
32
- "work_off_track",
33
- "completion",
34
- ]);
35
- const parsed = normalizeAssessment({
36
- answers: {
37
- requirements_addressed: { type: "noul", probability: 0.8 },
38
- verification_needed: { type: "noul", p_true: 0.2 },
39
- meaningful_progress: { type: "noul", score: 0.7 },
40
- worker_stuck: true,
41
- work_off_track: 0.1,
42
- completion: { type: "noul", noul: 0.9 },
43
- },
44
- });
45
- assert.deepEqual(parsed, {
46
- requirements_addressed: 0.8,
47
- verification_needed: 0.2,
48
- meaningful_progress: 0.7,
49
- worker_stuck: 1,
50
- work_off_track: 0.1,
51
- completion: 0.9,
52
- });
53
- });
54
-
55
- test("deterministic host policy owns the supervision action", () => {
56
- assert.equal(deterministicSupervisionPolicy({ assessment: assessment({ verification_needed: 0.9 }) }).action, "verify");
57
- assert.equal(deterministicSupervisionPolicy({ assessment: assessment({ worker_stuck: 0.9 }), attempts: 0 }).action, "retry");
58
- assert.equal(deterministicSupervisionPolicy({ assessment: assessment({ worker_stuck: 0.9 }), attempts: 2 }).action, "escalate");
59
- assert.equal(deterministicSupervisionPolicy({ assessment: assessment(), evidence: { tests_passed: true } }).action, "finish");
60
- });
61
-
62
- test("supervision is opt-in, fail-open, and records replayable judgments", async () => {
63
- const disabled = await superviseWork({ enabled: false, provider: { async evaluate() { throw new Error("must not call"); } } });
64
- assert.equal(disabled.status, "fallback");
65
- assert.equal(disabled.action, "continue");
66
- assert.equal(disabled.fallback.type, "disabled");
67
-
68
- const directory = await mkdtemp(join(tmpdir(), "jev-supervision-test-"));
69
- const path = join(directory, "cases.jsonl");
70
- let calls = 0;
71
- const result = await superviseWork({
72
- enabled: true,
73
- harness: "supervision-test",
74
- job: { requirements: ["run tests"] },
75
- observation: { status: "ready" },
76
- evidence: { tests_passed: true },
77
- provider: {
78
- name: "injected-supervision",
79
- async evaluate({ questions }) {
80
- calls += 1;
81
- return {
82
- answers: Object.fromEntries(Object.keys(questions).map((key) => [key, { type: "noul", probability: assessment()[key] }])),
83
- usage: { cost: 0.0003 },
84
- };
85
- },
86
- },
87
- receiptPath: path,
88
- });
89
- assert.equal(calls, 1);
90
- assert.equal(result.status, "judged");
91
- assert.equal(result.action, "finish");
92
- assert.equal(result.metrics.jev_calls, 1);
93
- assert.equal(result.metrics.cost_usd, 0.0003);
94
- const cases = await readSupervisionCases(path);
95
- assert.equal(cases.length, 1);
96
- assert.equal(cases[0].record_type, "supervision_case");
97
- assert.equal(cases[0].supervision.action, "finish");
98
- assert.equal(JSON.parse(await readFile(path, "utf8")).record_type, "supervision_case");
99
- });
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, readFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { test } from "node:test";
6
+ import {
7
+ deterministicEvidenceState,
8
+ deterministicSupervisionPolicy,
9
+ normalizeAssessment,
10
+ superviseWork,
11
+ supervisionQuestions,
12
+ } from "../src/supervision.mjs";
13
+ import { readSupervisionCases } from "../src/receipts.mjs";
14
+
15
+ function assessment(overrides = {}) {
16
+ return {
17
+ requirements_addressed: 0.8,
18
+ verification_needed: 0.1,
19
+ meaningful_progress: 0.8,
20
+ worker_stuck: 0.1,
21
+ work_off_track: 0.1,
22
+ completion: 0.8,
23
+ ...overrides,
24
+ };
25
+ }
26
+
27
+ test("supervision contract exposes bounded dimensions and parses Noul answers", () => {
28
+ assert.deepEqual(Object.keys(supervisionQuestions()), [
29
+ "requirements_addressed",
30
+ "verification_needed",
31
+ "meaningful_progress",
32
+ "worker_stuck",
33
+ "work_off_track",
34
+ "completion",
35
+ ]);
36
+ const parsed = normalizeAssessment({
37
+ answers: {
38
+ requirements_addressed: { type: "noul", probability: 0.8 },
39
+ verification_needed: { type: "noul", p_true: 0.2 },
40
+ meaningful_progress: { type: "noul", score: 0.7 },
41
+ worker_stuck: true,
42
+ work_off_track: 0.1,
43
+ completion: { type: "noul", noul: 0.9 },
44
+ },
45
+ });
46
+ assert.deepEqual(parsed, {
47
+ requirements_addressed: 0.8,
48
+ verification_needed: 0.2,
49
+ meaningful_progress: 0.7,
50
+ worker_stuck: 1,
51
+ work_off_track: 0.1,
52
+ completion: 0.9,
53
+ });
54
+ });
55
+
56
+ test("deterministic evidence state distinguishes proof, absence, and contradiction", () => {
57
+ assert.deepEqual(deterministicEvidenceState({}), { state: "missing", sources: [] });
58
+ assert.deepEqual(deterministicEvidenceState({ tests_passed: true, receipts: [{ id: "test:1", status: "completed" }] }), {
59
+ state: "present",
60
+ sources: ["tests_passed", "receipt:test:1"],
61
+ });
62
+ assert.deepEqual(deterministicEvidenceState({ tests_passed: true, tests_failed: true }), {
63
+ state: "contradictory",
64
+ sources: ["tests_passed", "tests_failed"],
65
+ });
66
+ });
67
+
68
+ test("deterministic host policy owns the supervision action", () => {
69
+ assert.equal(deterministicSupervisionPolicy({ assessment: assessment({ verification_needed: 0.9 }) }).action, "verify");
70
+ assert.equal(deterministicSupervisionPolicy({ assessment: assessment({ worker_stuck: 0.9 }), attempts: 0 }).action, "retry");
71
+ assert.equal(deterministicSupervisionPolicy({ assessment: assessment({ worker_stuck: 0.9 }), attempts: 2 }).action, "escalate");
72
+ assert.equal(deterministicSupervisionPolicy({ assessment: assessment(), evidence: { tests_passed: true } }).action, "finish");
73
+ const contradiction = deterministicSupervisionPolicy({ assessment: assessment(), evidence: { tests_passed: true, tests_failed: true } });
74
+ assert.equal(contradiction.action, "verify");
75
+ assert.match(contradiction.reason, /contradictory/);
76
+ });
77
+
78
+ test("supervision is opt-in, fail-open, and records replayable judgments", async () => {
79
+ const disabled = await superviseWork({ enabled: false, provider: { async evaluate() { throw new Error("must not call"); } } });
80
+ assert.equal(disabled.status, "fallback");
81
+ assert.equal(disabled.action, "continue");
82
+ assert.equal(disabled.fallback.type, "disabled");
83
+
84
+ const directory = await mkdtemp(join(tmpdir(), "jev-supervision-test-"));
85
+ const path = join(directory, "cases.jsonl");
86
+ let calls = 0;
87
+ const result = await superviseWork({
88
+ enabled: true,
89
+ harness: "supervision-test",
90
+ job: { requirements: ["run tests"] },
91
+ observation: { status: "ready" },
92
+ evidence: { tests_passed: true },
93
+ provider: {
94
+ name: "injected-supervision",
95
+ async evaluate({ questions }) {
96
+ calls += 1;
97
+ return {
98
+ answers: Object.fromEntries(Object.keys(questions).map((key) => [key, { type: "noul", probability: assessment()[key] }])),
99
+ usage: { cost: 0.0003 },
100
+ };
101
+ },
102
+ },
103
+ receiptPath: path,
104
+ });
105
+ assert.equal(calls, 1);
106
+ assert.equal(result.status, "judged");
107
+ assert.equal(result.action, "finish");
108
+ assert.equal(result.metrics.jev_calls, 1);
109
+ assert.equal(result.metrics.cost_usd, 0.0003);
110
+ assert.deepEqual(result.evidence_state, { state: "present", sources: ["tests_passed"] });
111
+ const cases = await readSupervisionCases(path);
112
+ assert.equal(cases.length, 1);
113
+ assert.equal(cases[0].record_type, "supervision_case");
114
+ assert.equal(cases[0].supervision.action, "finish");
115
+ assert.deepEqual(cases[0].supervision.evidence_state, { state: "present", sources: ["tests_passed"] });
116
+ assert.equal(JSON.parse(await readFile(path, "utf8")).record_type, "supervision_case");
117
+ });