@fleetless/contracts 1.0.0 → 1.0.3
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.
- package/CHANGELOG.md +97 -2
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +136 -0
- package/README.md +49 -13
- package/SECURITY.md +55 -0
- package/artifacts/openapi.json +11 -11
- package/artifacts/routes.json +12 -12
- package/artifacts/schema/create-app-oidc-provider-request.schema.json +1 -1
- package/dist/alerts.d.ts +23 -28
- package/dist/alerts.js +23 -29
- package/dist/app-users.d.ts +18 -19
- package/dist/app-users.js +18 -20
- package/dist/apps.d.ts +21 -25
- package/dist/apps.js +42 -52
- package/dist/assets.d.ts +70 -132
- package/dist/assets.js +130 -223
- package/dist/audit.d.ts +14 -15
- package/dist/audit.js +28 -55
- package/dist/client-auth.d.ts +9 -9
- package/dist/client-auth.js +8 -9
- package/dist/common.d.ts +29 -37
- package/dist/common.js +28 -37
- package/dist/config-issues.d.ts +23 -25
- package/dist/config-issues.js +17 -17
- package/dist/config.d.ts +37 -44
- package/dist/config.js +145 -187
- package/dist/errors.d.ts +4 -3
- package/dist/errors.js +83 -116
- package/dist/identity.d.ts +24 -27
- package/dist/identity.js +23 -27
- package/dist/index.d.ts +4 -4
- package/dist/index.js +14 -15
- package/dist/introspection.d.ts +7 -6
- package/dist/introspection.js +6 -6
- package/dist/jobs.d.ts +16 -16
- package/dist/jobs.js +24 -29
- package/dist/mcp.d.ts +14 -15
- package/dist/mcp.js +12 -14
- package/dist/oauth.d.ts +21 -27
- package/dist/oauth.js +33 -43
- package/dist/protocol.d.ts +51 -62
- package/dist/protocol.js +107 -139
- package/dist/realtime.d.ts +53 -68
- package/dist/realtime.js +78 -104
- package/dist/rest.d.ts +183 -244
- package/dist/rest.js +305 -399
- package/dist/routes.d.ts +4 -3
- package/dist/routes.js +33 -32
- package/package.json +12 -7
package/dist/config-issues.d.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import type { ValidationIssue } from './config.js';
|
|
3
4
|
/**
|
|
4
5
|
* What is wrong with a configuration document, in one account.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* it has not pinned, so its own copy of `schemaIssues`, `refusal`, `slugOf`,
|
|
16
|
-
* `formatPath` and `valueAt` stood from wave 1, when this module landed here,
|
|
7
|
+
* It lives in contracts because an editor has to say **exactly** what the
|
|
8
|
+
* server says about a document — same codes, same sentences, same paths — and
|
|
9
|
+
* the only way that is true is if it is the same code. An editor that
|
|
10
|
+
* reimplemented this and then disagreed with the server about what is wrong
|
|
11
|
+
* would be worse than one that said nothing.
|
|
12
|
+
*
|
|
13
|
+
* **A second implementation is the thing this module exists to prevent.** A
|
|
14
|
+
* server that cannot import this package keeps its own copy of `schemaIssues`,
|
|
15
|
+
* `refusal`, `slugOf`, `formatPath` and `valueAt`,
|
|
17
16
|
* until that re-pin deleted them and imported these. The window is recorded
|
|
18
17
|
* rather than dropped because it cost a live bug while it was open: the
|
|
19
18
|
* cloud's own `formatPath` wrote a blank path segment as the empty string,
|
|
@@ -41,17 +40,16 @@ export declare const DOCUMENT_ROOT_PATH = "(document)";
|
|
|
41
40
|
* `messages:` is deliberately not among them: its names are their own
|
|
42
41
|
* namespace.
|
|
43
42
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* wave 2 task 8 (cloud `a307e18`, 2026-09-03) — this is the only spelling
|
|
43
|
+
* The cloud re-exports this constant and drives its iteration over sections
|
|
44
|
+
* from it; an editor reads it directly. Spelling it out separately in each
|
|
45
|
+
* would be three copies — this is the only spelling
|
|
48
46
|
* since.
|
|
49
47
|
*/
|
|
50
48
|
export declare const EXPOSURE_SECTIONS: readonly ["datapoints", "actions", "services", "publishers", "cameras"];
|
|
51
49
|
export type ExposureSection = (typeof EXPOSURE_SECTIONS)[number];
|
|
52
50
|
/**
|
|
53
51
|
* The refusals `robotConfigDoc` already made, reported as validation issues
|
|
54
|
-
* with
|
|
52
|
+
* with the format's own codes.
|
|
55
53
|
*
|
|
56
54
|
* **This maps; it does not re-decide.** Seven of the thirteen codes are
|
|
57
55
|
* answered by the schema before a document ever becomes a `RobotConfigDoc`,
|
|
@@ -72,8 +70,8 @@ export type ExposureSection = (typeof EXPOSURE_SECTIONS)[number];
|
|
|
72
70
|
* sentence differs at the document root, where there is no key to remove:
|
|
73
71
|
* see `EMPTY_DOCUMENT_MESSAGE`.
|
|
74
72
|
*
|
|
75
|
-
* Everything else keeps zod's own code. Those are refusals
|
|
76
|
-
* code — a reversed `min_value`/`max_value` pair, a section over its cap, a
|
|
73
|
+
* Everything else keeps zod's own code. Those are refusals the format names no
|
|
74
|
+
* code for — a reversed `min_value`/`max_value` pair, a section over its cap, a
|
|
77
75
|
* key that is not a slug — and inventing a fourteenth code for them would put
|
|
78
76
|
* a code on the wire that no table documents.
|
|
79
77
|
*/
|
|
@@ -89,10 +87,10 @@ export declare function schemaIssues(value: unknown, issues: readonly SchemaIssu
|
|
|
89
87
|
* width. Rendered bare, such a key produced a path a reader cannot act
|
|
90
88
|
* on — and at the root it produced the empty string, which
|
|
91
89
|
* `validationIssue.path` (`z.string().min(1)`) refuses. That was the cloud
|
|
92
|
-
* publishing a finding that fails
|
|
93
|
-
*
|
|
94
|
-
* issue:
|
|
95
|
-
* nothing, for two characters typed.
|
|
90
|
+
* publishing a finding that fails its own contract for findings, and because a
|
|
91
|
+
* draft that does not parse is stored rather than refused it costs the whole
|
|
92
|
+
* `configDraftResponse`, not one issue: a client's `safeParse` drops the
|
|
93
|
+
* response and hands the editor nothing, for two characters typed.
|
|
96
94
|
*
|
|
97
95
|
* The quoted spelling is the segment's JSON string literal, and that is the
|
|
98
96
|
* whole of the reason for choosing it: JSON's string syntax is a subset of
|
|
@@ -125,9 +123,9 @@ export declare function formatPath(path: readonly PropertyKey[]): string;
|
|
|
125
123
|
*
|
|
126
124
|
* Escaping on the way out was the alternative and was rejected: `path` is a
|
|
127
125
|
* wire field (`validationIssue.path`), it is rendered to developers as-is,
|
|
128
|
-
* and every recorded expectation
|
|
129
|
-
*
|
|
130
|
-
*
|
|
126
|
+
* and every recorded expectation on both sides spells it unescaped. Changing
|
|
127
|
+
* what the server says about every document to make one client-side lookup
|
|
128
|
+
* total is the larger of the two costs.
|
|
131
129
|
*
|
|
132
130
|
* So the property this has, and the one its test asserts, is the narrow one:
|
|
133
131
|
* **a path round-trips when no string segment contains `.` or `[`, and the
|
package/dist/config-issues.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
2
|
/** What a path with no segments at all is called, since `path` may not be empty. */
|
|
2
3
|
export const DOCUMENT_ROOT_PATH = '(document)';
|
|
3
4
|
/**
|
|
@@ -6,16 +7,15 @@ export const DOCUMENT_ROOT_PATH = '(document)';
|
|
|
6
7
|
* `messages:` is deliberately not among them: its names are their own
|
|
7
8
|
* namespace.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* wave 2 task 8 (cloud `a307e18`, 2026-09-03) — this is the only spelling
|
|
10
|
+
* The cloud re-exports this constant and drives its iteration over sections
|
|
11
|
+
* from it; an editor reads it directly. Spelling it out separately in each
|
|
12
|
+
* would be three copies — this is the only spelling
|
|
13
13
|
* since.
|
|
14
14
|
*/
|
|
15
15
|
export const EXPOSURE_SECTIONS = ['datapoints', 'actions', 'services', 'publishers', 'cameras'];
|
|
16
16
|
/**
|
|
17
17
|
* The refusals `robotConfigDoc` already made, reported as validation issues
|
|
18
|
-
* with
|
|
18
|
+
* with the format's own codes.
|
|
19
19
|
*
|
|
20
20
|
* **This maps; it does not re-decide.** Seven of the thirteen codes are
|
|
21
21
|
* answered by the schema before a document ever becomes a `RobotConfigDoc`,
|
|
@@ -36,8 +36,8 @@ export const EXPOSURE_SECTIONS = ['datapoints', 'actions', 'services', 'publishe
|
|
|
36
36
|
* sentence differs at the document root, where there is no key to remove:
|
|
37
37
|
* see `EMPTY_DOCUMENT_MESSAGE`.
|
|
38
38
|
*
|
|
39
|
-
* Everything else keeps zod's own code. Those are refusals
|
|
40
|
-
* code — a reversed `min_value`/`max_value` pair, a section over its cap, a
|
|
39
|
+
* Everything else keeps zod's own code. Those are refusals the format names no
|
|
40
|
+
* code for — a reversed `min_value`/`max_value` pair, a section over its cap, a
|
|
41
41
|
* key that is not a slug — and inventing a fourteenth code for them would put
|
|
42
42
|
* a code on the wire that no table documents.
|
|
43
43
|
*/
|
|
@@ -69,9 +69,9 @@ const NULL_KEY_MESSAGE = 'This key is null. Omission is the only spelling of "no
|
|
|
69
69
|
* a genuine `invalid_type` on `null` at the empty path, so the code is right —
|
|
70
70
|
* but the sentence for a null *key* told the developer to remove a key that
|
|
71
71
|
* does not exist, and "select all, delete" is the commonest way anybody gets
|
|
72
|
-
* here.
|
|
73
|
-
* sentence is what
|
|
74
|
-
*
|
|
72
|
+
* here. A draft that does not parse is stored rather than refused, so this
|
|
73
|
+
* sentence is what an editor's findings panel shows persistently for an emptied
|
|
74
|
+
* document rather than a one-shot refusal nobody reads.
|
|
75
75
|
*
|
|
76
76
|
* It names the smallest legal document rather than only saying what is wrong,
|
|
77
77
|
* because at this path there is no line to jump to and no repair to offer —
|
|
@@ -106,10 +106,10 @@ function slugOf(path) {
|
|
|
106
106
|
* width. Rendered bare, such a key produced a path a reader cannot act
|
|
107
107
|
* on — and at the root it produced the empty string, which
|
|
108
108
|
* `validationIssue.path` (`z.string().min(1)`) refuses. That was the cloud
|
|
109
|
-
* publishing a finding that fails
|
|
110
|
-
*
|
|
111
|
-
* issue:
|
|
112
|
-
* nothing, for two characters typed.
|
|
109
|
+
* publishing a finding that fails its own contract for findings, and because a
|
|
110
|
+
* draft that does not parse is stored rather than refused it costs the whole
|
|
111
|
+
* `configDraftResponse`, not one issue: a client's `safeParse` drops the
|
|
112
|
+
* response and hands the editor nothing, for two characters typed.
|
|
113
113
|
*
|
|
114
114
|
* The quoted spelling is the segment's JSON string literal, and that is the
|
|
115
115
|
* whole of the reason for choosing it: JSON's string syntax is a subset of
|
|
@@ -198,9 +198,9 @@ function isBlank(segment) {
|
|
|
198
198
|
*
|
|
199
199
|
* Escaping on the way out was the alternative and was rejected: `path` is a
|
|
200
200
|
* wire field (`validationIssue.path`), it is rendered to developers as-is,
|
|
201
|
-
* and every recorded expectation
|
|
202
|
-
*
|
|
203
|
-
*
|
|
201
|
+
* and every recorded expectation on both sides spells it unescaped. Changing
|
|
202
|
+
* what the server says about every document to make one client-side lookup
|
|
203
|
+
* total is the larger of the two costs.
|
|
204
204
|
*
|
|
205
205
|
* So the property this has, and the one its test asserts, is the narrow one:
|
|
206
206
|
* **a path round-trips when no string segment contains `.` or `[`, and the
|
package/dist/config.d.ts
CHANGED
|
@@ -1,34 +1,29 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
/**
|
|
3
|
-
* What an exposed service *is*, in the developer's own words
|
|
4
|
+
* What an exposed service *is*, in the developer's own words.
|
|
4
5
|
*
|
|
5
6
|
* This is what `robot_describe` carries verbatim, so it is read by a model
|
|
6
7
|
* that has never seen this robot and cannot ask a follow-up question.
|
|
7
8
|
* `unit` and `range` already say what a number *is*; this says what it
|
|
8
9
|
* *means*.
|
|
9
10
|
*
|
|
10
|
-
* **It lives on the configuration rather than on the app, and that
|
|
11
|
-
* decision with a cost.**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* rediscovered as a bug: **two apps can no longer describe one service
|
|
16
|
-
* differently for two audiences.** §17 was reworded in the same wave rather
|
|
17
|
-
* than left contradicting this field.
|
|
11
|
+
* **It lives on the configuration rather than on the app, and that is a
|
|
12
|
+
* decision with a cost.** A description written here is written once per
|
|
13
|
+
* service and is true for every app that reaches the robot, beside the other
|
|
14
|
+
* metadata. What is given up is real and should not be rediscovered as a bug:
|
|
15
|
+
* **two apps cannot describe one service differently for two audiences.**
|
|
18
16
|
*
|
|
19
17
|
* **`.optional()` and not `.nullable().default(null)`, deliberately.** The
|
|
20
18
|
* established shape in this file is a default — and every use of it has
|
|
21
19
|
* added an instance to a known contradiction: `.default()` publishes the
|
|
22
20
|
* field as **required** in the generated JSON Schema, because after parsing
|
|
23
|
-
* it is always present. That is recorded
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* on this pin. So the field simply does not create a fifth instance:
|
|
30
|
-
* optional is optional in both modes, and *absent* is the single spelling of
|
|
31
|
-
* "not described". `.min(1)` keeps the empty string from becoming a second.
|
|
21
|
+
* it is always present. That is recorded in `scripts/export-schemas.ts`, whose
|
|
22
|
+
* remedy (`io: 'input'`, applied per schema) is a judgement call across dozens
|
|
23
|
+
* of schemas and every consumer that vendors them. So this field simply does
|
|
24
|
+
* not add another instance: optional is optional in both modes, and *absent* is
|
|
25
|
+
* the single spelling of "not described". `.min(1)` keeps the empty string from
|
|
26
|
+
* becoming a second.
|
|
32
27
|
*/
|
|
33
28
|
export declare const serviceDescription: z.ZodOptional<z.ZodString>;
|
|
34
29
|
/**
|
|
@@ -134,7 +129,7 @@ export declare const parameterMap: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
|
134
129
|
description: z.ZodOptional<z.ZodString>;
|
|
135
130
|
}, z.core.$strict>>;
|
|
136
131
|
/**
|
|
137
|
-
* Slugs no configured entry may take
|
|
132
|
+
* Slugs no configured entry may take, across **all five exposure
|
|
138
133
|
* sections at once** — slugs are one namespace, so a name reserved here is
|
|
139
134
|
* reserved everywhere.
|
|
140
135
|
*
|
|
@@ -147,12 +142,12 @@ export declare const parameterMap: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
|
147
142
|
* name is the honest half of that: the alternative is a slug the format
|
|
148
143
|
* accepts and one route silently cannot address.
|
|
149
144
|
*
|
|
150
|
-
* **This constant is the only list.** The cloud
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
145
|
+
* **This constant is the only list.** The cloud builds its set from it and
|
|
146
|
+
* emits `reserved_slug`; the rename path reads it for the target; an editor
|
|
147
|
+
* reads it for slug suggestion and repairs. Nothing copies the members. Note
|
|
148
|
+
* that it is NOT the enumeration of built-in datapoints — those are kept
|
|
149
|
+
* separately, and must be, because a reserved name exists that nothing
|
|
150
|
+
* publishes.
|
|
156
151
|
*
|
|
157
152
|
* **What it does not do: `robotConfigDoc` does not enforce it.** Reservation is
|
|
158
153
|
* a semantic check that belongs with the ones that need the robot's context,
|
|
@@ -367,10 +362,9 @@ export declare const messageBody: z.ZodUnknown;
|
|
|
367
362
|
* contract.** This runs inside `publisherConfig`'s failsafe refinement, so a
|
|
368
363
|
* `RangeError: Maximum call stack size exceeded` did not stay here: it
|
|
369
364
|
* propagated out of `safeParse`, which is specified to return a result and
|
|
370
|
-
* not to throw.
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
* meant 400.
|
|
365
|
+
* not to throw. A recursive walk survives a few thousand levels of nesting and
|
|
366
|
+
* throws somewhere above that, which a flow-style YAML one-liner reaches in
|
|
367
|
+
* about 120 KB of input — so a draft PUT would answer 500 where it meant 400.
|
|
374
368
|
*
|
|
375
369
|
* `seen` is not an optimisation. YAML anchors can express a cycle
|
|
376
370
|
* (`&a { b: *a }`), and the parser resolves an alias to the same object, so
|
|
@@ -390,7 +384,7 @@ export declare function placeholderNames(node: unknown, found?: Set<string>): Se
|
|
|
390
384
|
*/
|
|
391
385
|
export declare const messageMap: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
392
386
|
/**
|
|
393
|
-
* An action the robot can be asked to perform
|
|
387
|
+
* An action the robot can be asked to perform. At most one
|
|
394
388
|
* job runs per action slug; a second call is refused `busy`, and every
|
|
395
389
|
* observer of the slug watches the same job.
|
|
396
390
|
*/
|
|
@@ -426,7 +420,7 @@ export declare const actionConfig: z.ZodObject<{
|
|
|
426
420
|
description: z.ZodOptional<z.ZodString>;
|
|
427
421
|
}, z.core.$strict>;
|
|
428
422
|
export type ActionConfig = z.infer<typeof actionConfig>;
|
|
429
|
-
/** A ROS service call with validated parameters
|
|
423
|
+
/** A ROS service call with validated parameters. */
|
|
430
424
|
export declare const serviceConfig: z.ZodObject<{
|
|
431
425
|
ros_name: z.ZodString;
|
|
432
426
|
type: z.ZodString;
|
|
@@ -565,20 +559,20 @@ export type CameraSource = z.infer<typeof cameraSource>;
|
|
|
565
559
|
*
|
|
566
560
|
* The bound lives here once, and `rest.ts`'s `cameraDescriptor` reuses it —
|
|
567
561
|
* the same treatment `rateThrottleHz` got, and for the same reason: the
|
|
568
|
-
*
|
|
569
|
-
*
|
|
570
|
-
*
|
|
562
|
+
* two spellings of one interval — milliseconds in a descriptor, seconds in the
|
|
563
|
+
* document — mean a server converting on one and not the other, with nothing in
|
|
564
|
+
* either file saying so.
|
|
571
565
|
*/
|
|
572
566
|
export declare const snapshotIntervalSeconds: z.ZodNumber;
|
|
573
567
|
/**
|
|
574
|
-
* A camera the robot exposes
|
|
568
|
+
* A camera the robot exposes.
|
|
575
569
|
*
|
|
576
|
-
* `width`/`height`/`fps`/`bitrate_kbps` are not cosmetic:
|
|
570
|
+
* `width`/`height`/`fps`/`bitrate_kbps` are not cosmetic: they are the
|
|
577
571
|
* developer's control over **the robot's own bandwidth**, which is why they
|
|
578
572
|
* live in the configuration rather than in a viewer's request. A viewer never
|
|
579
573
|
* gets to make a robot send more.
|
|
580
574
|
*
|
|
581
|
-
* The two modes are deliberately independent
|
|
575
|
+
* The two modes are deliberately independent:
|
|
582
576
|
*
|
|
583
577
|
* - **Snapshot** runs always, at `snapshot_interval_seconds`, whether or not
|
|
584
578
|
* anyone is watching live. The cloud caches the one frame and serves every
|
|
@@ -635,7 +629,7 @@ export declare const FLEETLESS_FORMAT_VERSION = 1;
|
|
|
635
629
|
* their own name. A duplicate name is then a YAML syntax error rather than a
|
|
636
630
|
* rule somebody has to write, and the name reads as the entry's heading.
|
|
637
631
|
*
|
|
638
|
-
* Slugs remain ONE namespace across all five exposure sections
|
|
632
|
+
* Slugs remain ONE namespace across all five exposure sections, which
|
|
639
633
|
* is what lets a role grant say `{robot, slug}` without naming a kind. That
|
|
640
634
|
* check spans sections and therefore lives in the cloud, not here.
|
|
641
635
|
*/
|
|
@@ -816,12 +810,12 @@ export declare const robotConfigDoc: z.ZodObject<{
|
|
|
816
810
|
}, z.core.$strict>;
|
|
817
811
|
export type RobotConfigDoc = z.infer<typeof robotConfigDoc>;
|
|
818
812
|
/**
|
|
819
|
-
* One thing the cloud has to say about a configuration
|
|
820
|
-
*
|
|
813
|
+
* One thing the cloud has to say about a configuration: the field, and the
|
|
814
|
+
* rule it violates.
|
|
821
815
|
*
|
|
822
816
|
* `error` blocks the publish. `warning` does not — an unknown topic is a
|
|
823
|
-
* warning on purpose, because configuring a robot that has never been
|
|
824
|
-
*
|
|
817
|
+
* warning on purpose, because configuring a robot that has never been connected
|
|
818
|
+
* must stay possible.
|
|
825
819
|
*/
|
|
826
820
|
export declare const validationIssue: z.ZodObject<{
|
|
827
821
|
path: z.ZodString;
|
|
@@ -836,8 +830,7 @@ export declare const validationIssue: z.ZodObject<{
|
|
|
836
830
|
export type ValidationIssue = z.infer<typeof validationIssue>;
|
|
837
831
|
/**
|
|
838
832
|
* Where a robot's configuration stands — the material for the console's
|
|
839
|
-
* "draft newer than published", "published v2 · applied v1 · bridge offline"
|
|
840
|
-
* (spec §15.2, robot tab 1).
|
|
833
|
+
* "draft newer than published", "published v2 · applied v1 · bridge offline".
|
|
841
834
|
*/
|
|
842
835
|
export declare const configState: z.ZodObject<{
|
|
843
836
|
published_version: z.ZodNullable<z.ZodNumber>;
|