@sdxc/spec 0.0.0-pre.1

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 (77) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +924 -0
  3. package/dist/ast.d.ts +193 -0
  4. package/dist/ast.js +9 -0
  5. package/dist/builtins.d.ts +29 -0
  6. package/dist/builtins.js +66 -0
  7. package/dist/cli.d.ts +21 -0
  8. package/dist/cli.js +297 -0
  9. package/dist/diagnostics.d.ts +47 -0
  10. package/dist/diagnostics.js +8 -0
  11. package/dist/errors.d.ts +131 -0
  12. package/dist/errors.js +159 -0
  13. package/dist/executor.d.ts +66 -0
  14. package/dist/executor.js +320 -0
  15. package/dist/expectation.d.ts +61 -0
  16. package/dist/expectation.js +222 -0
  17. package/dist/index.d.ts +51 -0
  18. package/dist/index.js +36 -0
  19. package/dist/lexer.d.ts +22 -0
  20. package/dist/lexer.js +284 -0
  21. package/dist/loader.d.ts +21 -0
  22. package/dist/loader.js +81 -0
  23. package/dist/parser.d.ts +24 -0
  24. package/dist/parser.js +502 -0
  25. package/dist/permissions.d.ts +139 -0
  26. package/dist/permissions.js +325 -0
  27. package/dist/plugin.d.ts +90 -0
  28. package/dist/plugin.js +9 -0
  29. package/dist/plugins/browser.d.ts +24 -0
  30. package/dist/plugins/browser.js +896 -0
  31. package/dist/plugins/cli.d.ts +17 -0
  32. package/dist/plugins/cli.js +134 -0
  33. package/dist/plugins/db-e2e-probe.d.ts +14 -0
  34. package/dist/plugins/db-e2e-probe.js +112 -0
  35. package/dist/plugins/db.d.ts +19 -0
  36. package/dist/plugins/db.js +199 -0
  37. package/dist/plugins/demo.d.ts +17 -0
  38. package/dist/plugins/demo.js +70 -0
  39. package/dist/plugins/env.d.ts +18 -0
  40. package/dist/plugins/env.js +87 -0
  41. package/dist/plugins/fs.d.ts +16 -0
  42. package/dist/plugins/fs.js +415 -0
  43. package/dist/plugins/http.d.ts +19 -0
  44. package/dist/plugins/http.js +505 -0
  45. package/dist/plugins/jwt.d.ts +17 -0
  46. package/dist/plugins/jwt.js +342 -0
  47. package/dist/plugins/sample.d.ts +27 -0
  48. package/dist/plugins/sample.js +400 -0
  49. package/dist/plugins/url.d.ts +18 -0
  50. package/dist/plugins/url.js +126 -0
  51. package/dist/project-config.d.ts +163 -0
  52. package/dist/project-config.js +497 -0
  53. package/dist/registry.d.ts +56 -0
  54. package/dist/registry.js +110 -0
  55. package/dist/reporter.d.ts +30 -0
  56. package/dist/reporter.js +237 -0
  57. package/dist/run.d.ts +74 -0
  58. package/dist/run.js +179 -0
  59. package/dist/runner.d.ts +52 -0
  60. package/dist/runner.js +38 -0
  61. package/dist/source.d.ts +37 -0
  62. package/dist/source.js +31 -0
  63. package/dist/sources.d.ts +45 -0
  64. package/dist/sources.js +54 -0
  65. package/dist/tokens.d.ts +34 -0
  66. package/dist/tokens.js +25 -0
  67. package/dist/transport-stdio.d.ts +34 -0
  68. package/dist/transport-stdio.js +400 -0
  69. package/dist/values.d.ts +48 -0
  70. package/dist/values.js +52 -0
  71. package/dist/workers.d.ts +40 -0
  72. package/dist/workers.js +26 -0
  73. package/dist/workspace-none.d.ts +23 -0
  74. package/dist/workspace-none.js +33 -0
  75. package/dist/workspace.d.ts +47 -0
  76. package/dist/workspace.js +116 -0
  77. package/package.json +28 -0
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The built-in `env` capability: read one named environment variable the
3
+ * caller granted. It is how a spec names a secret without containing one
4
+ * (ADR-007 §6) — the suite says which variable holds the session cookie or the
5
+ * API token, and the environment says what it is. Every read is gated on the
6
+ * `env` permission for that exact name, so a spec can never widen its own
7
+ * reach by asking for a different variable.
8
+ *
9
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
10
+ * @copyright Sergio Xalambrí 2026
11
+ */
12
+ import { failure, isFailure, success } from "@sdxc/result";
13
+ import { ToolError } from "../errors.js";
14
+ import { formatValue } from "../values.js";
15
+ /** Descriptors of every tool the `env` namespace exposes. */
16
+ const ENV_TOOLS = [
17
+ {
18
+ name: "get",
19
+ summary: "Read a granted environment variable, optionally falling back to a value.",
20
+ kind: "observable",
21
+ requires: "env",
22
+ params: [
23
+ {
24
+ name: "name",
25
+ kind: "value",
26
+ required: true,
27
+ summary: "Exact name of the variable, which must be granted with `--allow-env`.",
28
+ },
29
+ {
30
+ name: "fallback",
31
+ kind: "value",
32
+ required: false,
33
+ summary: "Value to read when the variable is unset; without it, unset is an error.",
34
+ },
35
+ ],
36
+ },
37
+ ];
38
+ /**
39
+ * Create the built-in `env` plugin (namespace `"env"`). `env.get NAME` reads
40
+ * the variable, falling back to a second argument when unset; the permission
41
+ * check always runs first, so a fallback substitutes only for an absent value.
42
+ */
43
+ export function createEnvPlugin() {
44
+ return {
45
+ namespace: "env",
46
+ describe() {
47
+ return ENV_TOOLS;
48
+ },
49
+ async call(tool, args, context) {
50
+ if (tool !== "get") {
51
+ return failure(new ToolError(`env has no tool named "${tool}"; tools: get`));
52
+ }
53
+ return get(args, context);
54
+ },
55
+ };
56
+ }
57
+ /** `env.get name [fallback]` — the whole capability. */
58
+ function get(args, context) {
59
+ if (args.length > 2) {
60
+ return failure(new ToolError("env.get takes at most two arguments: a variable name and a fallback"));
61
+ }
62
+ let name = stringArg(args, 0);
63
+ if (isFailure(name))
64
+ return name;
65
+ let allowed = context.permissions.checkEnv(name.data);
66
+ if (isFailure(allowed))
67
+ return allowed;
68
+ let value = process.env[name.data];
69
+ if (value !== undefined)
70
+ return success(value);
71
+ let fallback = args[1];
72
+ if (fallback !== undefined) {
73
+ if (fallback.kind !== "value") {
74
+ return failure(new ToolError("env.get expects a value for its fallback argument (position 2)"));
75
+ }
76
+ return success(fallback.value);
77
+ }
78
+ return failure(new ToolError(`the environment variable ${formatValue(name.data)} is not set; set it, or give env.get a fallback value`));
79
+ }
80
+ /** Extract the variable name as a required string value. */
81
+ function stringArg(args, index) {
82
+ let arg = args[index];
83
+ if (arg === undefined || arg.kind !== "value" || typeof arg.value !== "string") {
84
+ return failure(new ToolError(`env.get expects a string for its name argument (position ${index + 1})`));
85
+ }
86
+ return success(arg.value);
87
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The built-in `fs` capability: workspace-scoped filesystem tools for
3
+ * writing, reading, and observing files. Every path a spec supplies flows
4
+ * through the workspace's safe resolver before any I/O happens, so these
5
+ * tools are workspace-safe and demand no permission grant.
6
+ *
7
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
8
+ * @copyright Sergio Xalambrí 2026
9
+ */
10
+ import type { Plugin } from "../plugin.js";
11
+ /**
12
+ * Create the built-in `fs` plugin: the `fs` namespace of workspace-scoped
13
+ * filesystem tools. All tools resolve their paths through the workspace's
14
+ * safety boundary, so escapes fail before any I/O happens.
15
+ */
16
+ export declare function createFsPlugin(): Plugin;
@@ -0,0 +1,415 @@
1
+ /**
2
+ * The built-in `fs` capability: workspace-scoped filesystem tools for
3
+ * writing, reading, and observing files. Every path a spec supplies flows
4
+ * through the workspace's safe resolver before any I/O happens, so these
5
+ * tools are workspace-safe and demand no permission grant.
6
+ *
7
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
8
+ * @copyright Sergio Xalambrí 2026
9
+ */
10
+ import { cp, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
11
+ import { dirname } from "node:path";
12
+ import { failure, isFailure, success } from "@sdxc/result";
13
+ import { ExpectationError, ToolError } from "../errors.js";
14
+ import { formatValue } from "../values.js";
15
+ /** Words the `file` observable accepts as its assertion selector. */
16
+ const FILE_WORDS = ["exists", "contains"];
17
+ /** Words the `directory` observable accepts as its assertion selector. */
18
+ const DIRECTORY_WORDS = ["exists"];
19
+ /** Words the `remove` action accepts after the path. */
20
+ const REMOVE_WORDS = ["recursive"];
21
+ /** Descriptors of every tool the `fs` namespace exposes. */
22
+ const FS_TOOLS = [
23
+ {
24
+ name: "write",
25
+ summary: "Write content to a workspace file, creating parent directories.",
26
+ kind: "action",
27
+ params: [
28
+ {
29
+ name: "path",
30
+ kind: "value",
31
+ required: true,
32
+ summary: "Workspace path of the file to write.",
33
+ },
34
+ {
35
+ name: "content",
36
+ kind: "value",
37
+ required: true,
38
+ summary: "A string written verbatim; an object or array is serialized as JSON.",
39
+ },
40
+ ],
41
+ },
42
+ {
43
+ name: "read",
44
+ summary: "Read a workspace file as UTF-8 text.",
45
+ kind: "action",
46
+ params: [
47
+ {
48
+ name: "path",
49
+ kind: "value",
50
+ required: true,
51
+ summary: "Workspace path of the file to read.",
52
+ },
53
+ ],
54
+ },
55
+ {
56
+ name: "mkdir",
57
+ summary: "Create a directory (and any missing parents) in the workspace.",
58
+ kind: "action",
59
+ params: [
60
+ {
61
+ name: "path",
62
+ kind: "value",
63
+ required: true,
64
+ summary: "Workspace path of the directory to create.",
65
+ },
66
+ ],
67
+ },
68
+ {
69
+ name: "remove",
70
+ summary: "Delete a workspace file, or a directory with the `recursive` word.",
71
+ kind: "action",
72
+ params: [
73
+ {
74
+ name: "path",
75
+ kind: "value",
76
+ required: true,
77
+ summary: "Workspace path of the entry to delete.",
78
+ },
79
+ {
80
+ name: "recursive",
81
+ kind: "word",
82
+ required: false,
83
+ summary: "Pass the word `recursive` to delete a directory and its contents.",
84
+ },
85
+ ],
86
+ },
87
+ {
88
+ name: "copy",
89
+ summary: "Copy a workspace file or directory to another workspace path.",
90
+ kind: "action",
91
+ params: [
92
+ { name: "from", kind: "value", required: true, summary: "Workspace path to copy from." },
93
+ { name: "to", kind: "value", required: true, summary: "Workspace path to copy to." },
94
+ ],
95
+ },
96
+ {
97
+ name: "exists",
98
+ summary: "Observe whether anything exists at a workspace path.",
99
+ kind: "observable",
100
+ params: [
101
+ {
102
+ name: "path",
103
+ kind: "value",
104
+ required: true,
105
+ summary: "Workspace path to check for existence.",
106
+ },
107
+ ],
108
+ },
109
+ {
110
+ name: "file",
111
+ summary: "Assert on a workspace file: `exists`, or `contains` a substring.",
112
+ kind: "observable",
113
+ params: [
114
+ {
115
+ name: "path",
116
+ kind: "value",
117
+ required: true,
118
+ summary: "Workspace path of the file to inspect.",
119
+ },
120
+ {
121
+ name: "assertion",
122
+ kind: "word",
123
+ required: true,
124
+ summary: "One of the words `exists` or `contains`.",
125
+ },
126
+ {
127
+ name: "expected",
128
+ kind: "value",
129
+ required: false,
130
+ summary: "The substring `contains` demands the file to include.",
131
+ },
132
+ ],
133
+ },
134
+ {
135
+ name: "directory",
136
+ summary: "Assert on a workspace directory: `exists`.",
137
+ kind: "observable",
138
+ params: [
139
+ {
140
+ name: "path",
141
+ kind: "value",
142
+ required: true,
143
+ summary: "Workspace path of the directory to inspect.",
144
+ },
145
+ {
146
+ name: "assertion",
147
+ kind: "word",
148
+ required: true,
149
+ summary: "The word `exists`.",
150
+ },
151
+ ],
152
+ },
153
+ ];
154
+ /**
155
+ * Create the built-in `fs` plugin: the `fs` namespace of workspace-scoped
156
+ * filesystem tools. All tools resolve their paths through the workspace's
157
+ * safety boundary, so escapes fail before any I/O happens.
158
+ */
159
+ export function createFsPlugin() {
160
+ return {
161
+ namespace: "fs",
162
+ describe() {
163
+ return FS_TOOLS;
164
+ },
165
+ async call(tool, args, context) {
166
+ switch (tool) {
167
+ case "write":
168
+ return await write(args, context);
169
+ case "read":
170
+ return await read(args, context);
171
+ case "mkdir":
172
+ return await makeDirectory(args, context);
173
+ case "remove":
174
+ return await remove(args, context);
175
+ case "copy":
176
+ return await copy(args, context);
177
+ case "exists":
178
+ return await exists(args, context);
179
+ case "file":
180
+ return await file(args, context);
181
+ case "directory":
182
+ return await directory(args, context);
183
+ default: {
184
+ let names = FS_TOOLS.map((descriptor) => descriptor.name).join(", ");
185
+ return failure(new ToolError(`fs has no tool named "${tool}"; tools: ${names}`));
186
+ }
187
+ }
188
+ },
189
+ };
190
+ }
191
+ /** `fs.write path content` — write a file, creating parent directories. */
192
+ async function write(args, context) {
193
+ let specPath = stringArg(args, 0, "write", "path");
194
+ if (isFailure(specPath))
195
+ return specPath;
196
+ let content = args[1];
197
+ if (content === undefined || content.kind !== "value") {
198
+ return failure(new ToolError("fs.write expects a content value as its second argument"));
199
+ }
200
+ let serialized = serializeContent(content.value);
201
+ if (isFailure(serialized))
202
+ return serialized;
203
+ let resolved = context.workspace.resolve(specPath.data);
204
+ if (isFailure(resolved))
205
+ return resolved;
206
+ try {
207
+ await mkdir(dirname(resolved.data), { recursive: true });
208
+ await writeFile(resolved.data, serialized.data, "utf8");
209
+ return success(null);
210
+ }
211
+ catch (error) {
212
+ return failure(new ToolError(`fs.write failed for "${specPath.data}": ${describeError(error)}`));
213
+ }
214
+ }
215
+ /** `fs.read path` — read a workspace file as UTF-8 text. */
216
+ async function read(args, context) {
217
+ let specPath = stringArg(args, 0, "read", "path");
218
+ if (isFailure(specPath))
219
+ return specPath;
220
+ let resolved = context.workspace.resolve(specPath.data);
221
+ if (isFailure(resolved))
222
+ return resolved;
223
+ try {
224
+ let content = await readFile(resolved.data, "utf8");
225
+ return success(content);
226
+ }
227
+ catch (error) {
228
+ return failure(new ToolError(`fs.read failed for "${specPath.data}": ${describeError(error)}`));
229
+ }
230
+ }
231
+ /** `fs.mkdir path` — create a directory and any missing parents. */
232
+ async function makeDirectory(args, context) {
233
+ let specPath = stringArg(args, 0, "mkdir", "path");
234
+ if (isFailure(specPath))
235
+ return specPath;
236
+ let resolved = context.workspace.resolve(specPath.data);
237
+ if (isFailure(resolved))
238
+ return resolved;
239
+ try {
240
+ await mkdir(resolved.data, { recursive: true });
241
+ return success(null);
242
+ }
243
+ catch (error) {
244
+ return failure(new ToolError(`fs.mkdir failed for "${specPath.data}": ${describeError(error)}`));
245
+ }
246
+ }
247
+ /** `fs.remove path [recursive]` — delete a file, or a directory with `recursive`. */
248
+ async function remove(args, context) {
249
+ let specPath = stringArg(args, 0, "remove", "path");
250
+ if (isFailure(specPath))
251
+ return specPath;
252
+ let recursive = false;
253
+ if (args.length > 1) {
254
+ let selector = wordArg(args, 1, "remove", REMOVE_WORDS);
255
+ if (isFailure(selector))
256
+ return selector;
257
+ recursive = true;
258
+ }
259
+ let resolved = context.workspace.resolve(specPath.data);
260
+ if (isFailure(resolved))
261
+ return resolved;
262
+ try {
263
+ await rm(resolved.data, { recursive });
264
+ return success(null);
265
+ }
266
+ catch (error) {
267
+ return failure(new ToolError(`fs.remove failed for "${specPath.data}": ${describeError(error)}`));
268
+ }
269
+ }
270
+ /** `fs.copy from to` — copy a file or directory inside the workspace. */
271
+ async function copy(args, context) {
272
+ let from = stringArg(args, 0, "copy", "from");
273
+ if (isFailure(from))
274
+ return from;
275
+ let to = stringArg(args, 1, "copy", "to");
276
+ if (isFailure(to))
277
+ return to;
278
+ let resolvedFrom = context.workspace.resolve(from.data);
279
+ if (isFailure(resolvedFrom))
280
+ return resolvedFrom;
281
+ let resolvedTo = context.workspace.resolve(to.data);
282
+ if (isFailure(resolvedTo))
283
+ return resolvedTo;
284
+ try {
285
+ await mkdir(dirname(resolvedTo.data), { recursive: true });
286
+ await cp(resolvedFrom.data, resolvedTo.data, { recursive: true });
287
+ return success(null);
288
+ }
289
+ catch (error) {
290
+ return failure(new ToolError(`fs.copy failed from "${from.data}" to "${to.data}": ${describeError(error)}`));
291
+ }
292
+ }
293
+ /** `fs.exists path` — observe whether anything exists at the path. */
294
+ async function exists(args, context) {
295
+ let specPath = stringArg(args, 0, "exists", "path");
296
+ if (isFailure(specPath))
297
+ return specPath;
298
+ let resolved = context.workspace.resolve(specPath.data);
299
+ if (isFailure(resolved))
300
+ return resolved;
301
+ try {
302
+ await stat(resolved.data);
303
+ return success(true);
304
+ }
305
+ catch {
306
+ return success(false);
307
+ }
308
+ }
309
+ /** `fs.file path exists|contains [expected]` — checked file assertions. */
310
+ async function file(args, context) {
311
+ let specPath = stringArg(args, 0, "file", "path");
312
+ if (isFailure(specPath))
313
+ return specPath;
314
+ let selector = wordArg(args, 1, "file", FILE_WORDS);
315
+ if (isFailure(selector))
316
+ return selector;
317
+ let resolved = context.workspace.resolve(specPath.data);
318
+ if (isFailure(resolved))
319
+ return resolved;
320
+ if (selector.data === "exists") {
321
+ let isFile = false;
322
+ try {
323
+ let stats = await stat(resolved.data);
324
+ isFile = stats.isFile();
325
+ }
326
+ catch {
327
+ isFile = false;
328
+ }
329
+ if (isFile)
330
+ return success(true);
331
+ return failure(new ExpectationError(`file ${specPath.data} does not exist`));
332
+ }
333
+ let expected = stringArg(args, 2, "file", "expected");
334
+ if (isFailure(expected))
335
+ return expected;
336
+ let content;
337
+ try {
338
+ content = await readFile(resolved.data, "utf8");
339
+ }
340
+ catch {
341
+ return failure(new ExpectationError(`file ${specPath.data} does not exist`));
342
+ }
343
+ if (content.includes(expected.data))
344
+ return success(true);
345
+ return failure(new ExpectationError(`file ${specPath.data} does not contain ${formatValue(expected.data)}`, expected.data, content));
346
+ }
347
+ /** `fs.directory path exists` — checked directory assertion. */
348
+ async function directory(args, context) {
349
+ let specPath = stringArg(args, 0, "directory", "path");
350
+ if (isFailure(specPath))
351
+ return specPath;
352
+ let selector = wordArg(args, 1, "directory", DIRECTORY_WORDS);
353
+ if (isFailure(selector))
354
+ return selector;
355
+ let resolved = context.workspace.resolve(specPath.data);
356
+ if (isFailure(resolved))
357
+ return resolved;
358
+ let isDirectory = false;
359
+ try {
360
+ let stats = await stat(resolved.data);
361
+ isDirectory = stats.isDirectory();
362
+ }
363
+ catch {
364
+ isDirectory = false;
365
+ }
366
+ if (isDirectory)
367
+ return success(true);
368
+ return failure(new ExpectationError(`directory ${specPath.data} does not exist`));
369
+ }
370
+ /**
371
+ * Turn `fs.write` content into file text: strings are written verbatim,
372
+ * objects and arrays become JSON with tab indentation and a trailing newline.
373
+ */
374
+ function serializeContent(content) {
375
+ if (typeof content === "string")
376
+ return success(content);
377
+ if (Array.isArray(content) || isValueObject(content)) {
378
+ return success(`${JSON.stringify(content, null, "\t")}\n`);
379
+ }
380
+ return failure(new ToolError(`fs.write content must be a string, an object, or an array; got ${formatValue(content)}`));
381
+ }
382
+ function isValueObject(value) {
383
+ return typeof value === "object" && value !== null && !Array.isArray(value);
384
+ }
385
+ /**
386
+ * Extract a required string argument, failing with the tool's usage when the
387
+ * argument is missing, a bare word, or not a string.
388
+ */
389
+ function stringArg(args, index, tool, name) {
390
+ let arg = args[index];
391
+ if (arg === undefined || arg.kind !== "value" || typeof arg.value !== "string") {
392
+ return failure(new ToolError(`fs.${tool} expects a string for its ${name} argument (position ${index + 1})`));
393
+ }
394
+ return success(arg.value);
395
+ }
396
+ /**
397
+ * Extract a bare-word argument and validate it against the tool's accepted
398
+ * words, naming them all on any mismatch.
399
+ */
400
+ function wordArg(args, index, tool, accepted) {
401
+ let arg = args[index];
402
+ if (arg === undefined || arg.kind !== "word") {
403
+ return failure(new ToolError(`fs.${tool} expects a bare word as argument ${index + 1}; accepted words: ${accepted.join(", ")}`));
404
+ }
405
+ if (!accepted.includes(arg.word)) {
406
+ return failure(new ToolError(`fs.${tool} does not understand the word "${arg.word}"; accepted words: ${accepted.join(", ")}`));
407
+ }
408
+ return success(arg.word);
409
+ }
410
+ /** Render an unknown thrown value as a one-line message. */
411
+ function describeError(error) {
412
+ if (error instanceof Error)
413
+ return error.message;
414
+ return String(error);
415
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The built-in `http` plugin: `get`/`post`/`put`/`patch`/`delete` tools that
3
+ * issue real requests through the global fetch. Every tool requires the `net`
4
+ * grant, checked against the URL's host and port, and URLs must be absolute
5
+ * because v1 ships no environments mechanism to bind a base URL against. Beyond
6
+ * the URL, calls may carry word-tagged options — `headers { … }`, `form { … }`,
7
+ * `json …`, `text "…"`, and the auth shortcuts `bearer <token>` and
8
+ * `basic <user> <pass>` — in any order, alongside the back-compatible bare body.
9
+ *
10
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
11
+ * @copyright Sergio Xalambrí 2026
12
+ */
13
+ import type { Plugin } from "../plugin.js";
14
+ /**
15
+ * Create the built-in `http` plugin (namespace `"http"`). Tools take an
16
+ * absolute URL and optional body, check the `net` permission for the URL's
17
+ * host and port, then fetch; only network failures or misuse become errors.
18
+ */
19
+ export declare function createHttpPlugin(): Plugin;