@lovrozagar/oat 0.10.0 → 0.11.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 (278) hide show
  1. package/README.md +25 -2006
  2. package/dist/cli.js +283 -59
  3. package/dist/config/define-config.d.ts +8 -0
  4. package/dist/config/define-config.d.ts.map +1 -1
  5. package/dist/config/define-config.js.map +1 -1
  6. package/dist/config/validate.d.ts +12 -0
  7. package/dist/config/validate.d.ts.map +1 -0
  8. package/dist/config/validate.js +86 -0
  9. package/dist/config/validate.js.map +1 -0
  10. package/dist/conformance/effects.d.ts +9 -0
  11. package/dist/conformance/effects.d.ts.map +1 -0
  12. package/dist/conformance/effects.js +276 -0
  13. package/dist/conformance/effects.js.map +1 -0
  14. package/dist/conformance/expected-failures.d.ts +18 -0
  15. package/dist/conformance/expected-failures.d.ts.map +1 -0
  16. package/dist/conformance/expected-failures.js +48 -0
  17. package/dist/conformance/expected-failures.js.map +1 -0
  18. package/dist/conformance/feature-gate.d.ts +7 -0
  19. package/dist/conformance/feature-gate.d.ts.map +1 -0
  20. package/dist/conformance/feature-gate.js +428 -0
  21. package/dist/conformance/feature-gate.js.map +1 -0
  22. package/dist/conformance/fuzz.d.ts +108 -0
  23. package/dist/conformance/fuzz.d.ts.map +1 -0
  24. package/dist/conformance/fuzz.js +246 -0
  25. package/dist/conformance/fuzz.js.map +1 -0
  26. package/dist/conformance/leaks.d.ts +10 -0
  27. package/dist/conformance/leaks.d.ts.map +1 -0
  28. package/dist/conformance/leaks.js +23 -0
  29. package/dist/conformance/leaks.js.map +1 -0
  30. package/dist/conformance/pool.d.ts +49 -0
  31. package/dist/conformance/pool.d.ts.map +1 -0
  32. package/dist/conformance/pool.js +75 -0
  33. package/dist/conformance/pool.js.map +1 -0
  34. package/dist/conformance/rate-limit.d.ts +7 -0
  35. package/dist/conformance/rate-limit.d.ts.map +1 -0
  36. package/dist/conformance/rate-limit.js +372 -0
  37. package/dist/conformance/rate-limit.js.map +1 -0
  38. package/dist/conformance/scope.d.ts +15 -0
  39. package/dist/conformance/scope.d.ts.map +1 -0
  40. package/dist/conformance/scope.js +78 -0
  41. package/dist/conformance/scope.js.map +1 -0
  42. package/dist/conformance/seed-contract.d.ts +11 -0
  43. package/dist/conformance/seed-contract.d.ts.map +1 -0
  44. package/dist/conformance/seed-contract.js +680 -0
  45. package/dist/conformance/seed-contract.js.map +1 -0
  46. package/dist/conformance/shapes.d.ts +35 -0
  47. package/dist/conformance/shapes.d.ts.map +1 -0
  48. package/dist/conformance/shapes.js +162 -0
  49. package/dist/conformance/shapes.js.map +1 -0
  50. package/dist/conformance/specs.d.ts +21 -0
  51. package/dist/conformance/specs.d.ts.map +1 -0
  52. package/dist/conformance/specs.js +306 -0
  53. package/dist/conformance/specs.js.map +1 -0
  54. package/dist/conformance/suite.d.ts +209 -0
  55. package/dist/conformance/suite.d.ts.map +1 -0
  56. package/dist/conformance/suite.js +1538 -0
  57. package/dist/conformance/suite.js.map +1 -0
  58. package/dist/conformance/unique.d.ts +7 -0
  59. package/dist/conformance/unique.d.ts.map +1 -0
  60. package/dist/conformance/unique.js +586 -0
  61. package/dist/conformance/unique.js.map +1 -0
  62. package/dist/conformance/worker.d.ts +3 -0
  63. package/dist/conformance/worker.d.ts.map +1 -0
  64. package/dist/conformance/worker.js +27 -0
  65. package/dist/conformance/worker.js.map +1 -0
  66. package/dist/index.d.ts +8 -3
  67. package/dist/index.js +5 -2
  68. package/dist/reference/defects.d.ts +71 -1
  69. package/dist/reference/defects.d.ts.map +1 -1
  70. package/dist/reference/defects.js +74 -8
  71. package/dist/reference/defects.js.map +1 -1
  72. package/dist/reference/dialect.d.ts +6 -4
  73. package/dist/reference/dialect.d.ts.map +1 -1
  74. package/dist/reference/dialect.js +1 -0
  75. package/dist/reference/dialect.js.map +1 -1
  76. package/dist/reference/http.d.ts +17 -25
  77. package/dist/reference/http.d.ts.map +1 -1
  78. package/dist/reference/http.js +574 -224
  79. package/dist/reference/http.js.map +1 -1
  80. package/dist/reference/listing.d.ts +51 -0
  81. package/dist/reference/listing.d.ts.map +1 -0
  82. package/dist/reference/listing.js +256 -0
  83. package/dist/reference/listing.js.map +1 -0
  84. package/dist/reference/model.d.ts +35 -1
  85. package/dist/reference/model.d.ts.map +1 -1
  86. package/dist/reference/model.js +42 -0
  87. package/dist/reference/model.js.map +1 -1
  88. package/dist/reference/query.d.ts +132 -43
  89. package/dist/reference/query.d.ts.map +1 -1
  90. package/dist/reference/query.js +397 -315
  91. package/dist/reference/query.js.map +1 -1
  92. package/dist/reference/shapes.d.ts +60 -0
  93. package/dist/reference/shapes.d.ts.map +1 -0
  94. package/dist/reference/shapes.js +142 -0
  95. package/dist/reference/shapes.js.map +1 -0
  96. package/dist/reference/spec.d.ts +9 -2
  97. package/dist/reference/spec.d.ts.map +1 -1
  98. package/dist/reference/spec.js +291 -71
  99. package/dist/reference/spec.js.map +1 -1
  100. package/dist/reference/store-api.d.ts +14 -43
  101. package/dist/reference/store-api.d.ts.map +1 -1
  102. package/dist/reference/store-api.js +8 -58
  103. package/dist/reference/store-api.js.map +1 -1
  104. package/dist/reference/stores/memory.d.ts +12 -15
  105. package/dist/reference/stores/memory.d.ts.map +1 -1
  106. package/dist/reference/stores/memory.js +22 -70
  107. package/dist/reference/stores/memory.js.map +1 -1
  108. package/dist/reference/stores/postgres.d.ts +11 -23
  109. package/dist/reference/stores/postgres.d.ts.map +1 -1
  110. package/dist/reference/stores/postgres.js +53 -334
  111. package/dist/reference/stores/postgres.js.map +1 -1
  112. package/dist/reference/stores/sql-compile.d.ts +36 -0
  113. package/dist/reference/stores/sql-compile.d.ts.map +1 -0
  114. package/dist/reference/stores/sql-compile.js +95 -0
  115. package/dist/reference/stores/sql-compile.js.map +1 -0
  116. package/dist/reference/stores/sqlite.d.ts +12 -27
  117. package/dist/reference/stores/sqlite.d.ts.map +1 -1
  118. package/dist/reference/stores/sqlite.js +54 -355
  119. package/dist/reference/stores/sqlite.js.map +1 -1
  120. package/dist/report/console.d.ts.map +1 -1
  121. package/dist/report/console.js +23 -9
  122. package/dist/report/console.js.map +1 -1
  123. package/dist/report/matrix.d.ts +2 -0
  124. package/dist/report/matrix.d.ts.map +1 -1
  125. package/dist/report/matrix.js +63 -10
  126. package/dist/report/matrix.js.map +1 -1
  127. package/dist/report/render.d.ts +10 -1
  128. package/dist/report/render.d.ts.map +1 -1
  129. package/dist/report/render.js +75 -13
  130. package/dist/report/render.js.map +1 -1
  131. package/dist/runtime/async.d.ts +16 -3
  132. package/dist/runtime/async.d.ts.map +1 -1
  133. package/dist/runtime/async.js +24 -19
  134. package/dist/runtime/async.js.map +1 -1
  135. package/dist/runtime/auth.d.ts +2 -9
  136. package/dist/runtime/auth.d.ts.map +1 -1
  137. package/dist/runtime/auth.js +38 -62
  138. package/dist/runtime/auth.js.map +1 -1
  139. package/dist/runtime/body.d.ts +15 -3
  140. package/dist/runtime/body.d.ts.map +1 -1
  141. package/dist/runtime/body.js +61 -12
  142. package/dist/runtime/body.js.map +1 -1
  143. package/dist/runtime/check-costs.d.ts +15 -0
  144. package/dist/runtime/check-costs.d.ts.map +1 -0
  145. package/dist/runtime/check-costs.js +114 -0
  146. package/dist/runtime/check-costs.js.map +1 -0
  147. package/dist/runtime/check-ids.d.ts +15 -0
  148. package/dist/runtime/check-ids.d.ts.map +1 -0
  149. package/dist/runtime/check-ids.js +140 -0
  150. package/dist/runtime/check-ids.js.map +1 -0
  151. package/dist/runtime/checks.d.ts +83 -20
  152. package/dist/runtime/checks.d.ts.map +1 -1
  153. package/dist/runtime/checks.js +3285 -1947
  154. package/dist/runtime/checks.js.map +1 -1
  155. package/dist/runtime/client.d.ts +112 -0
  156. package/dist/runtime/client.d.ts.map +1 -1
  157. package/dist/runtime/client.js +271 -39
  158. package/dist/runtime/client.js.map +1 -1
  159. package/dist/runtime/cookies.d.ts +16 -2
  160. package/dist/runtime/cookies.d.ts.map +1 -1
  161. package/dist/runtime/cookies.js +83 -22
  162. package/dist/runtime/cookies.js.map +1 -1
  163. package/dist/runtime/exchanges.d.ts +4 -5
  164. package/dist/runtime/exchanges.d.ts.map +1 -1
  165. package/dist/runtime/exchanges.js +33 -55
  166. package/dist/runtime/exchanges.js.map +1 -1
  167. package/dist/runtime/exit.d.ts +40 -0
  168. package/dist/runtime/exit.d.ts.map +1 -0
  169. package/dist/runtime/exit.js +48 -0
  170. package/dist/runtime/exit.js.map +1 -0
  171. package/dist/runtime/feature-gate.d.ts +0 -9
  172. package/dist/runtime/feature-gate.d.ts.map +1 -1
  173. package/dist/runtime/feature-gate.js +0 -21
  174. package/dist/runtime/feature-gate.js.map +1 -1
  175. package/dist/runtime/finding.d.ts +49 -8
  176. package/dist/runtime/finding.d.ts.map +1 -1
  177. package/dist/runtime/finding.js +51 -8
  178. package/dist/runtime/finding.js.map +1 -1
  179. package/dist/runtime/fixture.d.ts +35 -28
  180. package/dist/runtime/fixture.d.ts.map +1 -1
  181. package/dist/runtime/fixture.js +92 -731
  182. package/dist/runtime/fixture.js.map +1 -1
  183. package/dist/runtime/generate.d.ts +63 -0
  184. package/dist/runtime/generate.d.ts.map +1 -0
  185. package/dist/runtime/generate.js +635 -0
  186. package/dist/runtime/generate.js.map +1 -0
  187. package/dist/runtime/headers.d.ts +6 -0
  188. package/dist/runtime/headers.d.ts.map +1 -0
  189. package/dist/runtime/headers.js +16 -0
  190. package/dist/runtime/headers.js.map +1 -0
  191. package/dist/runtime/path.d.ts +7 -0
  192. package/dist/runtime/path.d.ts.map +1 -0
  193. package/dist/runtime/path.js +44 -0
  194. package/dist/runtime/path.js.map +1 -0
  195. package/dist/runtime/payloads.d.ts +2 -0
  196. package/dist/runtime/payloads.d.ts.map +1 -1
  197. package/dist/runtime/payloads.js +2 -1
  198. package/dist/runtime/payloads.js.map +1 -1
  199. package/dist/runtime/rate-limit.d.ts +6 -4
  200. package/dist/runtime/rate-limit.d.ts.map +1 -1
  201. package/dist/runtime/rate-limit.js +22 -33
  202. package/dist/runtime/rate-limit.js.map +1 -1
  203. package/dist/runtime/redact.d.ts +24 -0
  204. package/dist/runtime/redact.d.ts.map +1 -0
  205. package/dist/runtime/redact.js +98 -0
  206. package/dist/runtime/redact.js.map +1 -0
  207. package/dist/runtime/run.d.ts +34 -2
  208. package/dist/runtime/run.d.ts.map +1 -1
  209. package/dist/runtime/run.js +346 -138
  210. package/dist/runtime/run.js.map +1 -1
  211. package/dist/runtime/runs.d.ts +5 -0
  212. package/dist/runtime/runs.d.ts.map +1 -1
  213. package/dist/runtime/runs.js +7 -1
  214. package/dist/runtime/runs.js.map +1 -1
  215. package/dist/runtime/teardown.d.ts +17 -4
  216. package/dist/runtime/teardown.d.ts.map +1 -1
  217. package/dist/runtime/teardown.js +49 -16
  218. package/dist/runtime/teardown.js.map +1 -1
  219. package/dist/runtime/transcript.d.ts +34 -2
  220. package/dist/runtime/transcript.d.ts.map +1 -1
  221. package/dist/runtime/transcript.js +119 -24
  222. package/dist/runtime/transcript.js.map +1 -1
  223. package/dist/runtime/unique.d.ts +2 -11
  224. package/dist/runtime/unique.d.ts.map +1 -1
  225. package/dist/runtime/unique.js +27 -53
  226. package/dist/runtime/unique.js.map +1 -1
  227. package/dist/runtime/validate.d.ts +13 -2
  228. package/dist/runtime/validate.d.ts.map +1 -1
  229. package/dist/runtime/validate.js +51 -31
  230. package/dist/runtime/validate.js.map +1 -1
  231. package/dist/runtime/wait.d.ts +2 -1
  232. package/dist/runtime/wait.d.ts.map +1 -1
  233. package/dist/runtime/wait.js +2 -36
  234. package/dist/runtime/wait.js.map +1 -1
  235. package/dist/runtime/world.d.ts +24 -1
  236. package/dist/runtime/world.d.ts.map +1 -1
  237. package/dist/runtime/world.js +101 -26
  238. package/dist/runtime/world.js.map +1 -1
  239. package/dist/spec/collection.d.ts +8 -2
  240. package/dist/spec/collection.d.ts.map +1 -1
  241. package/dist/spec/collection.js +47 -7
  242. package/dist/spec/collection.js.map +1 -1
  243. package/dist/spec/extensions.d.ts +5 -1
  244. package/dist/spec/extensions.d.ts.map +1 -1
  245. package/dist/spec/extensions.js +9 -2
  246. package/dist/spec/extensions.js.map +1 -1
  247. package/dist/spec/graph.d.ts +46 -0
  248. package/dist/spec/graph.d.ts.map +1 -1
  249. package/dist/spec/graph.js +175 -11
  250. package/dist/spec/graph.js.map +1 -1
  251. package/dist/spec/load.d.ts +25 -5
  252. package/dist/spec/load.d.ts.map +1 -1
  253. package/dist/spec/load.js +160 -59
  254. package/dist/spec/load.js.map +1 -1
  255. package/dist/spec/query-capabilities.d.ts +4 -0
  256. package/dist/spec/query-capabilities.d.ts.map +1 -1
  257. package/dist/spec/query-capabilities.js +6 -0
  258. package/dist/spec/query-capabilities.js.map +1 -1
  259. package/dist/spec/schema.d.ts +20 -0
  260. package/dist/spec/schema.d.ts.map +1 -0
  261. package/dist/spec/schema.js +118 -0
  262. package/dist/spec/schema.js.map +1 -0
  263. package/dist/spec/tag-schema.d.ts +12 -0
  264. package/dist/spec/tag-schema.d.ts.map +1 -0
  265. package/dist/spec/tag-schema.js +143 -0
  266. package/dist/spec/tag-schema.js.map +1 -0
  267. package/dist/spec/types.js +17 -1
  268. package/dist/spec/types.js.map +1 -1
  269. package/docs/api.md +59 -0
  270. package/docs/assumptions.md +99 -0
  271. package/docs/checks.md +183 -0
  272. package/docs/commands.md +214 -0
  273. package/docs/configuration.md +650 -0
  274. package/docs/model.md +184 -0
  275. package/docs/reference-defects.md +94 -0
  276. package/docs/reports.md +211 -0
  277. package/docs/tags.md +400 -0
  278. package/package.json +3 -1
@@ -0,0 +1,680 @@
1
+ /**
2
+ * Fixture walk, cheap-profile effects, and invite/register-as-create contracts.
3
+ *
4
+ * These are the cases a real document trips: `z.unknown()` / `z.record` after `$ref` inlining,
5
+ * `--profile cheap` still POSTing `extract.once`, and `org.inviteMember` / `auth.registerWithEmail`
6
+ * being treated as `POST /things`.
7
+ */
8
+ import { createServer } from "node:http";
9
+ import { buildCohort, FixtureOverflow, generateBody, isOverflowError, mulberry32, overflowFrom, } from "../runtime/fixture.js";
10
+ import { codePointCount } from "../runtime/payloads.js";
11
+ import { run } from "../runtime/run.js";
12
+ import { buildModel } from "../spec/graph.js";
13
+ import { dereference, documentDefs } from "../spec/load.js";
14
+ function push(results, name, why, ok, detail) {
15
+ results.push({ detail, name, ok, why });
16
+ }
17
+ export function runFixtureWalkCases() {
18
+ const results = [];
19
+ try {
20
+ const members = buildCohort({
21
+ properties: {
22
+ columns: {
23
+ items: {
24
+ additionalProperties: {},
25
+ default: {},
26
+ properties: { name: { type: "string" } },
27
+ required: ["name"],
28
+ type: "object",
29
+ },
30
+ type: "array",
31
+ },
32
+ },
33
+ required: ["columns"],
34
+ type: "object",
35
+ }, 1, ["baseline"], "table.create");
36
+ const body = members[0]?.body;
37
+ push(results, "default: {} on a column object seeds", "z.unknown() / default: {} must not recurse", body !== undefined && Array.isArray(body.columns), body === undefined ? "threw or empty" : JSON.stringify(body).slice(0, 120));
38
+ }
39
+ catch (error) {
40
+ push(results, "default: {} on a column object seeds", "z.unknown() / default: {} must not recurse", false, error instanceof Error ? error.message : String(error));
41
+ }
42
+ try {
43
+ const cyclic = {
44
+ properties: { child: { $ref: "#" } },
45
+ type: "object",
46
+ };
47
+ const { doc } = dereference({
48
+ components: { schemas: { Node: cyclic } },
49
+ info: { title: "cycle", version: "1" },
50
+ openapi: "3.1.0",
51
+ paths: {
52
+ "/tables": {
53
+ post: {
54
+ operationId: "table.create",
55
+ requestBody: {
56
+ content: { "application/json": { schema: { $ref: "#" } } },
57
+ required: true,
58
+ },
59
+ responses: { "201": { description: "ok" } },
60
+ "x-entity": { action: "create", identity: "id", name: "table" },
61
+ },
62
+ },
63
+ },
64
+ });
65
+ const schema = doc.paths?.["/tables"]?.post?.requestBody?.content?.["application/json"]?.schema;
66
+ const members = buildCohort(schema ?? {}, 1, ["baseline"], "table.create", { defs: documentDefs(doc) });
67
+ push(results, "cyclic $ref: # after deref does not throw", "inlined identity cycles must terminate", members[0]?.body !== undefined, members[0] === undefined ? "empty" : "body returned");
68
+ }
69
+ catch (error) {
70
+ push(results, "cyclic $ref: # after deref does not throw", "inlined identity cycles must terminate", false, error instanceof Error ? error.message : String(error));
71
+ }
72
+ try {
73
+ const [member] = buildCohort({
74
+ properties: { email: { format: "email", type: "string" } },
75
+ required: ["email"],
76
+ type: "object",
77
+ }, 1, ["baseline"], "user.create");
78
+ const email = member?.body.email;
79
+ push(results, "format: email matches a conservative regex", "required email must not be Quarterly Report N", typeof email === "string" && /^[^@]+@[^@]+\.[^@]+$/.test(email), typeof email === "string" ? email : String(email));
80
+ }
81
+ catch (error) {
82
+ push(results, "format: email matches a conservative regex", "required email must not be Quarterly Report N", false, error instanceof Error ? error.message : String(error));
83
+ }
84
+ try {
85
+ const HANDLE = "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$";
86
+ const members = buildCohort({
87
+ properties: {
88
+ handle: { maxLength: 32, minLength: 3, pattern: HANDLE, type: "string" },
89
+ name: { maxLength: 32, minLength: 3, type: "string" },
90
+ },
91
+ required: ["handle", "name"],
92
+ type: "object",
93
+ }, 1, undefined, "user.create");
94
+ const re = new RegExp(HANDLE);
95
+ const ok = members.every((member) => {
96
+ const handle = member.body.handle;
97
+ const name = String(member.body.name ?? "");
98
+ const n = codePointCount(name);
99
+ return (typeof handle === "string" &&
100
+ handle !== "a" &&
101
+ re.test(handle) &&
102
+ codePointCount(handle) >= 3 &&
103
+ n >= 3 &&
104
+ n <= 32);
105
+ });
106
+ push(results, "fixture strings honour minLength with pattern", "handle must not be a; every cohort name is in [3, 32]", ok, ok ? `${members.length} variants` : JSON.stringify(members.map((m) => m.body)));
107
+ }
108
+ catch (error) {
109
+ push(results, "fixture strings honour minLength with pattern", "handle must not be a; every cohort name is in [3, 32]", false, error instanceof Error ? error.message : String(error));
110
+ }
111
+ try {
112
+ const { body, missingRequired } = generateBody({
113
+ properties: {
114
+ opt: { maxLength: 3, minLength: 10, type: "string" },
115
+ req: { maxLength: 3, minLength: 10, type: "string" },
116
+ },
117
+ required: ["req"],
118
+ type: "object",
119
+ }, "baseline", mulberry32(1), 0, "thing.create");
120
+ const ok = body.opt === undefined && body.req === undefined && missingRequired.includes("/req");
121
+ push(results, "minLength > maxLength is a generation gap", "omit optional; missingRequired on required; do not loop", ok, `body=${JSON.stringify(body)} missing=${missingRequired.join(",")}`);
122
+ }
123
+ catch (error) {
124
+ push(results, "minLength > maxLength is a generation gap", "omit optional; missingRequired on required; do not loop", false, error instanceof Error ? error.message : String(error));
125
+ }
126
+ try {
127
+ overflowFrom(new RangeError("Maximum call stack size exceeded"), "table.create");
128
+ const named = new FixtureOverflow("table.create", "/columns_json");
129
+ push(results, "overflow names table.create, not unknown", "RangeError must become a named fixture gap", named.message.includes("table.create") && !named.message.includes("unknown") && isOverflowError(named), named.message);
130
+ }
131
+ catch (error) {
132
+ push(results, "overflow names table.create, not unknown", "RangeError must become a named fixture gap", false, error instanceof Error ? error.message : String(error));
133
+ }
134
+ return results;
135
+ }
136
+ const PROJECT = {
137
+ properties: { id: { type: "string" }, name: { type: "string" } },
138
+ required: ["id", "name"],
139
+ type: "object",
140
+ };
141
+ function projectSpec() {
142
+ return {
143
+ info: { title: "cheap-effects", version: "1" },
144
+ openapi: "3.1.0",
145
+ paths: {
146
+ "/v1/projects": {
147
+ get: {
148
+ operationId: "project.list",
149
+ responses: {
150
+ "200": {
151
+ content: {
152
+ "application/json": {
153
+ schema: {
154
+ properties: { projects: { items: PROJECT, type: "array" } },
155
+ required: ["projects"],
156
+ type: "object",
157
+ },
158
+ },
159
+ },
160
+ description: "ok",
161
+ },
162
+ },
163
+ "x-entity": { action: "list", identity: "id", name: "project" },
164
+ },
165
+ post: {
166
+ operationId: "project.create",
167
+ requestBody: {
168
+ content: {
169
+ "application/json": {
170
+ schema: {
171
+ properties: { name: { type: "string" } },
172
+ required: ["name"],
173
+ type: "object",
174
+ },
175
+ },
176
+ },
177
+ required: true,
178
+ },
179
+ responses: {
180
+ "201": { content: { "application/json": { schema: PROJECT } }, description: "created" },
181
+ },
182
+ "x-entity": { action: "create", identity: "id", name: "project" },
183
+ },
184
+ },
185
+ "/v1/projects/{project_id}": {
186
+ get: {
187
+ operationId: "project.read",
188
+ parameters: [{ in: "path", name: "project_id", required: true, schema: { type: "string" } }],
189
+ responses: {
190
+ "200": { content: { "application/json": { schema: PROJECT } }, description: "ok" },
191
+ },
192
+ "x-entity": { action: "read", identity: "id", name: "project" },
193
+ },
194
+ },
195
+ "/v1/projects/{project_id}/extract": {
196
+ post: {
197
+ operationId: "extract.once",
198
+ parameters: [{ in: "path", name: "project_id", required: true, schema: { type: "string" } }],
199
+ responses: {
200
+ "200": { content: { "application/json": { schema: PROJECT } }, description: "ok" },
201
+ "500": { description: "paid inference failed" },
202
+ },
203
+ "x-cost": "high",
204
+ "x-effects": [{ entity: "project", op: "update" }],
205
+ "x-entity": { action: "action", identity: "id", name: "project" },
206
+ },
207
+ },
208
+ },
209
+ };
210
+ }
211
+ async function serveProject() {
212
+ const spec = projectSpec();
213
+ const rows = new Map();
214
+ let seq = 0;
215
+ let extractHits = 0;
216
+ const send = (res, status, body) => {
217
+ if (body === undefined) {
218
+ res.writeHead(status);
219
+ res.end();
220
+ return;
221
+ }
222
+ const text = JSON.stringify(body);
223
+ res.writeHead(status, { "content-length": String(Buffer.byteLength(text)), "content-type": "application/json" });
224
+ res.end(text);
225
+ };
226
+ const readJson = (req) => new Promise((resolve, reject) => {
227
+ const chunks = [];
228
+ req.on("data", (chunk) => {
229
+ chunks.push(chunk);
230
+ });
231
+ req.on("end", () => {
232
+ const text = Buffer.concat(chunks).toString("utf8");
233
+ if (text === "") {
234
+ resolve(undefined);
235
+ return;
236
+ }
237
+ try {
238
+ resolve(JSON.parse(text));
239
+ }
240
+ catch (error) {
241
+ reject(error);
242
+ }
243
+ });
244
+ req.on("error", reject);
245
+ });
246
+ const server = createServer((req, res) => {
247
+ void (async () => {
248
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
249
+ const method = (req.method ?? "GET").toUpperCase();
250
+ if (url.pathname === "/v1/openapi/spec" && method === "GET")
251
+ return send(res, 200, spec);
252
+ if (url.pathname === "/v1/projects" && method === "GET")
253
+ return send(res, 200, { projects: [...rows.values()] });
254
+ if (url.pathname === "/v1/projects" && method === "POST") {
255
+ const body = (await readJson(req));
256
+ const id = `p_${String((seq += 1))}`;
257
+ const row = { id, name: typeof body?.name === "string" ? body.name : "n" };
258
+ rows.set(id, row);
259
+ return send(res, 201, row);
260
+ }
261
+ const item = /^\/v1\/projects\/([^/]+)$/.exec(url.pathname);
262
+ if (item !== null && method === "GET") {
263
+ const row = rows.get(decodeURIComponent(item[1] ?? ""));
264
+ return row === undefined ? send(res, 404) : send(res, 200, row);
265
+ }
266
+ const extract = /^\/v1\/projects\/([^/]+)\/extract$/.exec(url.pathname);
267
+ if (extract !== null && method === "POST") {
268
+ extractHits += 1;
269
+ return send(res, 500, { error: "paid inference" });
270
+ }
271
+ return send(res, 404);
272
+ })().catch(() => {
273
+ if (!res.headersSent)
274
+ send(res, 500);
275
+ });
276
+ });
277
+ await new Promise((resolve) => {
278
+ server.listen(0, "127.0.0.1", resolve);
279
+ });
280
+ const addr = server.address();
281
+ return {
282
+ close: () => new Promise((resolve, reject) => {
283
+ server.close((error) => (error ? reject(error) : resolve()));
284
+ }),
285
+ extractHits: () => extractHits,
286
+ url: `http://127.0.0.1:${addr.port}`,
287
+ };
288
+ }
289
+ function memberSpec() {
290
+ const member = {
291
+ properties: { email: { format: "email", type: "string" }, id: { type: "string" } },
292
+ required: ["id", "email"],
293
+ type: "object",
294
+ };
295
+ return {
296
+ info: { title: "invite-only", version: "1" },
297
+ openapi: "3.1.0",
298
+ paths: {
299
+ "/v1/orgs/{org_id}/members": {
300
+ get: {
301
+ operationId: "member.list",
302
+ parameters: [{ in: "path", name: "org_id", required: true, schema: { type: "string" } }],
303
+ responses: {
304
+ "200": {
305
+ content: {
306
+ "application/json": {
307
+ schema: {
308
+ properties: { members: { items: member, type: "array" } },
309
+ required: ["members"],
310
+ type: "object",
311
+ },
312
+ },
313
+ },
314
+ description: "ok",
315
+ },
316
+ },
317
+ "x-entity": { action: "list", identity: "id", name: "member" },
318
+ },
319
+ post: {
320
+ operationId: "org.inviteMember",
321
+ parameters: [{ in: "path", name: "org_id", required: true, schema: { type: "string" } }],
322
+ requestBody: {
323
+ content: {
324
+ "application/json": {
325
+ schema: {
326
+ properties: { invite_email: { format: "email", type: "string" } },
327
+ required: ["invite_email"],
328
+ type: "object",
329
+ },
330
+ },
331
+ },
332
+ required: true,
333
+ },
334
+ responses: {
335
+ "201": {
336
+ content: {
337
+ "application/json": {
338
+ schema: {
339
+ properties: { grant_id: { type: "string" }, token: { type: "string" } },
340
+ required: ["grant_id", "token"],
341
+ type: "object",
342
+ },
343
+ },
344
+ },
345
+ description: "invited",
346
+ },
347
+ },
348
+ "x-entity": { action: "create", identity: "id", name: "member" },
349
+ "x-invite": {
350
+ accept: "invite.accept",
351
+ grantPointer: "$.grant_id",
352
+ granteeField: "invite_email",
353
+ invite: "org.inviteMember",
354
+ revoke: "invite.revoke",
355
+ tokenPointer: "$.token",
356
+ },
357
+ },
358
+ },
359
+ "/v1/orgs/{org_id}/members/{member_id}": {
360
+ get: {
361
+ operationId: "member.read",
362
+ parameters: [
363
+ { in: "path", name: "org_id", required: true, schema: { type: "string" } },
364
+ { in: "path", name: "member_id", required: true, schema: { type: "string" } },
365
+ ],
366
+ responses: {
367
+ "200": { content: { "application/json": { schema: member } }, description: "ok" },
368
+ },
369
+ "x-entity": { action: "read", identity: "id", name: "member" },
370
+ },
371
+ },
372
+ "/v1/invites/{token}/accept": {
373
+ post: {
374
+ operationId: "invite.accept",
375
+ parameters: [{ in: "path", name: "token", required: true, schema: { type: "string" } }],
376
+ responses: { "200": { description: "ok" } },
377
+ "x-entity": { action: "action", identity: "id", name: "member" },
378
+ },
379
+ },
380
+ "/v1/orgs/{org_id}/grants/{grant_id}": {
381
+ delete: {
382
+ operationId: "invite.revoke",
383
+ parameters: [
384
+ { in: "path", name: "org_id", required: true, schema: { type: "string" } },
385
+ { in: "path", name: "grant_id", required: true, schema: { type: "string" } },
386
+ ],
387
+ responses: { "200": { description: "ok" } },
388
+ "x-entity": { action: "action", identity: "id", name: "member" },
389
+ },
390
+ },
391
+ },
392
+ };
393
+ }
394
+ function userSpec() {
395
+ const user = {
396
+ properties: { email: { format: "email", type: "string" }, id: { type: "string" } },
397
+ required: ["id", "email"],
398
+ type: "object",
399
+ };
400
+ return {
401
+ info: { title: "register-is-not-seed", version: "1" },
402
+ openapi: "3.1.0",
403
+ paths: {
404
+ "/v1/auth/register": {
405
+ post: {
406
+ operationId: "auth.registerWithEmail",
407
+ requestBody: {
408
+ content: {
409
+ "application/json": {
410
+ schema: {
411
+ properties: { email: { format: "email", type: "string" }, password: { type: "string" } },
412
+ required: ["email", "password"],
413
+ type: "object",
414
+ },
415
+ },
416
+ },
417
+ required: true,
418
+ },
419
+ responses: {
420
+ "201": { content: { "application/json": { schema: user } }, description: "created" },
421
+ },
422
+ "x-entity": { action: "create", identity: "id", name: "user" },
423
+ "x-fresh-principal": true,
424
+ },
425
+ },
426
+ "/v1/auth/login": {
427
+ post: {
428
+ operationId: "auth.login",
429
+ requestBody: {
430
+ content: {
431
+ "application/json": {
432
+ schema: {
433
+ properties: { email: { type: "string" }, password: { type: "string" } },
434
+ type: "object",
435
+ },
436
+ },
437
+ },
438
+ },
439
+ responses: {
440
+ "200": {
441
+ content: {
442
+ "application/json": {
443
+ schema: {
444
+ properties: { access_token: { type: "string" } },
445
+ required: ["access_token"],
446
+ type: "object",
447
+ },
448
+ },
449
+ },
450
+ description: "ok",
451
+ },
452
+ },
453
+ },
454
+ },
455
+ },
456
+ };
457
+ }
458
+ function readJson(req) {
459
+ return new Promise((resolve, reject) => {
460
+ const chunks = [];
461
+ req.on("data", (chunk) => {
462
+ chunks.push(chunk);
463
+ });
464
+ req.on("end", () => {
465
+ const text = Buffer.concat(chunks).toString("utf8");
466
+ if (text === "") {
467
+ resolve(undefined);
468
+ return;
469
+ }
470
+ try {
471
+ resolve(JSON.parse(text));
472
+ }
473
+ catch (error) {
474
+ reject(error);
475
+ }
476
+ });
477
+ req.on("error", reject);
478
+ });
479
+ }
480
+ function send(res, status, body) {
481
+ if (body === undefined) {
482
+ res.writeHead(status);
483
+ res.end();
484
+ return;
485
+ }
486
+ const text = JSON.stringify(body);
487
+ res.writeHead(status, { "content-length": String(Buffer.byteLength(text)), "content-type": "application/json" });
488
+ res.end(text);
489
+ }
490
+ async function listen(handler) {
491
+ const server = createServer(handler);
492
+ await new Promise((resolve) => {
493
+ server.listen(0, "127.0.0.1", resolve);
494
+ });
495
+ const addr = server.address();
496
+ return {
497
+ close: () => new Promise((resolve, reject) => {
498
+ server.close((error) => (error ? reject(error) : resolve()));
499
+ }),
500
+ url: `http://127.0.0.1:${addr.port}`,
501
+ };
502
+ }
503
+ export async function runSeedContractSuite() {
504
+ const results = runFixtureWalkCases();
505
+ const model = buildModel(dereference(memberSpec()).doc);
506
+ const member = model.entities.get("member");
507
+ push(results, "invite op is not entity.create", "x-invite stays on entity.invite", member?.create === undefined && member?.invite !== null, `create=${member?.create ?? "none"} invite=${member?.invite?.invite ?? "none"}`);
508
+ try {
509
+ const extracted = [];
510
+ const server = await serveProject();
511
+ try {
512
+ const cheap = await run({
513
+ baseUrl: server.url,
514
+ only: ["project"],
515
+ principals: [{ headers: { authorization: "Bearer t" }, id: "alpha", roots: { project_id: "p_seed" } }],
516
+ profile: "cheap",
517
+ seed: 1,
518
+ spec: `${server.url}/v1/openapi/spec`,
519
+ });
520
+ const extractPosted = cheap.client.transcript.some((e) => e.url.includes("/extract"));
521
+ const skip = cheap.findings.find((f) => f.check === "profile.skip" && f.detail.includes("x-cost: high"));
522
+ push(results, "cheap profile does not POST extract.once", "effects must honour excludedByProfile", !extractPosted && skip !== undefined && server.extractHits() === 0, `hits=${server.extractHits()} skip=${skip?.detail ?? "none"}`);
523
+ extracted.push(...cheap.client.transcript.filter((e) => e.url.includes("/extract")).map((e) => e.url));
524
+ }
525
+ finally {
526
+ await server.close();
527
+ }
528
+ const full = await serveProject();
529
+ try {
530
+ const result = await run({
531
+ baseUrl: full.url,
532
+ only: ["project"],
533
+ principals: [{ headers: { authorization: "Bearer t" }, id: "alpha", roots: { project_id: "p_seed" } }],
534
+ profile: "full",
535
+ seed: 1,
536
+ spec: `${full.url}/v1/openapi/spec`,
537
+ });
538
+ const extractPosted = result.client.transcript.some((e) => e.url.includes("/extract"));
539
+ push(results, "full profile still invokes extract.once", "cheap is a restriction, not a deletion", extractPosted && full.extractHits() > 0, `hits=${full.extractHits()} posted=${extractPosted}`);
540
+ void extracted;
541
+ }
542
+ finally {
543
+ await full.close();
544
+ }
545
+ }
546
+ catch (error) {
547
+ push(results, "cheap profile does not POST extract.once", "effects must honour excludedByProfile", false, error instanceof Error ? error.message : String(error));
548
+ }
549
+ try {
550
+ const seedBodies = [];
551
+ const inviteBodies = [];
552
+ const spec = memberSpec();
553
+ const members = new Map();
554
+ const server = await listen((req, res) => {
555
+ void (async () => {
556
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
557
+ const method = (req.method ?? "GET").toUpperCase();
558
+ if (url.pathname === "/v1/openapi/spec" && method === "GET")
559
+ return send(res, 200, spec);
560
+ if (url.pathname === "/v1/orgs/org_1/members" && method === "GET") {
561
+ return send(res, 200, { members: [...members.values()] });
562
+ }
563
+ if (url.pathname === "/v1/orgs/org_1/members" && method === "POST") {
564
+ const body = await readJson(req);
565
+ inviteBodies.push(body);
566
+ if (body !== null &&
567
+ typeof body === "object" &&
568
+ "invite_email" in body &&
569
+ typeof body.invite_email === "string" &&
570
+ !body.invite_email.includes("@")) {
571
+ seedBodies.push(body);
572
+ return send(res, 400, { error: "invalid email" });
573
+ }
574
+ return send(res, 201, { grant_id: "g1", token: "tok" });
575
+ }
576
+ if (url.pathname === "/v1/invites/tok/accept" && method === "POST")
577
+ return send(res, 200, { ok: true });
578
+ if (url.pathname === "/v1/orgs/org_1/grants/g1" && method === "DELETE")
579
+ return send(res, 200, { ok: true });
580
+ const item = /^\/v1\/orgs\/org_1\/members\/([^/]+)$/.exec(url.pathname);
581
+ if (item !== null && method === "GET")
582
+ return send(res, 404);
583
+ return send(res, 404);
584
+ })().catch(() => {
585
+ if (!res.headersSent)
586
+ send(res, 500);
587
+ });
588
+ });
589
+ try {
590
+ const result = await run({
591
+ baseUrl: server.url,
592
+ only: ["member"],
593
+ principals: [
594
+ { headers: { authorization: "Bearer a" }, id: "alpha", roots: { org_id: "org_1" } },
595
+ {
596
+ headers: { authorization: "Bearer b" },
597
+ id: "beta",
598
+ inviteAs: "beta@x.test",
599
+ roots: { org_id: "org_2" },
600
+ },
601
+ ],
602
+ seed: 1,
603
+ spec: `${server.url}/v1/openapi/spec`,
604
+ });
605
+ const inviteWithPeer = inviteBodies.some((body) => body !== null &&
606
+ typeof body === "object" &&
607
+ body.invite_email === "beta@x.test");
608
+ const generated = seedBodies.length > 0;
609
+ const ran = result.checksRun.includes("auth.invite-grants-then-revokes");
610
+ push(results, "invite-only member is not seeded with a generated email", "invite check sends inviteAs", !generated && inviteWithPeer && ran, `seedJunk=${generated} inviteAsSent=${inviteWithPeer} ran=${ran} bodies=${JSON.stringify(inviteBodies)}`);
611
+ }
612
+ finally {
613
+ await server.close();
614
+ }
615
+ }
616
+ catch (error) {
617
+ push(results, "invite-only member is not seeded with a generated email", "invite check sends inviteAs", false, error instanceof Error ? error.message : String(error));
618
+ }
619
+ try {
620
+ const registerFrom = [];
621
+ const spec = userSpec();
622
+ const server = await listen((req, res) => {
623
+ void (async () => {
624
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
625
+ const method = (req.method ?? "GET").toUpperCase();
626
+ if (url.pathname === "/v1/openapi/spec" && method === "GET")
627
+ return send(res, 200, spec);
628
+ if (url.pathname === "/v1/auth/register" && method === "POST") {
629
+ const via = req.headers["x-via"] === "auth" ? "auth" : "other";
630
+ registerFrom.push(via);
631
+ const body = (await readJson(req));
632
+ if (typeof body?.email === "string" && !body.email.includes("@")) {
633
+ return send(res, 400, { error: "invalid email" });
634
+ }
635
+ return send(res, 201, { email: body?.email, id: "u1" });
636
+ }
637
+ if (url.pathname === "/v1/auth/login" && method === "POST") {
638
+ return send(res, 200, { access_token: "tok" });
639
+ }
640
+ return send(res, 404);
641
+ })().catch(() => {
642
+ if (!res.headersSent)
643
+ send(res, 500);
644
+ });
645
+ });
646
+ try {
647
+ await run({
648
+ baseUrl: server.url,
649
+ only: ["user"],
650
+ principals: [
651
+ {
652
+ auth: {
653
+ credentialFrom: "$.access_token",
654
+ steps: [
655
+ {
656
+ body: { email: "alpha@x.test", password: "pw" },
657
+ headers: { "x-via": "auth" },
658
+ operationId: "auth.registerWithEmail",
659
+ },
660
+ { body: { email: "alpha@x.test", password: "pw" }, operationId: "auth.login" },
661
+ ],
662
+ },
663
+ id: "alpha",
664
+ },
665
+ ],
666
+ seed: 1,
667
+ spec: `${server.url}/v1/openapi/spec`,
668
+ });
669
+ push(results, "auth.registerWithEmail is not seeded", "auth-flow creates are provisioned, not fixtures", registerFrom.length === 1 && registerFrom[0] === "auth", `register calls=${registerFrom.join(",") || "none"}`);
670
+ }
671
+ finally {
672
+ await server.close();
673
+ }
674
+ }
675
+ catch (error) {
676
+ push(results, "auth.registerWithEmail is not seeded", "auth-flow creates are provisioned, not fixtures", false, error instanceof Error ? error.message : String(error));
677
+ }
678
+ return results;
679
+ }
680
+ //# sourceMappingURL=seed-contract.js.map