labcode 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {labcode-0.2.0 → labcode-0.3.0}/PKG-INFO +16 -12
- {labcode-0.2.0 → labcode-0.3.0}/README.md +12 -8
- {labcode-0.2.0 → labcode-0.3.0}/SPECIFICATIONS.md +89 -31
- {labcode-0.2.0 → labcode-0.3.0}/examples/README.md +5 -5
- {labcode-0.2.0 → labcode-0.3.0}/examples/run_sila2_plate_cycle.py +12 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/run_sila2_seal.py +16 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode.egg-info/PKG-INFO +16 -12
- {labcode-0.2.0 → labcode-0.3.0}/labcode.egg-info/requires.txt +3 -3
- labcode-0.3.0/labcode.egg-info/scm_version.json +8 -0
- {labcode-0.2.0 → labcode-0.3.0}/pyproject.toml +10 -3
- labcode-0.2.0/labcode.egg-info/scm_version.json +0 -8
- {labcode-0.2.0 → labcode-0.3.0}/.github/workflows/ci.yml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/.github/workflows/publish.yml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/.gitignore +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/LICENSE +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/MANIFEST.in +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/outputs/plate_line.boundary.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/outputs/plate_line.observation.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/outputs/plate_line.plan.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/outputs/plate_line.svg +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/outputs/sila2_plate_cycle.boundary.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/outputs/sila2_plate_cycle.observation.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/outputs/sila2_plate_cycle.plan.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/outputs/sila2_plate_cycle.svg +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/plate_line.boundary.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/plate_line.env.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/plate_line.workflow.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/preflight_sila2_env.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/render_plate_line.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/render_sila2_plate_cycle.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/run_all_sila2_examples.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/run_sila2_plate_cycle_no_atc.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/sila2_plate_cycle.boundary.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/sila2_plate_cycle.workflow.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/sila2_plate_cycle.wrapped.env.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/sila2_plate_cycle_no_atc.boundary.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/sila2_plate_cycle_no_atc.workflow.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/sila2_plate_cycle_no_atc.wrapped.env.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/sila2_seal.boundary.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/sila2_seal.env.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/sila2_seal.workflow.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/examples/sila2_seal.wrapped.env.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/__init__.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/__main__.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/_child.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/backend.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/cli.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/dialect.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/extension.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/idgen.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/objectid.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/otel.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/otel_sila2.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/probe.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/py.typed +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/record.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/run_cli.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/runner.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/sila2.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/sila2_commands.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode/sila2_instrument.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode.egg-info/SOURCES.txt +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode.egg-info/dependency_links.txt +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode.egg-info/entry_points.txt +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode.egg-info/scm_file_list.json +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/labcode.egg-info/top_level.txt +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/setup.cfg +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/fixtures/device_script.env.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/fixtures/device_script.workflow.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/fixtures/replenishment.env.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/fixtures/reroute_device.env.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/fixtures/reroute_transporter.env.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/fixtures/transport.env.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/fixtures/transport.workflow.yaml +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_backend.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_cli.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_dialect.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_objectid.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_otel.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_otel_child.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_otel_grpc.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_otel_sila2.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_probe.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_record.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_recording.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_run_cli.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_sila2.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_sila2_commands.py +0 -0
- {labcode-0.2.0 → labcode-0.3.0}/tests/test_sila2_instrument.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: labcode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: labcode -- a dialect wrapper over the Object-Flow Programming Language toolchain
|
|
5
5
|
Author-email: Kazunari Kaizu <kwaizu@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -20,9 +20,9 @@ Classifier: Typing :: Typed
|
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: ofplang-validate
|
|
24
|
-
Requires-Dist: ofplang-schedule
|
|
25
|
-
Requires-Dist: ofplang-run
|
|
23
|
+
Requires-Dist: ofplang-validate<0.3,>=0.2
|
|
24
|
+
Requires-Dist: ofplang-schedule<0.4,>=0.3
|
|
25
|
+
Requires-Dist: ofplang-run<0.5,>=0.4
|
|
26
26
|
Provides-Extra: test
|
|
27
27
|
Requires-Dist: pytest>=7.0; extra == "test"
|
|
28
28
|
Requires-Dist: opentelemetry-sdk>=1.20; extra == "test"
|
|
@@ -98,19 +98,23 @@ repository, and what labcode adds to it in [`SPECIFICATIONS.md`](SPECIFICATIONS.
|
|
|
98
98
|
|
|
99
99
|
What `lc run` brings of its own, beyond dispatching:
|
|
100
100
|
|
|
101
|
-
- **the labcode backend** — each
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
- **the labcode backend** — each operation's `x-labcode.script` runs out-of-process on a
|
|
102
|
+
wall clock (§1.2–§1.4), so a real operation that takes minutes does not block the
|
|
103
|
+
replan loop, and an operation that never returns is stopped by **`op_timeout`**
|
|
104
|
+
(§1.9; `--op-timeout` / `--no-op-timeout`).
|
|
105
|
+
- **refilling a stock** — where the environment says a replenisher can reach a device, a
|
|
106
|
+
stock that would run out is topped up rather than ending the run: the refill's own
|
|
107
|
+
script runs like any other, holding both machines while it works (§1.4). See
|
|
108
|
+
*Refilling a stock* below.
|
|
105
109
|
- **the dialect front door** — the environment's `x-labcode` extension is validated
|
|
106
110
|
before anything runs, on top of the portable-v0 check `lc validate` performs (§1, §2).
|
|
107
111
|
- **availability probing** — each machine is checked as often as its `probe` policy says,
|
|
108
112
|
and one that cannot be reached is taken out of the environment the scheduler plans
|
|
109
|
-
against, so the run routes around it (§1.
|
|
113
|
+
against, so the run routes around it (§1.6; `--no-probe`).
|
|
110
114
|
- **object identity** — the reserved `_id` view key is declared on Object types and minted
|
|
111
115
|
per object, so a physical thing can be followed through a run (§4).
|
|
112
116
|
- **`flavor: sila2`** — a script that speaks SiLA2 gets its clients opened around it
|
|
113
|
-
(§1.
|
|
117
|
+
(§1.7). The client library itself is the `sila2` extra: `pip install labcode[sila2]`,
|
|
114
118
|
installed into whichever interpreter runs the scripts.
|
|
115
119
|
- **recording a run** — with `--trace`, what the run did is recorded as OpenTelemetry
|
|
116
120
|
traces: one trace per run, a span per operation, and — measured inside the process that
|
|
@@ -168,10 +172,10 @@ lc run <workflow> --env <env>
|
|
|
168
172
|
completed reads as discrete, observable steps; a demo against a fast mock wants a small
|
|
169
173
|
value.
|
|
170
174
|
- `--op-timeout S` / `--no-op-timeout` — how long one operation may run before it is
|
|
171
|
-
stopped and failed (§1.
|
|
175
|
+
stopped and failed (§1.9). The default is the environment root's `x-labcode.op_timeout`,
|
|
172
176
|
else 7200 real seconds. The two forms exclude each other.
|
|
173
177
|
- `--no-probe` — ignore the environment's `x-labcode.probe` policies and treat every
|
|
174
|
-
machine as reachable (§1.
|
|
178
|
+
machine as reachable (§1.6). The documents are still validated.
|
|
175
179
|
- `--ignore-resources` — switch the consumable model off. The environment's resource
|
|
176
180
|
declarations are still checked for shape but none is applied, so a bench whose devices
|
|
177
181
|
declare stocks nobody is tracking runs without the boundary saying what they held.
|
|
@@ -54,19 +54,23 @@ repository, and what labcode adds to it in [`SPECIFICATIONS.md`](SPECIFICATIONS.
|
|
|
54
54
|
|
|
55
55
|
What `lc run` brings of its own, beyond dispatching:
|
|
56
56
|
|
|
57
|
-
- **the labcode backend** — each
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
- **the labcode backend** — each operation's `x-labcode.script` runs out-of-process on a
|
|
58
|
+
wall clock (§1.2–§1.4), so a real operation that takes minutes does not block the
|
|
59
|
+
replan loop, and an operation that never returns is stopped by **`op_timeout`**
|
|
60
|
+
(§1.9; `--op-timeout` / `--no-op-timeout`).
|
|
61
|
+
- **refilling a stock** — where the environment says a replenisher can reach a device, a
|
|
62
|
+
stock that would run out is topped up rather than ending the run: the refill's own
|
|
63
|
+
script runs like any other, holding both machines while it works (§1.4). See
|
|
64
|
+
*Refilling a stock* below.
|
|
61
65
|
- **the dialect front door** — the environment's `x-labcode` extension is validated
|
|
62
66
|
before anything runs, on top of the portable-v0 check `lc validate` performs (§1, §2).
|
|
63
67
|
- **availability probing** — each machine is checked as often as its `probe` policy says,
|
|
64
68
|
and one that cannot be reached is taken out of the environment the scheduler plans
|
|
65
|
-
against, so the run routes around it (§1.
|
|
69
|
+
against, so the run routes around it (§1.6; `--no-probe`).
|
|
66
70
|
- **object identity** — the reserved `_id` view key is declared on Object types and minted
|
|
67
71
|
per object, so a physical thing can be followed through a run (§4).
|
|
68
72
|
- **`flavor: sila2`** — a script that speaks SiLA2 gets its clients opened around it
|
|
69
|
-
(§1.
|
|
73
|
+
(§1.7). The client library itself is the `sila2` extra: `pip install labcode[sila2]`,
|
|
70
74
|
installed into whichever interpreter runs the scripts.
|
|
71
75
|
- **recording a run** — with `--trace`, what the run did is recorded as OpenTelemetry
|
|
72
76
|
traces: one trace per run, a span per operation, and — measured inside the process that
|
|
@@ -124,10 +128,10 @@ lc run <workflow> --env <env>
|
|
|
124
128
|
completed reads as discrete, observable steps; a demo against a fast mock wants a small
|
|
125
129
|
value.
|
|
126
130
|
- `--op-timeout S` / `--no-op-timeout` — how long one operation may run before it is
|
|
127
|
-
stopped and failed (§1.
|
|
131
|
+
stopped and failed (§1.9). The default is the environment root's `x-labcode.op_timeout`,
|
|
128
132
|
else 7200 real seconds. The two forms exclude each other.
|
|
129
133
|
- `--no-probe` — ignore the environment's `x-labcode.probe` policies and treat every
|
|
130
|
-
machine as reachable (§1.
|
|
134
|
+
machine as reachable (§1.6). The documents are still validated.
|
|
131
135
|
- `--ignore-resources` — switch the consumable model off. The environment's resource
|
|
132
136
|
declarations are still checked for shape but none is applied, so a bench whose devices
|
|
133
137
|
declare stocks nobody is tracking runs without the boundary saying what they held.
|
|
@@ -12,10 +12,12 @@ conformance validator, run at the `lc run` front door.
|
|
|
12
12
|
|
|
13
13
|
## 1. `x-labcode` in an environment (P5)
|
|
14
14
|
|
|
15
|
-
The extension
|
|
16
|
-
mode** and
|
|
17
|
-
**device** and
|
|
18
|
-
§1.
|
|
15
|
+
The extension answers two different questions, in two kinds of place. On a **process
|
|
16
|
+
mode**, a **transport route** and a **replenishment route** it says *what to run* (a
|
|
17
|
+
`script`, §1.1–§1.4); on a **device**, a **transporter** and a **replenisher** it says
|
|
18
|
+
*how to reach the machine* (a `connection`, §1.5), which is what lets a script be the
|
|
19
|
+
commands alone (§1.7). The division is the same each time: the machine has an address,
|
|
20
|
+
and the thing it does to something else has a procedure. Nowhere else — see §1.8.
|
|
19
21
|
|
|
20
22
|
An environment process mode (§5) may carry an `x-labcode` mapping holding a `script`: the
|
|
21
23
|
Python that carries out that `(process, mode)`.
|
|
@@ -39,19 +41,20 @@ still validates and schedules as plain v0. Only labcode interprets it.
|
|
|
39
41
|
|
|
40
42
|
### 1.1 Shape
|
|
41
43
|
|
|
42
|
-
- `x-labcode` MUST be a mapping. On a process mode
|
|
43
|
-
`script`.
|
|
44
|
+
- `x-labcode` MUST be a mapping. On a process mode, a transport route or a replenishment
|
|
45
|
+
route its only key is `script`.
|
|
44
46
|
- `x-labcode.script`, if present, MUST be a mapping with:
|
|
45
47
|
- `language`: MUST be `python`.
|
|
46
48
|
- `code`: MUST be a string (an implementation-provided Python function body).
|
|
47
49
|
- `flavor` (optional, default `raw`): MUST be `raw` or `sila2` — how `code` is meant to
|
|
48
|
-
be run (§1.
|
|
50
|
+
be run (§1.7). `sila2` is the **recommended** way to drive a SiLA2 lab: the code is the
|
|
49
51
|
commands alone and labcode supplies the clients. `raw` is the whole function body,
|
|
50
52
|
written by its author — the general escape hatch, and what a script that connects for
|
|
51
|
-
itself (or speaks something other than SiLA2) uses.
|
|
53
|
+
itself (or speaks something other than SiLA2) uses. On a **replenishment route**
|
|
54
|
+
`sila2` is an error in this version (§1.4).
|
|
52
55
|
- `endpoints` (**transport routes only**, optional, default `false`): MUST be a boolean —
|
|
53
56
|
whether this move is also given clients for the devices at either **end** of its route,
|
|
54
|
-
not only its `transporter` (§1.
|
|
57
|
+
not only its `transporter` (§1.7). A process mode may not declare it: a mode's machines
|
|
55
58
|
are the ones it lists.
|
|
56
59
|
|
|
57
60
|
**Unknown keys are an error** — in `x-labcode` at every position, and in the mappings it
|
|
@@ -121,12 +124,58 @@ output is verified. Success is "it ran without raising"; an exception is a grace
|
|
|
121
124
|
A route with no `x-labcode.script` runs as a plain timed move — the runner's material
|
|
122
125
|
bookkeeping only, with no device command (a warned no-op for a real move, from != to).
|
|
123
126
|
|
|
124
|
-
### 1.4 `x-labcode` on a
|
|
127
|
+
### 1.4 `x-labcode` on a replenishment route
|
|
125
128
|
|
|
126
|
-
An environment `
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
An environment `replenishments[]` route may carry an `x-labcode` with a `script`: the
|
|
130
|
+
Python that physically refills that device from that replenisher (e.g. commanding a
|
|
131
|
+
dispenser). Same shape as §1.1 (`language: python`, string `code`).
|
|
132
|
+
|
|
133
|
+
The procedure lives on the **route**, not on the machine — the same division transports
|
|
134
|
+
and transporters have. A dispenser's address is a property of the dispenser (§1.5); how it
|
|
135
|
+
fills *this* device is a property of the pair.
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
replenishments:
|
|
139
|
+
- replenisher: dispenser
|
|
140
|
+
device: reader
|
|
141
|
+
duration: 4 # ticks: the scheduler's estimate of the visit
|
|
142
|
+
x-labcode:
|
|
143
|
+
script:
|
|
144
|
+
language: python
|
|
145
|
+
code: |
|
|
146
|
+
import time
|
|
147
|
+
time.sleep(80) # real seconds: what the visit actually takes
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Calling convention (replenishment).** The script runs as a function body with these
|
|
151
|
+
locals: `replenisher`, `device` (the two machines the visit holds) and `amounts` — the
|
|
152
|
+
`{resource: amount}` the scheduler derived, which a planned refill fills to the device's
|
|
153
|
+
capacity. It is **not** given the duration: a real refill takes as long as it takes, and
|
|
154
|
+
the ticks the plan reserved are the scheduler's estimate rather than an instruction, so a
|
|
155
|
+
stand-in states its own time (which is why the two numbers above are written separately).
|
|
156
|
+
|
|
157
|
+
A replenishment script is **side-effect only**, as a transport's is: its return value is
|
|
158
|
+
ignored and no output is verified. An exception is a graceful failure — the refill ends
|
|
159
|
+
`failed` and the run stops, like any activity failure.
|
|
160
|
+
|
|
161
|
+
`flavor: sila2` is **an error** on a replenishment route in this version. A `sila2` script
|
|
162
|
+
is handed clients (§1.7), and which machine's clients a refill should receive — the
|
|
163
|
+
replenisher's, or both ends' as a transport route may ask for — is not settled. Refusing
|
|
164
|
+
says so; running the script without the clients it asked for would not. Use `raw` (the
|
|
165
|
+
default), which may of course connect for itself.
|
|
166
|
+
|
|
167
|
+
A route with no `x-labcode.script` runs as a plain timed visit: both machines are held for
|
|
168
|
+
the declared duration and nothing is commanded. That is a legitimate environment to write
|
|
169
|
+
— an operator tops the stock up while the schedule waits for them — and an easy one to
|
|
170
|
+
write by accident, so it is **warned** about, as a scriptless real move is.
|
|
171
|
+
|
|
172
|
+
### 1.5 `x-labcode` on a device, a transporter or a replenisher
|
|
173
|
+
|
|
174
|
+
An environment `devices[]`, `transporters[]` or `replenishers[]` entry may carry an
|
|
175
|
+
`x-labcode` with two keys: `connection` — **where that machine is**, written once per
|
|
176
|
+
physical machine rather than repeated in every script that drives it — and `probe` (§1.6)
|
|
177
|
+
— whether to check that it still answers. The three kinds are treated alike because they
|
|
178
|
+
are alike: each is a machine with an address that a run may find unreachable.
|
|
130
179
|
|
|
131
180
|
```yaml
|
|
132
181
|
devices:
|
|
@@ -165,7 +214,7 @@ a script uses it.
|
|
|
165
214
|
one.
|
|
166
215
|
|
|
167
216
|
A transport that declares `endpoints: true` is also handed the clients of the devices at
|
|
168
|
-
either **end** of its route (§1.
|
|
217
|
+
either **end** of its route (§1.7), but those are *not* required to declare a `connection`:
|
|
169
218
|
a route through a plain holding location is ordinary, and the end without an address is
|
|
170
219
|
simply not connected to (a **warning** when *neither* end has one, since then the request
|
|
171
220
|
does nothing). The transporter is the one that must be reachable, because it is the machine
|
|
@@ -176,7 +225,7 @@ honoured.
|
|
|
176
225
|
Declaring a `connection` on a device no script connects to is allowed — it is how an
|
|
177
226
|
environment is prepared before the scripts that use it are written.
|
|
178
227
|
|
|
179
|
-
### 1.
|
|
228
|
+
### 1.6 Availability — `probe`
|
|
180
229
|
|
|
181
230
|
A machine that stops answering should not keep receiving work. A `probe` policy asks labcode
|
|
182
231
|
to check the machines it knows how to reach, and to tell the scheduler about the ones it
|
|
@@ -263,7 +312,7 @@ belongs — as the operation that tried to command it failing.
|
|
|
263
312
|
document is still validated, so an environment that is wrong about probing stays wrong).
|
|
264
313
|
Each machine whose reachability changes is reported on stderr.
|
|
265
314
|
|
|
266
|
-
### 1.
|
|
315
|
+
### 1.7 Calling convention (`flavor: sila2`)
|
|
267
316
|
|
|
268
317
|
A `sila2` script is the **commands alone**: labcode opens a client to each of the
|
|
269
318
|
operation's machines, runs the code with them in scope, and closes them afterwards. On top
|
|
@@ -271,7 +320,7 @@ of the input ports of §1.2 (or the transport locals of §1.3), the code sees:
|
|
|
271
320
|
|
|
272
321
|
| name | meaning |
|
|
273
322
|
|---|---|
|
|
274
|
-
| `sila2_clients` | the clients by **machine id**, in the order the operation
|
|
323
|
+
| `sila2_clients` | the clients by **machine id**, in the order the operation names its machines: a mode's `devices[]` order, or — for a transport — its `transporter`, followed by the devices at either **end of the route** when it declares `endpoints: true`. Named, not held: a mode declaring `device_access: false` (ofplang-schedule §4.4.2) rests on its devices rather than occupying them, and its script is still handed their clients |
|
|
275
324
|
| `sila2_client` | the first of them — for a transport always its `transporter`; the one name a single-machine operation needs |
|
|
276
325
|
|
|
277
326
|
```yaml
|
|
@@ -371,7 +420,7 @@ equally available to a `raw` script, and stays visible in the code that depends
|
|
|
371
420
|
is to turn a hang into a diagnosable failure.
|
|
372
421
|
- **It is the inner of two limits.** This one is per command, chosen by the script that
|
|
373
422
|
knows what it is waiting for, and its failure can name the command that hung. The outer
|
|
374
|
-
one (§1.
|
|
423
|
+
one (§1.9) is per operation and lab-wide, and catches the hangs no script is watching
|
|
375
424
|
for. The outer default is looser than this one, so where both apply this is what fires.
|
|
376
425
|
- **Its timeout is in real seconds**, and is unrelated to the mode's `duration` — which is
|
|
377
426
|
an *estimate*, in environment time, for scheduling. A schedule's estimate is not a
|
|
@@ -381,11 +430,11 @@ equally available to a `raw` script, and stays visible in the code that depends
|
|
|
381
430
|
- A `sila2` script is only interpreted where the dialect is — in an environment
|
|
382
431
|
`x-labcode`. A workflow's own `script` (v0 §22) has no `flavor`.
|
|
383
432
|
|
|
384
|
-
### 1.
|
|
433
|
+
### 1.8 Where an `x-labcode` may appear
|
|
385
434
|
|
|
386
435
|
The positions of §1 are the only ones: the environment **root** (`probe` defaults and
|
|
387
|
-
`op_timeout`), `processes.<p>.modes[]`, `transports[]`, `devices[]
|
|
388
|
-
`transporters[]`. An `x-labcode`
|
|
436
|
+
`op_timeout`), `processes.<p>.modes[]`, `transports[]`, `replenishments[]`, `devices[]`,
|
|
437
|
+
`transporters[]` and `replenishers[]`. An `x-labcode`
|
|
389
438
|
anywhere else in the environment — on a process, beside `time` — is an **error**, as is a
|
|
390
439
|
key at a position that does not define it (a `connection` at the root, a `probe` on a mode).
|
|
391
440
|
Nothing would read it, and `ofplang-schedule` tolerates an `x-` key at *every* position
|
|
@@ -395,7 +444,7 @@ This rule covers the environment only. An `x-labcode` in the **workflow** is not
|
|
|
395
444
|
that document is portable v0, read by other implementations, and what extension keys it
|
|
396
445
|
carries is not labcode's business.
|
|
397
446
|
|
|
398
|
-
### 1.
|
|
447
|
+
### 1.9 Operation timeout — `op_timeout`
|
|
399
448
|
|
|
400
449
|
How long **one operation** may run before labcode stops waiting for it, in **real
|
|
401
450
|
seconds**. It lives at the environment root and nowhere else:
|
|
@@ -410,7 +459,7 @@ x-labcode:
|
|
|
410
459
|
way to say "no limit" and is an error; a machine may not declare one (a per-machine key
|
|
411
460
|
is an unknown key, §1.1).
|
|
412
461
|
- **One value for the whole lab.** The fine-grained waits belong to the scripts, which know
|
|
413
|
-
what they are waiting for (`settle`, §1.
|
|
462
|
+
what they are waiting for (`settle`, §1.7.1); this value only has to clear the longest
|
|
414
463
|
operation the lab legitimately runs. Its default (7200 s) is twice the `settle` default,
|
|
415
464
|
so where both apply the inner one — which can name the command — fires first.
|
|
416
465
|
- **The clock is real seconds**, from the moment the operation starts, covering everything
|
|
@@ -422,13 +471,13 @@ x-labcode:
|
|
|
422
471
|
failure — the run stops, the status document is written, the reason is reported, the exit
|
|
423
472
|
code is 1 — which is the point: without a limit, an instrument that stops answering
|
|
424
473
|
leaves a run polling with *no* status document and no reason at all.
|
|
425
|
-
- **A timeout is not a cancel**, exactly as in §1.
|
|
474
|
+
- **A timeout is not a cancel**, exactly as in §1.7.1: nothing here can stop a command the
|
|
426
475
|
instrument has already accepted. It keeps running, and the state that leaves behind —
|
|
427
476
|
including material a transport was part way through moving — is the operator's to
|
|
428
477
|
restore. The run stops there, so labcode's own picture of the lab is not relied on
|
|
429
478
|
afterwards.
|
|
430
479
|
- The machine that hung is **not** treated as unavailable: `op_timeout` does not add it to
|
|
431
|
-
the down machines (§1.
|
|
480
|
+
the down machines (§1.6), because "not answering" is not "not there", and re-routing work
|
|
432
481
|
onto other machines while this one is still physically running its command would make the
|
|
433
482
|
lab less consistent, not more.
|
|
434
483
|
- `lc run` overrides it for one run: `--op-timeout SECONDS`, or `--no-op-timeout` for no
|
|
@@ -450,6 +499,11 @@ For a dispatched `(process, mode)`, labcode resolves the code to run in this ord
|
|
|
450
499
|
will run as a typed-default no-op. This is allowed — convenient while mocking a device —
|
|
451
500
|
but `lc run` warns about it, so an unimplemented device is not silently a no-op.
|
|
452
501
|
|
|
502
|
+
**Transport and replenishment routes have no such chain.** There is nothing for them to
|
|
503
|
+
fall back to: a workflow describes neither a physical move nor a refill, so the route's
|
|
504
|
+
own `x-labcode.script` is the only source. A route without one runs as a plain timed
|
|
505
|
+
activity (§1.3, §1.4), warned about for the same reason as above.
|
|
506
|
+
|
|
453
507
|
## 3. Execution model
|
|
454
508
|
|
|
455
509
|
Each dispatched operation runs in its own child process (real, wall-clock-paced); the
|
|
@@ -457,7 +511,7 @@ runner discovers completion by polling, so a multi-minute computation never bloc
|
|
|
457
511
|
The advisory `duration` is the scheduler's estimate; the real duration is the script's.
|
|
458
512
|
A script error (an exception, a wrong/ missing output name, a non-conformant value) is a
|
|
459
513
|
graceful runtime failure (§22.2): the operation ends `failed` and the run stops. An
|
|
460
|
-
operation that never finishes at all ends the same way once it passes `op_timeout` (§1.
|
|
514
|
+
operation that never finishes at all ends the same way once it passes `op_timeout` (§1.9) —
|
|
461
515
|
polling for completion is not the same as waiting forever for it.
|
|
462
516
|
|
|
463
517
|
Cadence: the nominal poll period is `poll_interval × seconds_per_tick`. labcode defaults
|
|
@@ -503,7 +557,7 @@ statement that the cycle was cheap.
|
|
|
503
557
|
|
|
504
558
|
So: keep the budget comfortably larger than the cycle cost. What the cycle costs is not
|
|
505
559
|
fixed — replanning grows with the workflow, and a dialect step such as availability probing
|
|
506
|
-
(§1.
|
|
560
|
+
(§1.6) can add seconds — so the margin wants to be generous rather than exact. A run whose
|
|
507
561
|
recorded times matter (a checked-in example, a comparison against the plan's estimates)
|
|
508
562
|
needs this to hold; a run that only has to *complete* does not.
|
|
509
563
|
|
|
@@ -562,9 +616,13 @@ ids per physical Object swaps in `RealUuid4Generator` (via
|
|
|
562
616
|
|
|
563
617
|
## 5. Not yet in this version (roadmap)
|
|
564
618
|
|
|
619
|
+
- **`flavor: sila2` on a replenishment route** — refused today (§1.4). What has to be
|
|
620
|
+
settled first is which machine's clients a refill script receives: the replenisher's
|
|
621
|
+
alone, or both ends' as a transport route may ask for with `endpoints`. Until then a
|
|
622
|
+
refill that must speak SiLA2 uses a `raw` script and connects for itself.
|
|
565
623
|
- **A deeper probe** — asking a machine something (a SiLA2 property read) rather than only
|
|
566
624
|
opening a connection to it, so "answering" can be checked and not just "listening"
|
|
567
|
-
(§1.
|
|
625
|
+
(§1.6). It would be an opt-in depth, since it costs a real exchange per check.
|
|
568
626
|
- **Probing in parallel** — checking machines concurrently, so a lab with many unreachable
|
|
569
|
-
machines does not pay for them one timeout at a time (§1.
|
|
570
|
-
- **TLS** — the fields a secure connection needs, lifting the restriction in §1.
|
|
627
|
+
machines does not pay for them one timeout at a time (§1.6).
|
|
628
|
+
- **TLS** — the fields a secure connection needs, lifting the restriction in §1.5.
|
|
@@ -136,7 +136,7 @@ x-labcode:
|
|
|
136
136
|
|
|
137
137
|
What the flavor supplies is connections, nothing more. Waiting for an *observable* command to
|
|
138
138
|
finish stays in the script — but the loop itself does not have to be rewritten each time:
|
|
139
|
-
labcode ships `settle` in **`labcode.sila2_commands`** (§1.
|
|
139
|
+
labcode ships `settle` in **`labcode.sila2_commands`** (§1.7.1), reached by the ordinary import
|
|
140
140
|
above. Nothing is injected, so a script that does not import it does not have it, and a `raw`
|
|
141
141
|
script has exactly the same access as this one. A live connection is worth a name that appears
|
|
142
142
|
out of nowhere; an import is not.
|
|
@@ -149,14 +149,14 @@ instrument carries on, leaving the lab for the operator to restore.
|
|
|
149
149
|
The names a script hands a machine still have to be the ones that machine knows — for the arm
|
|
150
150
|
those are its **station** names (`Base1`, `Base4`, …), not labcode's `device.spot`, and one it
|
|
151
151
|
does not know fails with `InvalidStation` at the moment of use. See
|
|
152
|
-
[`../SPECIFICATIONS.md`](../SPECIFICATIONS.md) §1.
|
|
152
|
+
[`../SPECIFICATIONS.md`](../SPECIFICATIONS.md) §1.5 and §1.7, and the plate-cycle section below
|
|
153
153
|
for why the two vocabularies do not meet anywhere but in a transport script.
|
|
154
154
|
|
|
155
155
|
### If a machine stops answering
|
|
156
156
|
|
|
157
157
|
Neither environment here asks for it, but labcode can **check that a machine is reachable**
|
|
158
158
|
and schedule around the ones that are not — see
|
|
159
|
-
[`../SPECIFICATIONS.md`](../SPECIFICATIONS.md) §1.
|
|
159
|
+
[`../SPECIFICATIONS.md`](../SPECIFICATIONS.md) §1.6. Adding this to a device (or a
|
|
160
160
|
transporter) that declares a `connection`:
|
|
161
161
|
|
|
162
162
|
```yaml
|
|
@@ -175,7 +175,7 @@ run without editing the environment.
|
|
|
175
175
|
|
|
176
176
|
Probing catches a machine that is not there; it does not catch one that accepted a command
|
|
177
177
|
and never came back. That is what the **operation timeout** is for
|
|
178
|
-
([`../SPECIFICATIONS.md`](../SPECIFICATIONS.md) §1.
|
|
178
|
+
([`../SPECIFICATIONS.md`](../SPECIFICATIONS.md) §1.9): every operation has a real-seconds
|
|
179
179
|
deadline (7200 s by default, declared lab-wide at the environment root as
|
|
180
180
|
`x-labcode.op_timeout`), and one that passes it is stopped and failed with the reason
|
|
181
181
|
`op_timeout` — a run that ends with a status document and a reason instead of one that
|
|
@@ -324,7 +324,7 @@ dependency runs one way, and the lab's world model is not labcode's to edit.
|
|
|
324
324
|
**Lids and doors.** In the lab's world model a closed lid or door makes that spot inaccessible,
|
|
325
325
|
and an item cannot be moved into or out of an inaccessible spot. So the transport that delivers
|
|
326
326
|
the plate is what opens the instrument: three of the five routes declare `endpoints: true` and
|
|
327
|
-
are handed clients for the devices at either end as well as for the arm (§1.
|
|
327
|
+
are handed clients for the devices at either end as well as for the arm (§1.7). That is sound
|
|
328
328
|
because the scheduler has already given the move both instruments for its whole duration —
|
|
329
329
|
nothing else can be using them meanwhile.
|
|
330
330
|
|
|
@@ -325,6 +325,18 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
325
325
|
print(f"makespan : {status.get('now')}")
|
|
326
326
|
print(f"result boundary : {result.result_boundary}")
|
|
327
327
|
|
|
328
|
+
# Why the run stopped, when it did. `RunResult.failure` (D36) is a machine-readable
|
|
329
|
+
# `kind` and a human-readable `detail` -- the reason the failing operation gave, which
|
|
330
|
+
# `lc run` prints and this script was discarding. The status document names the
|
|
331
|
+
# activities that did not complete but never says why any of them failed, so without
|
|
332
|
+
# this the operator is left to guess between an instrument that refused the command,
|
|
333
|
+
# one that stopped answering, and a station name the arm does not have.
|
|
334
|
+
if result.failed and result.failure is not None:
|
|
335
|
+
print(
|
|
336
|
+
f"run failure : {result.failure.kind}: {result.failure.detail}",
|
|
337
|
+
file=sys.stderr,
|
|
338
|
+
)
|
|
339
|
+
|
|
328
340
|
try:
|
|
329
341
|
check_outcome(status, result.result_boundary, observation)
|
|
330
342
|
except CheckFailed as error:
|
|
@@ -281,6 +281,22 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
281
281
|
print(f"makespan : {status.get('now')}")
|
|
282
282
|
print(f"result boundary : {result.result_boundary}")
|
|
283
283
|
|
|
284
|
+
# Why the run stopped, when it did. `RunResult.failure` (D36) is a machine-readable
|
|
285
|
+
# `kind` and a human-readable `detail` -- the reason the failing operation gave, which
|
|
286
|
+
# `lc run` prints and this script was discarding. The status document names the
|
|
287
|
+
# activities that did not complete but never says why any of them failed, so without
|
|
288
|
+
# this the operator is left to guess between an instrument that refused the command,
|
|
289
|
+
# one that stopped answering, and a station name the arm does not have.
|
|
290
|
+
#
|
|
291
|
+
# It matters most here. This example is also the one that walks through what a run does
|
|
292
|
+
# when a machine stops answering -- before an operation and in the middle of one -- so
|
|
293
|
+
# the failure it exists to demonstrate was the failure it was declining to name.
|
|
294
|
+
if result.failed and result.failure is not None:
|
|
295
|
+
print(
|
|
296
|
+
f"run failure : {result.failure.kind}: {result.failure.detail}",
|
|
297
|
+
file=sys.stderr,
|
|
298
|
+
)
|
|
299
|
+
|
|
284
300
|
try:
|
|
285
301
|
check_outcome(status, result.result_boundary, observation)
|
|
286
302
|
except CheckFailed as error:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: labcode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: labcode -- a dialect wrapper over the Object-Flow Programming Language toolchain
|
|
5
5
|
Author-email: Kazunari Kaizu <kwaizu@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -20,9 +20,9 @@ Classifier: Typing :: Typed
|
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: ofplang-validate
|
|
24
|
-
Requires-Dist: ofplang-schedule
|
|
25
|
-
Requires-Dist: ofplang-run
|
|
23
|
+
Requires-Dist: ofplang-validate<0.3,>=0.2
|
|
24
|
+
Requires-Dist: ofplang-schedule<0.4,>=0.3
|
|
25
|
+
Requires-Dist: ofplang-run<0.5,>=0.4
|
|
26
26
|
Provides-Extra: test
|
|
27
27
|
Requires-Dist: pytest>=7.0; extra == "test"
|
|
28
28
|
Requires-Dist: opentelemetry-sdk>=1.20; extra == "test"
|
|
@@ -98,19 +98,23 @@ repository, and what labcode adds to it in [`SPECIFICATIONS.md`](SPECIFICATIONS.
|
|
|
98
98
|
|
|
99
99
|
What `lc run` brings of its own, beyond dispatching:
|
|
100
100
|
|
|
101
|
-
- **the labcode backend** — each
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
- **the labcode backend** — each operation's `x-labcode.script` runs out-of-process on a
|
|
102
|
+
wall clock (§1.2–§1.4), so a real operation that takes minutes does not block the
|
|
103
|
+
replan loop, and an operation that never returns is stopped by **`op_timeout`**
|
|
104
|
+
(§1.9; `--op-timeout` / `--no-op-timeout`).
|
|
105
|
+
- **refilling a stock** — where the environment says a replenisher can reach a device, a
|
|
106
|
+
stock that would run out is topped up rather than ending the run: the refill's own
|
|
107
|
+
script runs like any other, holding both machines while it works (§1.4). See
|
|
108
|
+
*Refilling a stock* below.
|
|
105
109
|
- **the dialect front door** — the environment's `x-labcode` extension is validated
|
|
106
110
|
before anything runs, on top of the portable-v0 check `lc validate` performs (§1, §2).
|
|
107
111
|
- **availability probing** — each machine is checked as often as its `probe` policy says,
|
|
108
112
|
and one that cannot be reached is taken out of the environment the scheduler plans
|
|
109
|
-
against, so the run routes around it (§1.
|
|
113
|
+
against, so the run routes around it (§1.6; `--no-probe`).
|
|
110
114
|
- **object identity** — the reserved `_id` view key is declared on Object types and minted
|
|
111
115
|
per object, so a physical thing can be followed through a run (§4).
|
|
112
116
|
- **`flavor: sila2`** — a script that speaks SiLA2 gets its clients opened around it
|
|
113
|
-
(§1.
|
|
117
|
+
(§1.7). The client library itself is the `sila2` extra: `pip install labcode[sila2]`,
|
|
114
118
|
installed into whichever interpreter runs the scripts.
|
|
115
119
|
- **recording a run** — with `--trace`, what the run did is recorded as OpenTelemetry
|
|
116
120
|
traces: one trace per run, a span per operation, and — measured inside the process that
|
|
@@ -168,10 +172,10 @@ lc run <workflow> --env <env>
|
|
|
168
172
|
completed reads as discrete, observable steps; a demo against a fast mock wants a small
|
|
169
173
|
value.
|
|
170
174
|
- `--op-timeout S` / `--no-op-timeout` — how long one operation may run before it is
|
|
171
|
-
stopped and failed (§1.
|
|
175
|
+
stopped and failed (§1.9). The default is the environment root's `x-labcode.op_timeout`,
|
|
172
176
|
else 7200 real seconds. The two forms exclude each other.
|
|
173
177
|
- `--no-probe` — ignore the environment's `x-labcode.probe` policies and treat every
|
|
174
|
-
machine as reachable (§1.
|
|
178
|
+
machine as reachable (§1.6). The documents are still validated.
|
|
175
179
|
- `--ignore-resources` — switch the consumable model off. The environment's resource
|
|
176
180
|
declarations are still checked for shape but none is applied, so a bench whose devices
|
|
177
181
|
declare stocks nobody is tracking runs without the boundary saying what they held.
|
|
@@ -47,9 +47,16 @@ classifiers = [
|
|
|
47
47
|
# every real-lab example already happens on it, and run's own floor requires it anyway
|
|
48
48
|
# (an older scheduler reports a running refill free before it is).
|
|
49
49
|
dependencies = [
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
# Upper bounds are deliberate. A sibling's minor version is where a
|
|
51
|
+
# specification revision lands, and a revision may reject documents an earlier one
|
|
52
|
+
# accepted: the 0.1 revision removed transform kinds and an output mode and renamed
|
|
53
|
+
# a process marker. Without a cap, an already-published release of this package
|
|
54
|
+
# would pick up the next such sibling and start refusing its own examples. The
|
|
55
|
+
# bounds cannot protect what is already on PyPI without one, which is why they go
|
|
56
|
+
# in now rather than at the first breakage.
|
|
57
|
+
"ofplang-validate>=0.2,<0.3",
|
|
58
|
+
"ofplang-schedule>=0.3,<0.4",
|
|
59
|
+
"ofplang-run>=0.4,<0.5",
|
|
53
60
|
]
|
|
54
61
|
|
|
55
62
|
[project.urls]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|