eval-quality 0.1.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 (230) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +355 -0
  3. package/corpus/dev/README.md +36 -0
  4. package/corpus/dev/compile-seal-example/brief.json +1 -0
  5. package/corpus/dev/compile-seal-example/contract.json +1 -0
  6. package/corpus/dev/contracts/absent-collection-locations.json +1 -0
  7. package/corpus/dev/contracts/absent-sibling-groups.json +1 -0
  8. package/corpus/dev/contracts/absent-success-indicator.json +1 -0
  9. package/corpus/dev/contracts/empty-channel-roles.json +1 -0
  10. package/corpus/dev/contracts/empty-collection-locations.json +1 -0
  11. package/corpus/dev/contracts/empty-request-shapes.json +1 -0
  12. package/corpus/dev/contracts/empty-sibling-groups.json +1 -0
  13. package/corpus/dev/contracts/no-collection-quantifier.json +1 -0
  14. package/corpus/dev/contracts/no-operation-inventory.json +1 -0
  15. package/corpus/dev/contracts/no-read-back-relation.json +1 -0
  16. package/corpus/dev/contracts/no-state-change-marker.json +1 -0
  17. package/corpus/dev/contracts/no-type-violating-step.json +1 -0
  18. package/corpus/dev/contracts/per-key-split-oracles.json +1 -0
  19. package/corpus/dev/contracts/satisfied-declarations.json +1 -0
  20. package/corpus/dev/contracts/single-required-response-key.json +1 -0
  21. package/corpus/dev/contracts/split-indicator-oracle.json +1 -0
  22. package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -0
  23. package/corpus/dev/contracts/unnamed-reference-set.json +1 -0
  24. package/corpus/dev/contracts/wrong-cardinality-form.json +1 -0
  25. package/corpus/dev/index.json +1 -0
  26. package/dist/adapters/index.d.ts +12 -0
  27. package/dist/adapters/index.js +3 -0
  28. package/dist/adapters/local-corpus-adapter.d.ts +7 -0
  29. package/dist/adapters/local-corpus-adapter.js +78 -0
  30. package/dist/adapters/node-file-system-adapter.d.ts +7 -0
  31. package/dist/adapters/node-file-system-adapter.js +39 -0
  32. package/dist/adapters/port-boundary.d.ts +18 -0
  33. package/dist/adapters/port-boundary.js +83 -0
  34. package/dist/adapters/system-clock-adapter.d.ts +10 -0
  35. package/dist/adapters/system-clock-adapter.js +18 -0
  36. package/dist/application/compile.d.ts +4 -0
  37. package/dist/application/compile.js +24 -0
  38. package/dist/application/diagnostics.d.ts +22 -0
  39. package/dist/application/diagnostics.js +18 -0
  40. package/dist/application/index.d.ts +23 -0
  41. package/dist/application/index.js +17 -0
  42. package/dist/application/invoke-port.d.ts +2 -0
  43. package/dist/application/invoke-port.js +45 -0
  44. package/dist/application/preflight.d.ts +27 -0
  45. package/dist/application/preflight.js +131 -0
  46. package/dist/application/seal.d.ts +4 -0
  47. package/dist/application/seal.js +28 -0
  48. package/dist/application/serialize.d.ts +6 -0
  49. package/dist/application/serialize.js +9 -0
  50. package/dist/cli/arguments.d.ts +28 -0
  51. package/dist/cli/arguments.js +167 -0
  52. package/dist/cli/exit-codes.d.ts +36 -0
  53. package/dist/cli/exit-codes.js +39 -0
  54. package/dist/cli/main.d.ts +2 -0
  55. package/dist/cli/main.js +143 -0
  56. package/dist/cli/render.d.ts +26 -0
  57. package/dist/cli/render.js +53 -0
  58. package/dist/cli/run.d.ts +41 -0
  59. package/dist/cli/run.js +203 -0
  60. package/dist/core/canonical/canonicalize.d.ts +1 -0
  61. package/dist/core/canonical/canonicalize.js +113 -0
  62. package/dist/core/canonical/digest.d.ts +7 -0
  63. package/dist/core/canonical/digest.js +81 -0
  64. package/dist/core/canonical/scan-json.d.ts +1 -0
  65. package/dist/core/canonical/scan-json.js +298 -0
  66. package/dist/core/canonical/value-domain.d.ts +4 -0
  67. package/dist/core/canonical/value-domain.js +113 -0
  68. package/dist/core/compile/compile.d.ts +24 -0
  69. package/dist/core/compile/compile.js +67 -0
  70. package/dist/core/compile/declarations.d.ts +5 -0
  71. package/dist/core/compile/declarations.js +22 -0
  72. package/dist/core/compile/expression-legality.d.ts +14 -0
  73. package/dist/core/compile/expression-legality.js +346 -0
  74. package/dist/core/compile/forbidden-inputs.d.ts +5 -0
  75. package/dist/core/compile/forbidden-inputs.js +19 -0
  76. package/dist/core/compile/interface-inventory.d.ts +10 -0
  77. package/dist/core/compile/interface-inventory.js +55 -0
  78. package/dist/core/compile/oracle-alignment.d.ts +14 -0
  79. package/dist/core/compile/oracle-alignment.js +93 -0
  80. package/dist/core/compile/reachability.d.ts +17 -0
  81. package/dist/core/compile/reachability.js +177 -0
  82. package/dist/core/compile/rubrics.d.ts +55 -0
  83. package/dist/core/compile/rubrics.js +198 -0
  84. package/dist/core/compile/scripting-bound.d.ts +5 -0
  85. package/dist/core/compile/scripting-bound.js +151 -0
  86. package/dist/core/compile/sensitivity-witness.d.ts +73 -0
  87. package/dist/core/compile/sensitivity-witness.js +232 -0
  88. package/dist/core/compile/waivers.d.ts +3 -0
  89. package/dist/core/compile/waivers.js +21 -0
  90. package/dist/core/coverage/coverage.d.ts +18 -0
  91. package/dist/core/coverage/coverage.js +47 -0
  92. package/dist/core/coverage/relevance.d.ts +75 -0
  93. package/dist/core/coverage/relevance.js +186 -0
  94. package/dist/core/coverage/rules.d.ts +7 -0
  95. package/dist/core/coverage/rules.js +18 -0
  96. package/dist/core/coverage/satisfaction.d.ts +107 -0
  97. package/dist/core/coverage/satisfaction.js +415 -0
  98. package/dist/core/coverage/table.d.ts +26 -0
  99. package/dist/core/coverage/table.js +222 -0
  100. package/dist/core/declared-inputs.d.ts +16 -0
  101. package/dist/core/declared-inputs.js +18 -0
  102. package/dist/core/evaluate/evidence-resolution.d.ts +54 -0
  103. package/dist/core/evaluate/evidence-resolution.js +153 -0
  104. package/dist/core/evaluate/operators.d.ts +87 -0
  105. package/dist/core/evaluate/operators.js +350 -0
  106. package/dist/core/evaluate/resolution.d.ts +40 -0
  107. package/dist/core/evaluate/resolution.js +336 -0
  108. package/dist/core/evaluate/resolved-value.d.ts +9 -0
  109. package/dist/core/evaluate/resolved-value.js +6 -0
  110. package/dist/core/failure-codes.d.ts +18 -0
  111. package/dist/core/failure-codes.js +50 -0
  112. package/dist/core/lineage/chain.d.ts +49 -0
  113. package/dist/core/lineage/chain.js +210 -0
  114. package/dist/core/lineage/freeze.d.ts +12 -0
  115. package/dist/core/lineage/freeze.js +35 -0
  116. package/dist/core/lineage/stage-table.d.ts +43 -0
  117. package/dist/core/lineage/stage-table.js +106 -0
  118. package/dist/core/preflight/plan.d.ts +67 -0
  119. package/dist/core/preflight/plan.js +313 -0
  120. package/dist/core/preflight/projection.d.ts +40 -0
  121. package/dist/core/preflight/projection.js +96 -0
  122. package/dist/core/preflight/reduce.d.ts +8 -0
  123. package/dist/core/preflight/reduce.js +180 -0
  124. package/dist/core/preflight/witness-evidence.d.ts +42 -0
  125. package/dist/core/preflight/witness-evidence.js +88 -0
  126. package/dist/core/probe/target-policy.d.ts +63 -0
  127. package/dist/core/probe/target-policy.js +328 -0
  128. package/dist/core/schemas/artifact-reference.d.ts +19 -0
  129. package/dist/core/schemas/artifact-reference.js +30 -0
  130. package/dist/core/schemas/artifact.d.ts +1153 -0
  131. package/dist/core/schemas/artifact.js +84 -0
  132. package/dist/core/schemas/constraint-ledger.d.ts +57 -0
  133. package/dist/core/schemas/constraint-ledger.js +143 -0
  134. package/dist/core/schemas/eval-contract.d.ts +402 -0
  135. package/dist/core/schemas/eval-contract.js +147 -0
  136. package/dist/core/schemas/evaluator-configuration.d.ts +37 -0
  137. package/dist/core/schemas/evaluator-configuration.js +43 -0
  138. package/dist/core/schemas/evidence-artifact.d.ts +471 -0
  139. package/dist/core/schemas/evidence-artifact.js +232 -0
  140. package/dist/core/schemas/expression.d.ts +197 -0
  141. package/dist/core/schemas/expression.js +284 -0
  142. package/dist/core/schemas/faults.d.ts +9 -0
  143. package/dist/core/schemas/faults.js +32 -0
  144. package/dist/core/schemas/interface.d.ts +417 -0
  145. package/dist/core/schemas/interface.js +115 -0
  146. package/dist/core/schemas/isolation-manifest.d.ts +136 -0
  147. package/dist/core/schemas/isolation-manifest.js +92 -0
  148. package/dist/core/schemas/lineage.d.ts +21 -0
  149. package/dist/core/schemas/lineage.js +28 -0
  150. package/dist/core/schemas/oracle.d.ts +72 -0
  151. package/dist/core/schemas/oracle.js +35 -0
  152. package/dist/core/schemas/plan.d.ts +115 -0
  153. package/dist/core/schemas/plan.js +58 -0
  154. package/dist/core/schemas/pointer.d.ts +63 -0
  155. package/dist/core/schemas/pointer.js +94 -0
  156. package/dist/core/schemas/port-messages.d.ts +103 -0
  157. package/dist/core/schemas/port-messages.js +90 -0
  158. package/dist/core/schemas/preflight-verdict.d.ts +60 -0
  159. package/dist/core/schemas/preflight-verdict.js +38 -0
  160. package/dist/core/schemas/primitives.d.ts +78 -0
  161. package/dist/core/schemas/primitives.js +121 -0
  162. package/dist/core/schemas/private-artifact-manifest.d.ts +56 -0
  163. package/dist/core/schemas/private-artifact-manifest.js +46 -0
  164. package/dist/core/schemas/probe-body.d.ts +25 -0
  165. package/dist/core/schemas/probe-body.js +23 -0
  166. package/dist/core/schemas/probe-policy.d.ts +76 -0
  167. package/dist/core/schemas/probe-policy.js +33 -0
  168. package/dist/core/schemas/probe.d.ts +189 -0
  169. package/dist/core/schemas/probe.js +82 -0
  170. package/dist/core/schemas/publish.d.ts +30 -0
  171. package/dist/core/schemas/publish.js +135 -0
  172. package/dist/core/schemas/reference-set.d.ts +18 -0
  173. package/dist/core/schemas/reference-set.js +27 -0
  174. package/dist/core/schemas/rubric.d.ts +62 -0
  175. package/dist/core/schemas/rubric.js +68 -0
  176. package/dist/core/schemas/scoring-policy.d.ts +28 -0
  177. package/dist/core/schemas/scoring-policy.js +51 -0
  178. package/dist/core/schemas/sealed-evaluator-brief.d.ts +80 -0
  179. package/dist/core/schemas/sealed-evaluator-brief.js +59 -0
  180. package/dist/core/schemas/sealed-run-record.d.ts +382 -0
  181. package/dist/core/schemas/sealed-run-record.js +215 -0
  182. package/dist/core/schemas/sensitivity-witness.d.ts +138 -0
  183. package/dist/core/schemas/sensitivity-witness.js +86 -0
  184. package/dist/core/schemas/verdict.d.ts +25 -0
  185. package/dist/core/schemas/verdict.js +20 -0
  186. package/dist/core/schemas/waiver.d.ts +17 -0
  187. package/dist/core/schemas/waiver.js +24 -0
  188. package/dist/core/seal/derived-reference.d.ts +36 -0
  189. package/dist/core/seal/derived-reference.js +383 -0
  190. package/dist/core/seal/direction-prose.d.ts +13 -0
  191. package/dist/core/seal/direction-prose.js +120 -0
  192. package/dist/core/seal/plan-index.d.ts +63 -0
  193. package/dist/core/seal/plan-index.js +155 -0
  194. package/dist/core/seal/scripting-audit.d.ts +15 -0
  195. package/dist/core/seal/scripting-audit.js +51 -0
  196. package/dist/core/seal/seal.d.ts +18 -0
  197. package/dist/core/seal/seal.js +155 -0
  198. package/dist/core/stage-contracts.d.ts +30 -0
  199. package/dist/core/stage-contracts.js +1 -0
  200. package/dist/index.d.ts +15 -0
  201. package/dist/index.js +22 -0
  202. package/dist/ports/clock-port.d.ts +13 -0
  203. package/dist/ports/clock-port.js +7 -0
  204. package/dist/ports/corpus-port.d.ts +16 -0
  205. package/dist/ports/corpus-port.js +7 -0
  206. package/dist/ports/environment-probe-port.d.ts +76 -0
  207. package/dist/ports/environment-probe-port.js +32 -0
  208. package/dist/ports/file-system-port.d.ts +27 -0
  209. package/dist/ports/file-system-port.js +11 -0
  210. package/dist/ports/port.d.ts +33 -0
  211. package/dist/ports/port.js +14 -0
  212. package/dist/testing/conformance.d.ts +88 -0
  213. package/dist/testing/conformance.js +281 -0
  214. package/dist/testing/index.d.ts +26 -0
  215. package/dist/testing/index.js +18 -0
  216. package/dist/testing/probe-conformance.d.ts +45 -0
  217. package/dist/testing/probe-conformance.js +186 -0
  218. package/package.json +130 -0
  219. package/schemas/artifact-reference.schema.json +64 -0
  220. package/schemas/eval-contract.schema.json +2145 -0
  221. package/schemas/evaluator-configuration.schema.json +199 -0
  222. package/schemas/evidence-artifact.schema.json +1229 -0
  223. package/schemas/isolation-manifest.schema.json +394 -0
  224. package/schemas/preflight-verdict.schema.json +114 -0
  225. package/schemas/private-artifact-manifest.schema.json +103 -0
  226. package/schemas/probe.schema.json +1151 -0
  227. package/schemas/rubric.schema.json +148 -0
  228. package/schemas/scoring-policy.schema.json +90 -0
  229. package/schemas/sealed-evaluator-brief.schema.json +268 -0
  230. package/schemas/sealed-run-record.schema.json +875 -0
@@ -0,0 +1,27 @@
1
+ /** the file-system port: two methods, each a byte-level read or write at a caller-owned path. */
2
+ import { FileReadRequest, FileReadResponse, FileWriteRequest, FileWriteResponse } from '../core/schemas/port-messages.ts';
3
+ import type { PortMethod } from './port.ts';
4
+ export type FileSystemPort = {
5
+ readonly readFile: PortMethod<FileReadRequest, FileReadResponse>;
6
+ readonly writeFile: PortMethod<FileWriteRequest, FileWriteResponse>;
7
+ };
8
+ /** the boundary parsers `application/` and the conformance suite validate with. */
9
+ export declare const fileReadParsers: {
10
+ readonly request: import("zod").ZodObject<{
11
+ path: import("zod").ZodString;
12
+ }, import("zod/v4/core").$strict>;
13
+ readonly response: import("zod").ZodObject<{
14
+ path: import("zod").ZodString;
15
+ bytes: import("zod").ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>;
16
+ }, import("zod/v4/core").$strict>;
17
+ };
18
+ export declare const fileWriteParsers: {
19
+ readonly request: import("zod").ZodObject<{
20
+ path: import("zod").ZodString;
21
+ bytes: import("zod").ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>;
22
+ }, import("zod/v4/core").$strict>;
23
+ readonly response: import("zod").ZodObject<{
24
+ path: import("zod").ZodString;
25
+ byteLength: import("zod").ZodInt;
26
+ }, import("zod/v4/core").$strict>;
27
+ };
@@ -0,0 +1,11 @@
1
+ /** the file-system port: two methods, each a byte-level read or write at a caller-owned path. */
2
+ import { FileReadRequest, FileReadResponse, FileWriteRequest, FileWriteResponse, } from '../core/schemas/port-messages.js';
3
+ /** the boundary parsers `application/` and the conformance suite validate with. */
4
+ export const fileReadParsers = {
5
+ request: FileReadRequest,
6
+ response: FileReadResponse,
7
+ };
8
+ export const fileWriteParsers = {
9
+ request: FileWriteRequest,
10
+ response: FileWriteResponse,
11
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * AD-28's shared port contract shape: the one asynchronous method signature
3
+ * every port implements, plus the smallest structural parser interface
4
+ * `invokePort` needs for two-way boundary validation. No concrete port lives
5
+ * here. `CorpusPort`, `EnvironmentProbePort`, `ClockPort`, and
6
+ * `FileSystemPort` each sit in their own file beside this one and read
7
+ * `PortMethod` from it; their adapters are under `adapters/` and the
8
+ * conformance suite that checks an adapter against AD-37 is under `testing/`.
9
+ *
10
+ * Deliberately no Zod import: `BoundaryParser` is structural, so a Zod
11
+ * schema satisfies it without making `ports/` depend on the runtime
12
+ * validation library by name. `ports/` may import `core/schemas` only.
13
+ */
14
+ export type BoundaryParseResult<Value> = {
15
+ readonly success: true;
16
+ readonly data: Value;
17
+ } | {
18
+ readonly success: false;
19
+ readonly error: unknown;
20
+ };
21
+ export type BoundaryParser<Value> = {
22
+ readonly safeParse: (input: unknown) => BoundaryParseResult<Value>;
23
+ };
24
+ export type PortMethod<Request, Response> = (request: Request, signal: AbortSignal) => Promise<Response>;
25
+ export type InvokePortOptions<Request, Response> = {
26
+ readonly request: unknown;
27
+ readonly requestParser: BoundaryParser<Request>;
28
+ readonly responseParser: BoundaryParser<Response>;
29
+ readonly port: PortMethod<Request, unknown>;
30
+ readonly signal: AbortSignal;
31
+ readonly requestPath: string;
32
+ readonly responsePath: string;
33
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * AD-28's shared port contract shape: the one asynchronous method signature
3
+ * every port implements, plus the smallest structural parser interface
4
+ * `invokePort` needs for two-way boundary validation. No concrete port lives
5
+ * here. `CorpusPort`, `EnvironmentProbePort`, `ClockPort`, and
6
+ * `FileSystemPort` each sit in their own file beside this one and read
7
+ * `PortMethod` from it; their adapters are under `adapters/` and the
8
+ * conformance suite that checks an adapter against AD-37 is under `testing/`.
9
+ *
10
+ * Deliberately no Zod import: `BoundaryParser` is structural, so a Zod
11
+ * schema satisfies it without making `ports/` depend on the runtime
12
+ * validation library by name. `ports/` may import `core/schemas` only.
13
+ */
14
+ export {};
@@ -0,0 +1,88 @@
1
+ import type { ClockReadRequest, CorpusResolveRequest, FileReadRequest, FileWriteRequest } from '../core/schemas/port-messages.ts';
2
+ import type { BoundaryParser } from '../ports/port.ts';
3
+ export type ConformanceOutcome = {
4
+ /** `<method>/<assertion>`, e.g. `readFile/typed-fault`. Unique within a report. */
5
+ readonly id: string;
6
+ readonly title: string;
7
+ readonly passed: boolean;
8
+ readonly detail: string;
9
+ };
10
+ export type ConformancePort = 'corpus' | 'clock' | 'file-system' | 'environment-probe';
11
+ /** how many outcomes a complete run of each port produces. Asserted as literals by fixture 58. */
12
+ export declare const CONFORMANCE_OUTCOME_COUNTS: {
13
+ readonly corpus: 6;
14
+ readonly clock: 6;
15
+ readonly 'file-system': 12;
16
+ readonly 'environment-probe': 19;
17
+ };
18
+ export type ConformanceReport = {
19
+ readonly subject: string;
20
+ readonly port: ConformancePort;
21
+ readonly outcomes: readonly ConformanceOutcome[];
22
+ readonly passed: boolean;
23
+ };
24
+ export type ScenarioKind = 'resolves' | 'fails' | 'in-band-error' | 'hangs';
25
+ export type BuiltSubject<Request> = {
26
+ readonly port: (request: Request, signal: AbortSignal) => Promise<unknown>;
27
+ readonly underlyingCalls: () => number;
28
+ readonly dispose?: () => Promise<void>;
29
+ };
30
+ export type PortSubject<Request> = {
31
+ readonly name: string;
32
+ readonly sampleRequest: Request;
33
+ /**
34
+ * A FRESH instance per scenario. The call counter must count the
35
+ * underlying mechanism (the filesystem call, the HTTP request), never the
36
+ * port invocation, or the retry assertion counts the wrong thing and
37
+ * passes for every adapter.
38
+ */
39
+ readonly build: (scenario: ScenarioKind) => Promise<BuiltSubject<Request>>;
40
+ /** how long "promptly" is for this subject. Default 1000. */
41
+ readonly abortBudgetMs?: number;
42
+ };
43
+ /** "Promptly" with no bound is unfalsifiable. One second clears any real adapter's abort latency and sits well inside a default test timeout. */
44
+ export declare const DEFAULT_ABORT_BUDGET_MS = 1000;
45
+ /**
46
+ * A thrown value read as AD-28's fault shape. Structural, not `instanceof`: a
47
+ * published suite can see a `RuntimeFault` from a second copy of this package.
48
+ * A plain `Error` fails it (fixture 45).
49
+ */
50
+ export type FaultView = {
51
+ readonly code: string;
52
+ readonly artifactPath: string;
53
+ };
54
+ export declare function faultView(error: unknown): FaultView | undefined;
55
+ export declare function describeThrown(error: unknown): string;
56
+ /** For assertions carrying their own title. `probe-conformance.ts` is the only caller; `index.ts` re-exports none of this machinery. */
57
+ export declare function titledOutcome(id: string, title: string, passed: boolean, detail: string): ConformanceOutcome;
58
+ export type BuildResult<Request> = {
59
+ readonly ok: true;
60
+ readonly built: BuiltSubject<Request>;
61
+ } | {
62
+ readonly ok: false;
63
+ readonly detail: string;
64
+ };
65
+ export declare function buildScenario<Request>(subject: PortSubject<Request>, scenario: ScenarioKind): Promise<BuildResult<Request>>;
66
+ /** `undefined` when dispose succeeded or was absent; the throw's description otherwise. */
67
+ export declare function disposeScenario<Request>(built: BuiltSubject<Request>): Promise<string | undefined>;
68
+ /** A counter that throws is a broken subject. */
69
+ export declare function countCalls<Request>(built: BuiltSubject<Request>): number | string;
70
+ /** Folds a `dispose` throw into an outcome that had passed. */
71
+ export declare function withDispose(base: ConformanceOutcome, disposeDetail: string | undefined): ConformanceOutcome;
72
+ export type Settled = {
73
+ readonly kind: 'resolved';
74
+ readonly value: unknown;
75
+ } | {
76
+ readonly kind: 'rejected';
77
+ readonly error: unknown;
78
+ };
79
+ export declare function settle(call: Promise<unknown>): Promise<Settled>;
80
+ /** The six outcomes for one port method, in report order. */
81
+ export declare function runSharedAssertions<Request>(prefix: string, subject: PortSubject<Request>, responseParser: BoundaryParser<unknown>): Promise<ConformanceOutcome[]>;
82
+ /** The count is part of the verdict, so an empty or short outcome list cannot read as a pass. */
83
+ export declare function reportOf(subject: string, port: ConformancePort, outcomes: readonly ConformanceOutcome[]): ConformanceReport;
84
+ /** One line per outcome; this string is an adapter author's only view of a failure. A failure leads with `FAIL` and carries its detail. */
85
+ export declare function formatConformanceReport(report: ConformanceReport): string;
86
+ export declare function runCorpusPortConformance(subject: PortSubject<CorpusResolveRequest>): Promise<ConformanceReport>;
87
+ export declare function runClockPortConformance(subject: PortSubject<ClockReadRequest>): Promise<ConformanceReport>;
88
+ export declare function runFileSystemPortConformance(readSubject: PortSubject<FileReadRequest>, writeSubject: PortSubject<FileWriteRequest>): Promise<ConformanceReport>;
@@ -0,0 +1,281 @@
1
+ /**
2
+ * AD-37's conformance suite: six assertions per port method, run against a
3
+ * caller-supplied subject.
4
+ *
5
+ * Imports no test runner. A published module that imported vitest would make
6
+ * it a runtime dependency, or fail to load for an adopter using something
7
+ * else. `scripts/dependency-direction.ts` enforces that.
8
+ *
9
+ * Imports no `core/probe/` either. A suite sharing the subject's decision
10
+ * procedure would pass any subject that shared it too.
11
+ */
12
+ import { RUNTIME_FAULT_CODES } from '../core/schemas/faults.js';
13
+ import { clockReadParsers } from '../ports/clock-port.js';
14
+ import { corpusResolveParsers } from '../ports/corpus-port.js';
15
+ import { fileReadParsers, fileWriteParsers } from '../ports/file-system-port.js';
16
+ /** how many outcomes a complete run of each port produces. Asserted as literals by fixture 58. */
17
+ export const CONFORMANCE_OUTCOME_COUNTS = {
18
+ corpus: 6,
19
+ clock: 6,
20
+ 'file-system': 12,
21
+ 'environment-probe': 19,
22
+ };
23
+ /** "Promptly" with no bound is unfalsifiable. One second clears any real adapter's abort latency and sits well inside a default test timeout. */
24
+ export const DEFAULT_ABORT_BUDGET_MS = 1000;
25
+ const TITLES = {
26
+ 'typed-fault': 'a mechanism failure rejects with a declared RuntimeFault',
27
+ 'single-underlying-call-on-success': 'exactly one underlying call on a successful invocation',
28
+ 'single-underlying-call-on-failure': 'exactly one underlying call on a failing invocation (no internal retry)',
29
+ 'prompt-abort': 'an aborted signal rejects promptly with code "aborted"',
30
+ 'no-in-band-error': 'a value the response schema rejects throws rather than resolving',
31
+ 'schema-valid-return': 'a successful invocation returns a schema-valid response',
32
+ };
33
+ /** The six ids in report order. Every port method emits all six, prefixed by its own name. */
34
+ const SHARED_ASSERTION_IDS = [
35
+ 'typed-fault',
36
+ 'single-underlying-call-on-success',
37
+ 'single-underlying-call-on-failure',
38
+ 'prompt-abort',
39
+ 'no-in-band-error',
40
+ 'schema-valid-return',
41
+ ];
42
+ export function faultView(error) {
43
+ if (!(error instanceof Error))
44
+ return undefined;
45
+ const candidate = error;
46
+ const { code, artifactPath } = candidate;
47
+ if (typeof code !== 'string' || typeof artifactPath !== 'string') {
48
+ return undefined;
49
+ }
50
+ if (!RUNTIME_FAULT_CODES.some((declared) => declared === code)) {
51
+ return undefined;
52
+ }
53
+ return { code, artifactPath };
54
+ }
55
+ export function describeThrown(error) {
56
+ if (error instanceof Error)
57
+ return `${error.name}: ${error.message}`;
58
+ return `a non-Error value: ${String(error)}`;
59
+ }
60
+ function outcome(prefix, id, passed, detail) {
61
+ return {
62
+ id: `${prefix}/${id}`,
63
+ title: TITLES[id] ?? id,
64
+ passed,
65
+ detail,
66
+ };
67
+ }
68
+ /** For assertions carrying their own title. `probe-conformance.ts` is the only caller; `index.ts` re-exports none of this machinery. */
69
+ export function titledOutcome(id, title, passed, detail) {
70
+ return { id, title, passed, detail };
71
+ }
72
+ export async function buildScenario(subject, scenario) {
73
+ try {
74
+ return { ok: true, built: await subject.build(scenario) };
75
+ }
76
+ catch (error) {
77
+ return {
78
+ ok: false,
79
+ detail: `build('${scenario}') threw ${describeThrown(error)}`,
80
+ };
81
+ }
82
+ }
83
+ /** `undefined` when dispose succeeded or was absent; the throw's description otherwise. */
84
+ export async function disposeScenario(built) {
85
+ if (built.dispose === undefined)
86
+ return undefined;
87
+ try {
88
+ await built.dispose();
89
+ return undefined;
90
+ }
91
+ catch (error) {
92
+ return `dispose() threw ${describeThrown(error)}`;
93
+ }
94
+ }
95
+ /** A counter that throws is a broken subject. */
96
+ export function countCalls(built) {
97
+ try {
98
+ return built.underlyingCalls();
99
+ }
100
+ catch (error) {
101
+ return `underlyingCalls() threw ${describeThrown(error)}`;
102
+ }
103
+ }
104
+ /** Folds a `dispose` throw into an outcome that had passed. */
105
+ export function withDispose(base, disposeDetail) {
106
+ if (disposeDetail === undefined)
107
+ return base;
108
+ return {
109
+ ...base,
110
+ passed: false,
111
+ detail: `${base.detail}; ${disposeDetail}`,
112
+ };
113
+ }
114
+ export async function settle(call) {
115
+ try {
116
+ return { kind: 'resolved', value: await call };
117
+ }
118
+ catch (error) {
119
+ return { kind: 'rejected', error };
120
+ }
121
+ }
122
+ /**
123
+ * The `resolves` build, shared by two assertions: one underlying call, and a
124
+ * response the port schema accepts. The schema is the suite's, so a subject
125
+ * cannot widen what counts as valid.
126
+ */
127
+ async function resolvesOutcomes(prefix, subject, responseParser) {
128
+ const run = await buildScenario(subject, 'resolves');
129
+ if (!run.ok) {
130
+ return [
131
+ outcome(prefix, 'single-underlying-call-on-success', false, run.detail),
132
+ outcome(prefix, 'schema-valid-return', false, run.detail),
133
+ ];
134
+ }
135
+ const settled = await settle(run.built.port(subject.sampleRequest, new AbortController().signal));
136
+ const calls = countCalls(run.built);
137
+ const disposeDetail = await disposeScenario(run.built);
138
+ const callOutcome = settled.kind === 'rejected'
139
+ ? outcome(prefix, 'single-underlying-call-on-success', false, `the 'resolves' scenario rejected with ${describeThrown(settled.error)}`)
140
+ : typeof calls === 'string'
141
+ ? outcome(prefix, 'single-underlying-call-on-success', false, calls)
142
+ : outcome(prefix, 'single-underlying-call-on-success', calls === 1, `underlyingCalls() was ${calls}, expected 1`);
143
+ const schemaOutcome = settled.kind === 'rejected'
144
+ ? outcome(prefix, 'schema-valid-return', false, `the 'resolves' scenario rejected with ${describeThrown(settled.error)}`)
145
+ : outcome(prefix, 'schema-valid-return', responseParser.safeParse(settled.value).success, 'the resolved value failed the port response schema');
146
+ return [
147
+ withDispose(callOutcome, disposeDetail),
148
+ withDispose(schemaOutcome, disposeDetail),
149
+ ];
150
+ }
151
+ /**
152
+ * The `fails` build, shared by two assertions. The second catches an internal
153
+ * retry: a retry happens on failure and not on success, so a single id would
154
+ * report a retrying adapter and a double-dispatching one identically.
155
+ */
156
+ async function failsOutcomes(prefix, subject) {
157
+ const run = await buildScenario(subject, 'fails');
158
+ if (!run.ok) {
159
+ return [
160
+ outcome(prefix, 'typed-fault', false, run.detail),
161
+ outcome(prefix, 'single-underlying-call-on-failure', false, run.detail),
162
+ ];
163
+ }
164
+ const settled = await settle(run.built.port(subject.sampleRequest, new AbortController().signal));
165
+ const calls = countCalls(run.built);
166
+ const disposeDetail = await disposeScenario(run.built);
167
+ let typed;
168
+ if (settled.kind === 'resolved') {
169
+ typed = outcome(prefix, 'typed-fault', false, "the 'fails' scenario resolved instead of rejecting");
170
+ }
171
+ else {
172
+ const view = faultView(settled.error);
173
+ typed =
174
+ view === undefined
175
+ ? outcome(prefix, 'typed-fault', false, `rejected with ${describeThrown(settled.error)}, which carries no declared AD-28 code`)
176
+ : outcome(prefix, 'typed-fault', view.artifactPath.length > 0, `the ${view.code} fault carries an empty artifactPath`);
177
+ }
178
+ const callOutcome = typeof calls === 'string'
179
+ ? outcome(prefix, 'single-underlying-call-on-failure', false, calls)
180
+ : outcome(prefix, 'single-underlying-call-on-failure', calls === 1, `underlyingCalls() was ${calls}, expected 1; more than one is an internal retry, which AD-28 forbids`);
181
+ return [
182
+ withDispose(typed, disposeDetail),
183
+ withDispose(callOutcome, disposeDetail),
184
+ ];
185
+ }
186
+ /** The `hangs` build. The signal aborts once the call is in flight; a promise that never settles fails on the budget. */
187
+ async function abortOutcome(prefix, subject) {
188
+ const run = await buildScenario(subject, 'hangs');
189
+ if (!run.ok)
190
+ return outcome(prefix, 'prompt-abort', false, run.detail);
191
+ const budgetMs = subject.abortBudgetMs ?? DEFAULT_ABORT_BUDGET_MS;
192
+ const controller = new AbortController();
193
+ // Handlers are attached before the abort, so a late rejection is never an
194
+ // unhandled one even when the budget wins the race.
195
+ const call = settle(run.built.port(subject.sampleRequest, controller.signal));
196
+ await new Promise((resolve) => setTimeout(resolve, 0));
197
+ controller.abort();
198
+ let timer;
199
+ const budget = new Promise((resolve) => {
200
+ timer = setTimeout(() => resolve('budget-expired'), budgetMs);
201
+ });
202
+ const raced = await Promise.race([call, budget]);
203
+ if (timer !== undefined)
204
+ clearTimeout(timer);
205
+ const disposeDetail = await disposeScenario(run.built);
206
+ let result;
207
+ if (raced === 'budget-expired') {
208
+ result = outcome(prefix, 'prompt-abort', false, `the call had not settled ${budgetMs}ms after the signal aborted`);
209
+ }
210
+ else if (raced.kind === 'resolved') {
211
+ result = outcome(prefix, 'prompt-abort', false, 'the call resolved after the signal aborted instead of rejecting');
212
+ }
213
+ else {
214
+ const view = faultView(raced.error);
215
+ result = outcome(prefix, 'prompt-abort', view?.code === 'aborted', `rejected with ${view === undefined ? describeThrown(raced.error) : `code "${view.code}"`}, expected the "aborted" fault`);
216
+ }
217
+ return withDispose(result, disposeDetail);
218
+ }
219
+ /**
220
+ * The `in-band-error` build: AD-28's "a port never returns a partial success
221
+ * or an in-band error value". The code is left unpinned, since an adapter may
222
+ * recognize its own mechanism's error shape and throw `port-failure` first.
223
+ * Resolving fails.
224
+ */
225
+ async function inBandOutcome(prefix, subject) {
226
+ const run = await buildScenario(subject, 'in-band-error');
227
+ if (!run.ok)
228
+ return outcome(prefix, 'no-in-band-error', false, run.detail);
229
+ const settled = await settle(run.built.port(subject.sampleRequest, new AbortController().signal));
230
+ const disposeDetail = await disposeScenario(run.built);
231
+ const result = settled.kind === 'resolved'
232
+ ? outcome(prefix, 'no-in-band-error', false, 'the in-band error value was returned instead of thrown')
233
+ : outcome(prefix, 'no-in-band-error', faultView(settled.error) !== undefined, `rejected with ${describeThrown(settled.error)}, which carries no declared AD-28 code`);
234
+ return withDispose(result, disposeDetail);
235
+ }
236
+ /** The six outcomes for one port method, in report order. */
237
+ export async function runSharedAssertions(prefix, subject, responseParser) {
238
+ const gathered = [
239
+ ...(await resolvesOutcomes(prefix, subject, responseParser)),
240
+ ...(await failsOutcomes(prefix, subject)),
241
+ await abortOutcome(prefix, subject),
242
+ await inBandOutcome(prefix, subject),
243
+ ];
244
+ const byId = new Map(gathered.map((each) => [each.id, each]));
245
+ return SHARED_ASSERTION_IDS.map((id) => byId.get(`${prefix}/${id}`) ??
246
+ outcome(prefix, id, false, 'the suite produced no outcome for this id'));
247
+ }
248
+ /** The count is part of the verdict, so an empty or short outcome list cannot read as a pass. */
249
+ export function reportOf(subject, port, outcomes) {
250
+ return {
251
+ subject,
252
+ port,
253
+ outcomes,
254
+ passed: outcomes.length === CONFORMANCE_OUTCOME_COUNTS[port] &&
255
+ outcomes.every((each) => each.passed),
256
+ };
257
+ }
258
+ /** One line per outcome; this string is an adapter author's only view of a failure. A failure leads with `FAIL` and carries its detail. */
259
+ export function formatConformanceReport(report) {
260
+ const passes = report.outcomes.filter((each) => each.passed).length;
261
+ const header = `${report.passed ? 'PASS' : 'FAIL'} ${report.port} conformance for "${report.subject}": ${passes}/${report.outcomes.length} assertions passed`;
262
+ const lines = report.outcomes.map((each) => each.passed
263
+ ? `pass ${each.id}: ${each.title}`
264
+ : `FAIL ${each.id}: ${each.title} - ${each.detail}`);
265
+ return [header, ...lines].join('\n');
266
+ }
267
+ export async function runCorpusPortConformance(subject) {
268
+ return reportOf(subject.name, 'corpus', await runSharedAssertions('resolve', subject, corpusResolveParsers.response));
269
+ }
270
+ export async function runClockPortConformance(subject) {
271
+ return reportOf(subject.name, 'clock', await runSharedAssertions('read', subject, clockReadParsers.response));
272
+ }
273
+ export async function runFileSystemPortConformance(readSubject, writeSubject) {
274
+ const name = readSubject.name === writeSubject.name
275
+ ? readSubject.name
276
+ : `${readSubject.name} + ${writeSubject.name}`;
277
+ return reportOf(name, 'file-system', [
278
+ ...(await runSharedAssertions('readFile', readSubject, fileReadParsers.response)),
279
+ ...(await runSharedAssertions('writeFile', writeSubject, fileWriteParsers.response)),
280
+ ]);
281
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The `eval-quality/conformance` subpath: what an outside adapter author needs
3
+ * to implement a port and check the implementation.
4
+ *
5
+ * The boundary vocabulary is published here because this subpath is where AD-37
6
+ * puts the conformance definition an adapter author reads, and that author
7
+ * cannot implement `CorpusPort` without the type. The root barrel does not
8
+ * carry it.
9
+ *
10
+ * Type re-exports use `export type`; `biome.json` sets
11
+ * `style.useExportType: "error"`.
12
+ */
13
+ export type { ClockReadRequest, ClockReadResponse, CorpusResolveRequest, CorpusResolveResponse, FileReadRequest, FileReadResponse, FileWriteRequest, FileWriteResponse, ProbeObservation, ProbeObservedBody, ProbeRequest, ProbeRequestBody, } from '../core/schemas/port-messages.ts';
14
+ export type { ProbeTargetAuthorization, ProbeTargetPolicy, } from '../core/schemas/probe-policy.ts';
15
+ export type { ClockPort } from '../ports/clock-port.ts';
16
+ export { clockReadParsers } from '../ports/clock-port.ts';
17
+ export type { CorpusPort } from '../ports/corpus-port.ts';
18
+ export { corpusResolveParsers } from '../ports/corpus-port.ts';
19
+ export type { EnvironmentProbePort } from '../ports/environment-probe-port.ts';
20
+ export { probeParsers } from '../ports/environment-probe-port.ts';
21
+ export type { FileSystemPort } from '../ports/file-system-port.ts';
22
+ export { fileReadParsers, fileWriteParsers, } from '../ports/file-system-port.ts';
23
+ export type { BuiltSubject, ConformanceOutcome, ConformancePort, ConformanceReport, PortSubject, ScenarioKind, } from './conformance.ts';
24
+ export { CONFORMANCE_OUTCOME_COUNTS, formatConformanceReport, runClockPortConformance, runCorpusPortConformance, runFileSystemPortConformance, } from './conformance.ts';
25
+ export type { ProbeSubject } from './probe-conformance.ts';
26
+ export { runEnvironmentProbePortConformance } from './probe-conformance.ts';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The `eval-quality/conformance` subpath: what an outside adapter author needs
3
+ * to implement a port and check the implementation.
4
+ *
5
+ * The boundary vocabulary is published here because this subpath is where AD-37
6
+ * puts the conformance definition an adapter author reads, and that author
7
+ * cannot implement `CorpusPort` without the type. The root barrel does not
8
+ * carry it.
9
+ *
10
+ * Type re-exports use `export type`; `biome.json` sets
11
+ * `style.useExportType: "error"`.
12
+ */
13
+ export { clockReadParsers } from '../ports/clock-port.js';
14
+ export { corpusResolveParsers } from '../ports/corpus-port.js';
15
+ export { probeParsers } from '../ports/environment-probe-port.js';
16
+ export { fileReadParsers, fileWriteParsers, } from '../ports/file-system-port.js';
17
+ export { CONFORMANCE_OUTCOME_COUNTS, formatConformanceReport, runClockPortConformance, runCorpusPortConformance, runFileSystemPortConformance, } from './conformance.js';
18
+ export { runEnvironmentProbePortConformance } from './probe-conformance.js';
@@ -0,0 +1,45 @@
1
+ /**
2
+ * AD-35's thirteen extra assertions, for the environment-probe port only. The
3
+ * subject supplies its policy and one request per denial, since only it knows
4
+ * how its own interface-to-target mapping is wired.
5
+ *
6
+ * Each of the thirteen builds its own `'resolves'` subject and reads
7
+ * `underlyingCalls()` from a counter starting at zero, so every count below is
8
+ * absolute.
9
+ */
10
+ import type { ProbeRequest } from '../core/schemas/port-messages.ts';
11
+ import type { ProbeTargetPolicy } from '../core/schemas/probe-policy.ts';
12
+ import type { ConformanceReport, PortSubject } from './conformance.ts';
13
+ export type ProbeSubject = PortSubject<ProbeRequest> & {
14
+ readonly policy: ProbeTargetPolicy;
15
+ /** a request the policy ALLOWS, pointed at the subject's own fixture server. */
16
+ readonly authorizedRequest: ProbeRequest;
17
+ /** a request whose interfaceId the policy does not name at all. */
18
+ readonly unmappedRequest: ProbeRequest;
19
+ /** requests whose mapped target resolves into each denied class. */
20
+ readonly deniedAddressRequests: {
21
+ readonly loopback: ProbeRequest;
22
+ readonly private: ProbeRequest;
23
+ readonly linkLocal: ProbeRequest;
24
+ readonly metadata: ProbeRequest;
25
+ };
26
+ readonly unauthorizedMethodRequest: ProbeRequest;
27
+ readonly unauthorizedSchemeRequest: ProbeRequest;
28
+ /** answered with a redirect to a target the policy denies. */
29
+ readonly redirectingRequest: ProbeRequest;
30
+ /** answered with a chain of authorized redirects longer than `maxRedirects`. */
31
+ readonly overRedirectRequest: ProbeRequest;
32
+ /** answered past `maxResponseBytes`. */
33
+ readonly oversizeResponseRequest: ProbeRequest;
34
+ /** answered after `maxElapsedMs`. */
35
+ readonly slowRequest: ProbeRequest;
36
+ /** answered 500 by an authorized target. */
37
+ readonly faultingRequest: ProbeRequest;
38
+ };
39
+ /**
40
+ * Nineteen outcomes: the six shared assertions plus AD-35's thirteen.
41
+ * `maxRequestBytes` is the one cap with no assertion; the request shape is the
42
+ * suite's own, so the suite cannot make a subject emit an oversize request. It
43
+ * stays declared and adapter-enforced.
44
+ */
45
+ export declare function runEnvironmentProbePortConformance(subject: ProbeSubject): Promise<ConformanceReport>;