devagent-ignition 0.1.0__py3-none-any.whl
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.
- devagent_hmi.py +471 -0
- devagent_ignition-0.1.0.dist-info/METADATA +204 -0
- devagent_ignition-0.1.0.dist-info/RECORD +133 -0
- devagent_ignition-0.1.0.dist-info/WHEEL +4 -0
- devagent_ignition-0.1.0.dist-info/entry_points.txt +2 -0
- devagent_spatial/__init__.py +1 -0
- devagent_spatial/_resources/README.md +187 -0
- devagent_spatial/_resources/RELEASE_MANIFEST.json +312 -0
- devagent_spatial/_resources/SHA256SUMS.txt +270 -0
- devagent_spatial/_resources/SOURCE_PROVENANCE.json +186 -0
- devagent_spatial/_resources/assets/agent.js +102 -0
- devagent_spatial/_resources/assets/creator.css +247 -0
- devagent_spatial/_resources/assets/creator_2d.mjs +16 -0
- devagent_spatial/_resources/assets/creator_original_r9.mjs +58 -0
- devagent_spatial/_resources/assets/creator_original_v8.mjs +120 -0
- devagent_spatial/_resources/assets/creator_r9_diagnostics.mjs +53 -0
- devagent_spatial/_resources/assets/creator_scene.mjs +128 -0
- devagent_spatial/_resources/assets/creator_ui.mjs +60 -0
- devagent_spatial/_resources/assets/customer_documents.js +110 -0
- devagent_spatial/_resources/assets/equipment_controls.mjs +27 -0
- devagent_spatial/_resources/assets/facility2d.css +24 -0
- devagent_spatial/_resources/assets/facility2d_app.mjs +45 -0
- devagent_spatial/_resources/assets/facility2d_core.mjs +114 -0
- devagent_spatial/_resources/assets/facility2d_simulation.mjs +167 -0
- devagent_spatial/_resources/assets/facility2d_viewer.html +7 -0
- devagent_spatial/_resources/assets/fallback_scene.js +17 -0
- devagent_spatial/_resources/assets/flow_geometry.mjs +218 -0
- devagent_spatial/_resources/assets/harness.js +78 -0
- devagent_spatial/_resources/assets/live_design_review.js +129 -0
- devagent_spatial/_resources/assets/operations_ui.js +305 -0
- devagent_spatial/_resources/assets/operator_viewer.html +64 -0
- devagent_spatial/_resources/assets/project_package.js +52 -0
- devagent_spatial/_resources/assets/r9_transport.mjs +70 -0
- devagent_spatial/_resources/assets/runtime_feed.js +30 -0
- devagent_spatial/_resources/assets/studio.html +139 -0
- devagent_spatial/_resources/assets/style.css +191 -0
- devagent_spatial/_resources/assets/test_workspace.html +195 -0
- devagent_spatial/_resources/assets/three_scene.js +442 -0
- devagent_spatial/_resources/assets/unified_engineer.js +101 -0
- devagent_spatial/_resources/assets/v076_fat_ui.js +40 -0
- devagent_spatial/_resources/assets/v078_command_ui.js +44 -0
- devagent_spatial/_resources/assets/viewer.html +45 -0
- devagent_spatial/_resources/assets/viewer.js +112 -0
- devagent_spatial/_resources/design-system.json +130 -0
- devagent_spatial/_resources/examples/CV101-command-contract-PENDING.json +30 -0
- devagent_spatial/_resources/examples/cv101_guided.json +272 -0
- devagent_spatial/_resources/examples/make_examples.py +36 -0
- devagent_spatial/_resources/examples/single_floor.json +444 -0
- devagent_spatial/_resources/examples/v0771_training_wizard.json +11 -0
- devagent_spatial/_resources/examples/warehouse-3floor-standard-2d.json +708 -0
- devagent_spatial/_resources/examples/warehouse-operations-synthetic.json +1186 -0
- devagent_spatial/_resources/examples/warehouse-v07-lab-bindings-PENDING.json +372 -0
- devagent_spatial/_resources/examples/warehouse.json +708 -0
- devagent_spatial/_resources/examples/water.json +288 -0
- devagent_spatial/_resources/reference/r9-golden/REFERENCE_SHA256.json +13 -0
- devagent_spatial/_resources/reference/r9-golden/web/app.mjs +745 -0
- devagent_spatial/_resources/reference/r9-golden/web/index.html +213 -0
- devagent_spatial/_resources/reference/r9-golden/web/lab_panel.mjs +53 -0
- devagent_spatial/_resources/reference/r9-golden/web/model.mjs +367 -0
- devagent_spatial/_resources/reference/r9-golden/web/state_adapter.mjs +78 -0
- devagent_spatial/_resources/reference/r9-golden/web/styles.css +76 -0
- devagent_spatial/_resources/reference/r9-original/README_R9.md +27 -0
- devagent_spatial/_resources/reference/r9-original/SHA256SUMS.txt +63 -0
- devagent_spatial/_resources/reference/r9-original/SNAPSHOT_SHA256SUMS.txt +16 -0
- devagent_spatial/_resources/reference/r9-original/flow_routes.json +527 -0
- devagent_spatial/_resources/reference/r9-original/flow_sim.py +117 -0
- devagent_spatial/_resources/reference/r9-original/lab_control.py +73 -0
- devagent_spatial/_resources/reference/r9-original/web/app.mjs +745 -0
- devagent_spatial/_resources/reference/r9-original/web/index.html +213 -0
- devagent_spatial/_resources/reference/r9-original/web/lab_panel.mjs +53 -0
- devagent_spatial/_resources/reference/r9-original/web/model.mjs +367 -0
- devagent_spatial/_resources/reference/r9-original/web/state_adapter.mjs +78 -0
- devagent_spatial/_resources/reference/r9-original/web/styles.css +76 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/LICENSE-three.txt +21 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/SHA256SUMS.txt +4 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/addons/controls/OrbitControls.js +1860 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/three.core.js +58773 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/three.module.js +18251 -0
- devagent_spatial/_resources/reference/v8-original/README.md +36 -0
- devagent_spatial/_resources/reference/v8-original/SHA256SUMS.txt +12 -0
- devagent_spatial/_resources/reference/v8-original/model.py +124 -0
- devagent_spatial/_resources/reference/v8-original/run_demo.sh +4 -0
- devagent_spatial/_resources/reference/v8-original/server.py +100 -0
- devagent_spatial/_resources/reference/v8-original/tests/browser_smoke.py +50 -0
- devagent_spatial/_resources/reference/v8-original/tests/test_v8.py +144 -0
- devagent_spatial/_resources/reference/v8-original/topology.json +24 -0
- devagent_spatial/_resources/reference/v8-original/web/app.js +114 -0
- devagent_spatial/_resources/reference/v8-original/web/index.html +22 -0
- devagent_spatial/_resources/reference/v8-original/web/simulator.html +1 -0
- devagent_spatial/_resources/reference/v8-original/web/simulator.js +6 -0
- devagent_spatial/_resources/reference/v8-original/web/style.css +16 -0
- devagent_spatial/_resources/sample_customer_inputs/Plant_Equipment_Register.csv +4 -0
- devagent_spatial/_resources/sample_customer_inputs/Plant_Flow_Requirements.txt +5 -0
- devagent_spatial/_resources/sample_customer_inputs/Raw_PLC_Tag_Export.csv +4 -0
- devagent_spatial/_resources/templates/warehouse_connections.csv +12 -0
- devagent_spatial/_resources/templates/warehouse_equipment.csv +21 -0
- devagent_spatial/_resources/templates/warehouse_signals.csv +34 -0
- devagent_spatial/_resources/templates/water_connections.csv +7 -0
- devagent_spatial/_resources/templates/water_equipment.csv +10 -0
- devagent_spatial/_resources/templates/water_signals.csv +12 -0
- devagent_spatial/agent_harness.py +299 -0
- devagent_spatial/agent_runtime.py +314 -0
- devagent_spatial/contract_opc_fat.py +216 -0
- devagent_spatial/contract_opc_source.py +265 -0
- devagent_spatial/control_intents.py +38 -0
- devagent_spatial/customer_intake.py +235 -0
- devagent_spatial/engineering_expertise.py +114 -0
- devagent_spatial/generator.py +201 -0
- devagent_spatial/ignition_native.py +234 -0
- devagent_spatial/ignition_project.py +144 -0
- devagent_spatial/live_lab_binding.py +128 -0
- devagent_spatial/model.py +297 -0
- devagent_spatial/native_reference.py +69 -0
- devagent_spatial/opc_snapshot.py +87 -0
- devagent_spatial/operations.py +284 -0
- devagent_spatial/operations_capture.py +47 -0
- devagent_spatial/operations_lab.py +64 -0
- devagent_spatial/original_r9.py +169 -0
- devagent_spatial/original_v8.py +81 -0
- devagent_spatial/preflight.py +140 -0
- devagent_spatial/preview_simulation.py +189 -0
- devagent_spatial/project_package.py +101 -0
- devagent_spatial/qualification.py +152 -0
- devagent_spatial/strict_json.py +29 -0
- devagent_spatial/studio.py +894 -0
- devagent_spatial/studio_workflow.py +277 -0
- devagent_spatial/test_reporting.py +87 -0
- devagent_spatial/v076_opc_fat.py +180 -0
- devagent_spatial/v078_commands.py +277 -0
- devagent_spatial/v078_native_commands.py +161 -0
- devagent_spatial/v07_fat.py +110 -0
- devagent_spatial/v07_native.py +318 -0
- devagent_spatial/v083_native.py +349 -0
devagent_hmi.py
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""DevAgent Spatial HMI Engine — independent local design and validation tool.
|
|
3
|
+
|
|
4
|
+
This program NEVER imports into Ignition, modifies PLC tags, or writes OPC UA.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import argparse
|
|
8
|
+
import json
|
|
9
|
+
import zipfile
|
|
10
|
+
from functools import partial
|
|
11
|
+
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from devagent_spatial.generator import build, BuildHold, ROOT
|
|
14
|
+
from devagent_spatial.model import qualify
|
|
15
|
+
from devagent_spatial.strict_json import loads as strict_loads
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main(argv=None):
|
|
19
|
+
p=argparse.ArgumentParser(description='DevAgent Spatial HMI — model-driven preview and static FAT contract generator (read-only)')
|
|
20
|
+
sub=p.add_subparsers(dest='action',required=True)
|
|
21
|
+
a=sub.add_parser('validate',help='Validate JSON project contract without writing files')
|
|
22
|
+
a.add_argument('--model',type=Path,required=True)
|
|
23
|
+
a=sub.add_parser('build',help='Create a NEW preview artifact directory (no overwrite)')
|
|
24
|
+
a.add_argument('--model',type=Path,required=True)
|
|
25
|
+
a.add_argument('--out',type=Path,required=True)
|
|
26
|
+
a.add_argument('--vendor-three',type=Path,default=None,help='Existing LOCAL Three.js r180 vendor directory with license; source is not changed')
|
|
27
|
+
a=sub.add_parser('studio',help='Start localhost customer project creator, no remote bind')
|
|
28
|
+
a.add_argument('--port',type=int,default=8895)
|
|
29
|
+
a.add_argument('--workspace',type=Path,default=Path.home()/'.local/share/devagent/spatial-projects')
|
|
30
|
+
a.add_argument('--vendor-three',type=Path,default=None,help='Optional local r180 vendor folder, e.g. R9 viewer web/vendor')
|
|
31
|
+
a=sub.add_parser('preview',help='Serve an existing generated preview over localhost HTTP')
|
|
32
|
+
a.add_argument('--project-dir',type=Path,required=True)
|
|
33
|
+
a.add_argument('--port',type=int,default=8896)
|
|
34
|
+
a=sub.add_parser('opc-snapshot',help='READ-ONLY isolated-lab OPC UA node/type/quality verification; no trigger or HMI claims')
|
|
35
|
+
a.add_argument('--model',type=Path,required=True)
|
|
36
|
+
a.add_argument('--endpoint',required=True)
|
|
37
|
+
a.add_argument('--lab-confirm',required=True)
|
|
38
|
+
a=sub.add_parser('qualify',help='Independent artifact integrity/traceability audit, preview or production gate')
|
|
39
|
+
a.add_argument('--project-dir',type=Path,required=True)
|
|
40
|
+
a.add_argument('--purpose',choices=['preview','production'],default='preview')
|
|
41
|
+
a=sub.add_parser('audit-ignition',help='Read-only safety audit of a native Ignition Perspective ZIP (not import qualification)')
|
|
42
|
+
a.add_argument('--project-zip',type=Path,required=True)
|
|
43
|
+
a=sub.add_parser('ignition-list',help='List views in a customer-provided native Ignition project export, without mutation')
|
|
44
|
+
a.add_argument('--reference-zip',type=Path,required=True)
|
|
45
|
+
a=sub.add_parser('ignition-candidate',help='Build read-only native Perspective view RESOURCE overlay (not a verified project export)')
|
|
46
|
+
a.add_argument('--model',type=Path,required=True)
|
|
47
|
+
a.add_argument('--reference-zip',type=Path,required=True)
|
|
48
|
+
a.add_argument('--reference-sha256',required=True)
|
|
49
|
+
a.add_argument('--template-view',required=True)
|
|
50
|
+
a.add_argument('--out',type=Path,required=True)
|
|
51
|
+
a=sub.add_parser('ignition-verify-candidate',help='Independently compare view candidate against original customer model and reference')
|
|
52
|
+
a.add_argument('--model',type=Path,required=True)
|
|
53
|
+
a.add_argument('--reference-zip',type=Path,required=True)
|
|
54
|
+
a.add_argument('--reference-sha256',required=True)
|
|
55
|
+
a.add_argument('--template-view',required=True)
|
|
56
|
+
a.add_argument('--candidate',type=Path,required=True)
|
|
57
|
+
a=sub.add_parser('ignition-project-candidate',help='Build a NEW minimal read-only isolated Ignition project ZIP candidate; never imports')
|
|
58
|
+
a.add_argument('--model',type=Path,required=True)
|
|
59
|
+
a.add_argument('--reference-zip',type=Path,required=True)
|
|
60
|
+
a.add_argument('--reference-sha256',required=True)
|
|
61
|
+
a.add_argument('--template-view',required=True)
|
|
62
|
+
a.add_argument('--new-project-name',required=True)
|
|
63
|
+
a.add_argument('--confirm',required=True)
|
|
64
|
+
a.add_argument('--out',type=Path,required=True)
|
|
65
|
+
a=sub.add_parser('ignition-verify-project',help='Independently verify a new minimal project candidate')
|
|
66
|
+
a.add_argument('--model',type=Path,required=True)
|
|
67
|
+
a.add_argument('--reference-zip',type=Path,required=True)
|
|
68
|
+
a.add_argument('--reference-sha256',required=True)
|
|
69
|
+
a.add_argument('--template-view',required=True)
|
|
70
|
+
a.add_argument('--new-project-name',required=True)
|
|
71
|
+
a.add_argument('--candidate',type=Path,required=True)
|
|
72
|
+
a=sub.add_parser('ops-studio-lab',help='Create copy-only Studio import CSV + lab-only remapped model, original unchanged')
|
|
73
|
+
a.add_argument('--model',type=Path,required=True)
|
|
74
|
+
a.add_argument('--out-dir',type=Path,required=True)
|
|
75
|
+
a.add_argument('--confirm',required=True)
|
|
76
|
+
a=sub.add_parser('ops-demo',help='Create synthetic LAB ONLY alarm/KPI replay JSON, never live proof')
|
|
77
|
+
a.add_argument('--model',type=Path,required=True)
|
|
78
|
+
a.add_argument('--out',type=Path,required=True)
|
|
79
|
+
a=sub.add_parser('ops-evaluate',help='Validate external isolated-lab observation frames by exact customer tag identity')
|
|
80
|
+
a.add_argument('--model',type=Path,required=True)
|
|
81
|
+
a.add_argument('--frames',type=Path,required=True)
|
|
82
|
+
a.add_argument('--out',type=Path,required=True)
|
|
83
|
+
a=sub.add_parser('ops-capture',help='Bounded READ ONLY OPC UA capture on approved isolated lab endpoint')
|
|
84
|
+
a.add_argument('--model',type=Path,required=True)
|
|
85
|
+
a.add_argument('--endpoint',required=True)
|
|
86
|
+
a.add_argument('--lab-confirm',required=True)
|
|
87
|
+
a.add_argument('--samples',type=int,default=3)
|
|
88
|
+
a.add_argument('--out',type=Path,required=True)
|
|
89
|
+
a=sub.add_parser('v07-binding-skeleton',help='Create NEW customer lab-tag manifest template, with NO guessed tag paths')
|
|
90
|
+
a.add_argument('--model',type=Path,required=True)
|
|
91
|
+
a.add_argument('--out',type=Path,required=True)
|
|
92
|
+
a=sub.add_parser('v07-native-compile',help='Compile isolated native Perspective project candidate with read-only lab bindings + route')
|
|
93
|
+
a.add_argument('--model',type=Path,required=True)
|
|
94
|
+
a.add_argument('--binding-manifest',type=Path,required=True)
|
|
95
|
+
a.add_argument('--reference-zip',type=Path,required=True)
|
|
96
|
+
a.add_argument('--reference-sha256',required=True)
|
|
97
|
+
a.add_argument('--template-view',required=True)
|
|
98
|
+
a.add_argument('--new-project-name',required=True)
|
|
99
|
+
a.add_argument('--confirm',required=True)
|
|
100
|
+
a.add_argument('--out',type=Path,required=True)
|
|
101
|
+
a=sub.add_parser('v07-native-verify',help='Byte-for-byte independent verifier for V0.7 offline project candidate')
|
|
102
|
+
a.add_argument('--model',type=Path,required=True)
|
|
103
|
+
a.add_argument('--binding-manifest',type=Path,required=True)
|
|
104
|
+
a.add_argument('--reference-zip',type=Path,required=True)
|
|
105
|
+
a.add_argument('--reference-sha256',required=True)
|
|
106
|
+
a.add_argument('--template-view',required=True)
|
|
107
|
+
a.add_argument('--new-project-name',required=True)
|
|
108
|
+
a.add_argument('--candidate',type=Path,required=True)
|
|
109
|
+
a=sub.add_parser('v07-fat-plan',help='Create unexecuted per-signal FAT plan: signal coverage and quality transitions')
|
|
110
|
+
a.add_argument('--model',type=Path,required=True)
|
|
111
|
+
a.add_argument('--binding-manifest',type=Path,required=True)
|
|
112
|
+
a.add_argument('--out',type=Path,required=True)
|
|
113
|
+
for action in ('v083-native-compile', 'v083-native-verify'):
|
|
114
|
+
a=sub.add_parser(action,help='Create/verify native operator candidate; UI selection only, no command authority')
|
|
115
|
+
a.add_argument('--model',type=Path,required=True)
|
|
116
|
+
a.add_argument('--binding-manifest',type=Path,required=True)
|
|
117
|
+
a.add_argument('--operator-policy',type=Path,help='Reviewed PE association and freshness policy; omitted means all pending')
|
|
118
|
+
a.add_argument('--reference-zip',type=Path,required=True)
|
|
119
|
+
a.add_argument('--reference-sha256',required=True)
|
|
120
|
+
a.add_argument('--template-view',required=True)
|
|
121
|
+
a.add_argument('--new-project-name',required=True)
|
|
122
|
+
if action=='v083-native-compile':
|
|
123
|
+
a.add_argument('--confirm',required=True)
|
|
124
|
+
a.add_argument('--out',type=Path,required=True)
|
|
125
|
+
else:
|
|
126
|
+
a.add_argument('--candidate',type=Path,required=True)
|
|
127
|
+
a=sub.add_parser('v07-fat-assess',help='Evaluate supplied OPC/Tag/HMI phase consistency; never asserts live FAT PASS')
|
|
128
|
+
a.add_argument('--candidate',type=Path,required=True)
|
|
129
|
+
a.add_argument('--model',type=Path,required=True)
|
|
130
|
+
a.add_argument('--binding-manifest',type=Path,required=True)
|
|
131
|
+
a.add_argument('--observations',type=Path,required=True)
|
|
132
|
+
a.add_argument('--out',type=Path,required=True)
|
|
133
|
+
a=sub.add_parser('v076-opc-plan',help='Generate synthetic OPC UA lab plan only; no protocol execution')
|
|
134
|
+
a.add_argument('--model',type=Path,required=True)
|
|
135
|
+
a.add_argument('--out',type=Path,required=True)
|
|
136
|
+
a=sub.add_parser('v076-opc-fat',help='Execute READ-ONLY-TO-CLIENTS fake OPC UA loopback FAT with independent client; NO IGNITION CLAIM')
|
|
137
|
+
a.add_argument('--model',type=Path,required=True)
|
|
138
|
+
a.add_argument('--out',type=Path,required=True)
|
|
139
|
+
a.add_argument('--port',type=int,default=4898)
|
|
140
|
+
a.add_argument('--confirm',required=True)
|
|
141
|
+
a=sub.add_parser('v078-command-template',help='Create UNAPPROVED lab-only Device Command Contract for a declared equipment ID')
|
|
142
|
+
a.add_argument('--model',type=Path,required=True)
|
|
143
|
+
a.add_argument('--asset-id',required=True)
|
|
144
|
+
a.add_argument('--out',type=Path,required=True)
|
|
145
|
+
a=sub.add_parser('v078-virtual-plc-serve',help='Serve bounded live fake OPC UA Virtual PLC on localhost for manual Ignition FAT')
|
|
146
|
+
a.add_argument('--model',type=Path,required=True)
|
|
147
|
+
a.add_argument('--contract',type=Path,required=True)
|
|
148
|
+
a.add_argument('--port',type=int,default=4899)
|
|
149
|
+
a.add_argument('--seconds',type=int,default=300)
|
|
150
|
+
a.add_argument('--confirm',required=True)
|
|
151
|
+
a=sub.add_parser('v078-virtual-plc-fat',help='Execute fake OPC UA START/STOP/RESET interlock/feedback FAT on loopback only')
|
|
152
|
+
a.add_argument('--model',type=Path,required=True)
|
|
153
|
+
a.add_argument('--contract',type=Path,required=True)
|
|
154
|
+
a.add_argument('--out',type=Path,required=True)
|
|
155
|
+
a.add_argument('--port',type=int,default=4899)
|
|
156
|
+
a.add_argument('--confirm',required=True)
|
|
157
|
+
for action in ('v078-native-compile','v078-native-verify'):
|
|
158
|
+
a=sub.add_parser(action,help='Create/verify disabled-by-default LAB ONLY Perspective device command project candidate')
|
|
159
|
+
a.add_argument('--model',type=Path,required=True)
|
|
160
|
+
a.add_argument('--contract',type=Path,required=True)
|
|
161
|
+
a.add_argument('--binding-manifest',type=Path,required=True)
|
|
162
|
+
a.add_argument('--reference-zip',type=Path,required=True)
|
|
163
|
+
a.add_argument('--reference-sha256',required=True)
|
|
164
|
+
a.add_argument('--template-view',required=True)
|
|
165
|
+
a.add_argument('--new-project-name',required=True)
|
|
166
|
+
a.add_argument('--confirm',default='',required=action=='v078-native-compile')
|
|
167
|
+
if action=='v078-native-compile':a.add_argument('--out',type=Path,required=True)
|
|
168
|
+
else:a.add_argument('--candidate',type=Path,required=True)
|
|
169
|
+
a=sub.add_parser('preflight',help='Offline cross-project release checks; does NOT certify Ignition runtime')
|
|
170
|
+
a.add_argument('--reference-zip',type=Path,default=None)
|
|
171
|
+
a.add_argument('--reference-sha256',default=None)
|
|
172
|
+
a.add_argument('--template-view',default=None)
|
|
173
|
+
a.add_argument('--report',type=Path,default=None,help='Create a new JSON report; refuses overwrite')
|
|
174
|
+
a=sub.add_parser('examples',help='Show bundled customer model examples')
|
|
175
|
+
args=p.parse_args(argv)
|
|
176
|
+
if args.action.startswith('v078-'):
|
|
177
|
+
from devagent_spatial.v078_commands import (contract_template,validate_contract,run_virtual_plc_fat,
|
|
178
|
+
CommandHold,LAB_CONFIRM)
|
|
179
|
+
try:
|
|
180
|
+
if args.model.stat().st_size>2_000_000:raise CommandHold('MODEL_TOO_LARGE')
|
|
181
|
+
model=strict_loads(args.model.read_text(encoding='utf8'))
|
|
182
|
+
if args.action=='v078-command-template':
|
|
183
|
+
result=contract_template(model,args.asset_id)
|
|
184
|
+
with args.out.open('x',encoding='utf8') as f:
|
|
185
|
+
json.dump(result,f,indent=2);f.write('\n')
|
|
186
|
+
print(json.dumps({'status':'PENDING_LAB_REVIEW','file':str(args.out),'production':'HOLD'}))
|
|
187
|
+
return 0
|
|
188
|
+
if args.contract.stat().st_size>100_000:raise CommandHold('CONTRACT_TOO_LARGE')
|
|
189
|
+
contract=strict_loads(args.contract.read_text(encoding='utf8'))
|
|
190
|
+
validate_contract(model,contract,require_lab_approval=True)
|
|
191
|
+
if args.action=='v078-virtual-plc-serve':
|
|
192
|
+
import asyncio
|
|
193
|
+
from devagent_spatial.v078_commands import serve_virtual_plc
|
|
194
|
+
print('LAB_VPLC_ENDPOINT=opc.tcp://127.0.0.1:'+str(args.port)+'/devagent-virtual-plc/')
|
|
195
|
+
print('LAB_VPLC_ONLY=YES | IGNITION_GATEWAY=NOT_VERIFIED | PRODUCTION=HOLD',flush=True)
|
|
196
|
+
result=asyncio.run(serve_virtual_plc(model,contract,port=args.port,
|
|
197
|
+
seconds=args.seconds,confirm=args.confirm))
|
|
198
|
+
print(json.dumps(result,indent=2));return 0
|
|
199
|
+
if args.action=='v078-virtual-plc-fat':
|
|
200
|
+
import asyncio
|
|
201
|
+
result=asyncio.run(asyncio.wait_for(run_virtual_plc_fat(
|
|
202
|
+
model,contract,port=args.port,confirm=args.confirm),timeout=45))
|
|
203
|
+
with args.out.open('x',encoding='utf8') as f:
|
|
204
|
+
json.dump(result,f,indent=2);f.write('\n')
|
|
205
|
+
print(json.dumps({'status':result['status'],'file':str(args.out),'production':'HOLD'}))
|
|
206
|
+
return 0 if result['status'].startswith('PASS_') else 2
|
|
207
|
+
from devagent_spatial.v078_native_commands import compile_lab_native,verify_lab_native
|
|
208
|
+
from devagent_spatial.ignition_native import NativeHold
|
|
209
|
+
if args.binding_manifest.stat().st_size>2_000_000:raise CommandHold('BINDING_MANIFEST_TOO_LARGE')
|
|
210
|
+
binding_manifest=strict_loads(args.binding_manifest.read_text(encoding='utf8'))
|
|
211
|
+
if args.action=='v078-native-compile':
|
|
212
|
+
result=compile_lab_native(model,binding_manifest,contract,args.reference_zip,
|
|
213
|
+
args.template_view,args.reference_sha256,args.new_project_name,args.out,args.confirm)
|
|
214
|
+
else:
|
|
215
|
+
result=verify_lab_native(model,binding_manifest,contract,args.reference_zip,
|
|
216
|
+
args.template_view,args.reference_sha256,args.new_project_name,args.candidate)
|
|
217
|
+
print(json.dumps(result,indent=2));return 0
|
|
218
|
+
except (OSError,CommandHold,ValueError,TypeError,KeyError,RecursionError,Exception) as ex:
|
|
219
|
+
# Only known stable error codes leave the CLI; never leak model credentials/hostnames.
|
|
220
|
+
from devagent_spatial.ignition_native import NativeHold
|
|
221
|
+
if not isinstance(ex,(OSError,CommandHold,ValueError,TypeError,KeyError,RecursionError,NativeHold)):
|
|
222
|
+
raise
|
|
223
|
+
code=str(ex)
|
|
224
|
+
import re
|
|
225
|
+
if not re.fullmatch('[A-Z0-9_:]{3,170}',code):code='LAB_COMMAND_OR_NATIVE_COMPILER_HOLD'
|
|
226
|
+
print('HOLD=V078: '+code);return 2
|
|
227
|
+
if args.action in ('v076-opc-plan','v076-opc-fat'):
|
|
228
|
+
from devagent_spatial.v076_opc_fat import plan,run_loopback_fat,save_new,LabHold
|
|
229
|
+
import asyncio
|
|
230
|
+
try:
|
|
231
|
+
if args.action=='v076-opc-fat' and args.confirm!='ISOLATED_FAKE_OPC_LOOPBACK_ONLY':
|
|
232
|
+
raise LabHold('EXPLICIT_LAB_CONFIRMATION_REQUIRED')
|
|
233
|
+
if args.model.stat().st_size>2_000_000:raise LabHold('MODEL_FILE_TOO_LARGE')
|
|
234
|
+
model=strict_loads(args.model.read_text(encoding='utf8'))
|
|
235
|
+
result=(plan(model) if args.action=='v076-opc-plan' else
|
|
236
|
+
asyncio.run(asyncio.wait_for(run_loopback_fat(model,args.port),timeout=50)))
|
|
237
|
+
save_new(args.out,result)
|
|
238
|
+
print(json.dumps({'status':result['status'],'report':str(args.out),
|
|
239
|
+
'production':'HOLD','ignition':'NOT_VERIFIED'},indent=2))
|
|
240
|
+
return 0 if result['status'] in ('NOT_EXECUTED','PASS_LAB_OPC_PROTOCOL_ONLY') else 2
|
|
241
|
+
except (OSError,LabHold,ValueError,TypeError,KeyError,RecursionError) as e:
|
|
242
|
+
print('HOLD=V076_FAKE_OPC_FAT: '+str(e)[:160]);return 2
|
|
243
|
+
if args.action in ('v083-native-compile','v083-native-verify'):
|
|
244
|
+
from devagent_spatial.v083_native import candidate, policy_skeleton
|
|
245
|
+
from devagent_spatial.ignition_native import NativeHold
|
|
246
|
+
try:
|
|
247
|
+
for path in (args.model,args.binding_manifest,args.operator_policy):
|
|
248
|
+
if path and path.stat().st_size>2_000_000:
|
|
249
|
+
raise NativeHold('OPERATOR_INPUT_TOO_LARGE')
|
|
250
|
+
model=strict_loads(args.model.read_text(encoding='utf8'))
|
|
251
|
+
manifest=strict_loads(args.binding_manifest.read_text(encoding='utf8'))
|
|
252
|
+
policy=(strict_loads(args.operator_policy.read_text(encoding='utf8'))
|
|
253
|
+
if args.operator_policy else policy_skeleton(model))
|
|
254
|
+
verify=args.action=='v083-native-verify'
|
|
255
|
+
result=candidate(model,manifest,policy,args.reference_zip,args.template_view,
|
|
256
|
+
args.reference_sha256,args.new_project_name,
|
|
257
|
+
args.candidate if verify else args.out,verify=verify,
|
|
258
|
+
confirm='' if verify else args.confirm)
|
|
259
|
+
print(json.dumps(result,indent=2,ensure_ascii=False));return 0
|
|
260
|
+
except (NativeHold,OSError,ValueError,TypeError,KeyError,zipfile.BadZipFile,RecursionError) as ex:
|
|
261
|
+
print('HOLD=V083: '+str(ex)[:300]);return 2
|
|
262
|
+
if args.action.startswith('v07-'):
|
|
263
|
+
from devagent_spatial.v07_native import (manifest_skeleton, compile_lab, verify_lab,
|
|
264
|
+
fat_plan, NativeHold)
|
|
265
|
+
try:
|
|
266
|
+
if args.model.stat().st_size>2_000_000:raise NativeHold('MODEL_FILE_TOO_LARGE')
|
|
267
|
+
model=strict_loads(args.model.read_text(encoding='utf8'))
|
|
268
|
+
if args.action=='v07-binding-skeleton':
|
|
269
|
+
result=manifest_skeleton(model)
|
|
270
|
+
else:
|
|
271
|
+
if args.binding_manifest.stat().st_size>2_000_000:raise NativeHold('MANIFEST_FILE_TOO_LARGE')
|
|
272
|
+
manifest=strict_loads(args.binding_manifest.read_text(encoding='utf8'))
|
|
273
|
+
if args.action=='v07-fat-assess':
|
|
274
|
+
from devagent_spatial.v07_fat import assess
|
|
275
|
+
if args.observations.stat().st_size>2_000_000:raise NativeHold('OBSERVATIONS_TOO_LARGE')
|
|
276
|
+
observations=strict_loads(args.observations.read_text(encoding='utf8'))
|
|
277
|
+
result=assess(model,manifest,observations,args.candidate)
|
|
278
|
+
elif args.action=='v07-native-compile':
|
|
279
|
+
result=compile_lab(model,manifest,args.reference_zip,args.template_view,
|
|
280
|
+
args.reference_sha256,args.new_project_name,args.out,args.confirm)
|
|
281
|
+
elif args.action=='v07-native-verify':
|
|
282
|
+
result=verify_lab(model,manifest,args.reference_zip,args.template_view,
|
|
283
|
+
args.reference_sha256,args.new_project_name,args.candidate)
|
|
284
|
+
else:result=fat_plan(model,manifest)
|
|
285
|
+
if args.action in ('v07-binding-skeleton','v07-fat-plan','v07-fat-assess'):
|
|
286
|
+
with args.out.expanduser().open('x',encoding='utf8') as f:
|
|
287
|
+
json.dump(result,f,indent=2,ensure_ascii=False);f.write('\n')
|
|
288
|
+
print(json.dumps({'status':result.get('status','PASS_UNEXECUTED_PLAN_ONLY'),
|
|
289
|
+
'file':str(args.out), 'production_release':'HOLD',
|
|
290
|
+
'gateway_fat':'NOT_VERIFIED'},indent=2))
|
|
291
|
+
else:print(json.dumps(result,indent=2,ensure_ascii=False))
|
|
292
|
+
return 0
|
|
293
|
+
except (NativeHold,OSError,ValueError,TypeError,KeyError,zipfile.BadZipFile,RecursionError) as e:
|
|
294
|
+
print('HOLD=V07: '+str(e)[:400]);return 2
|
|
295
|
+
if args.action=='ops-studio-lab':
|
|
296
|
+
from devagent_spatial.operations_lab import create_lab_derivative
|
|
297
|
+
from devagent_spatial.operations import OperationsHold
|
|
298
|
+
try:
|
|
299
|
+
if args.model.stat().st_size>2_000_000:raise OperationsHold('MODEL_TOO_LARGE')
|
|
300
|
+
original=strict_loads(args.model.read_text(encoding='utf-8'))
|
|
301
|
+
result=create_lab_derivative(original,args.out_dir,args.confirm)
|
|
302
|
+
print(json.dumps(result,indent=2,ensure_ascii=False))
|
|
303
|
+
return 0
|
|
304
|
+
except (OSError,ValueError,KeyError,OperationsHold) as exc:
|
|
305
|
+
print('HOLD=STUDIO_LAB_ALIAS: '+str(exc)[:300]);return 2
|
|
306
|
+
if args.action in ('ops-demo','ops-evaluate','ops-capture'):
|
|
307
|
+
from devagent_spatial.operations import (evaluate,make_synthetic_frames,OperationsHold)
|
|
308
|
+
try:
|
|
309
|
+
if args.model.stat().st_size>2_000_000:raise OperationsHold('MODEL_TOO_LARGE')
|
|
310
|
+
model=strict_loads(args.model.read_text(encoding='utf-8'))
|
|
311
|
+
if args.action=='ops-demo':
|
|
312
|
+
frames=make_synthetic_frames(model)
|
|
313
|
+
origin='SYNTHETIC_OFFLINE_FIXTURE'
|
|
314
|
+
elif args.action=='ops-evaluate':
|
|
315
|
+
if args.frames.stat().st_size>2_000_000:raise OperationsHold('FRAMES_TOO_LARGE')
|
|
316
|
+
frames=strict_loads(args.frames.read_text(encoding='utf-8'))
|
|
317
|
+
origin='SYNTHETIC_OFFLINE_FIXTURE' # Unproven supplied frames cannot claim verified Studio readings.
|
|
318
|
+
else:
|
|
319
|
+
if args.lab_confirm!='ISOLATED_TEST_ONLY':raise OperationsHold('EXPLICIT_LAB_CONFIRMATION_REQUIRED')
|
|
320
|
+
if not 1<=args.samples<=12:raise OperationsHold('UNSAFE_CAPTURE_SAMPLE_COUNT')
|
|
321
|
+
import asyncio
|
|
322
|
+
from devagent_spatial.operations_capture import capture_lab
|
|
323
|
+
frames=asyncio.run(capture_lab(model,args.endpoint,args.samples))
|
|
324
|
+
origin='ISOLATED_LAB_OPC_READ_ONLY'
|
|
325
|
+
receipt=evaluate(model,frames,origin=origin)
|
|
326
|
+
with args.out.expanduser().open('x',encoding='utf-8') as f:
|
|
327
|
+
json.dump(receipt,f,indent=2,ensure_ascii=False)
|
|
328
|
+
f.write('\n')
|
|
329
|
+
print(json.dumps({'status':receipt['status'],'file':str(args.out),'frames':len(receipt['frames']),
|
|
330
|
+
'events':len(receipt['events']),'mapping_digest':receipt['mapping_digest'],
|
|
331
|
+
'ignition_hmi':'NOT_VERIFIED','plc_writes':'DISABLED'},indent=2))
|
|
332
|
+
return 0
|
|
333
|
+
except (OSError,ValueError,TypeError,KeyError,OperationsHold) as exc:
|
|
334
|
+
print('HOLD=OPERATIONS: '+str(exc)[:300]);return 2
|
|
335
|
+
if args.action=='preflight':
|
|
336
|
+
from devagent_spatial.preflight import preflight
|
|
337
|
+
result=preflight(reference_zip=args.reference_zip,
|
|
338
|
+
reference_sha256=args.reference_sha256,template_view=args.template_view)
|
|
339
|
+
blob=json.dumps(result,indent=2,ensure_ascii=False)+'\n'
|
|
340
|
+
if args.report:
|
|
341
|
+
try:
|
|
342
|
+
with args.report.open('x',encoding='utf-8') as f:f.write(blob)
|
|
343
|
+
except (OSError,ValueError) as exc:
|
|
344
|
+
print('HOLD=REPORT_CREATE_ONLY: '+str(exc));return 2
|
|
345
|
+
print(blob)
|
|
346
|
+
return 0 if result['status']=='PASS_OFFLINE_PRETEST_ONLY' else 2
|
|
347
|
+
if args.action in ('ignition-project-candidate','ignition-verify-project'):
|
|
348
|
+
from devagent_spatial.ignition_project import compile_project, verify_project
|
|
349
|
+
from devagent_spatial.ignition_native import NativeHold
|
|
350
|
+
try:
|
|
351
|
+
if args.model.stat().st_size>2_000_000:raise NativeHold('MODEL_FILE_TOO_LARGE')
|
|
352
|
+
model=strict_loads(args.model.read_text(encoding='utf-8'))
|
|
353
|
+
if args.action=='ignition-project-candidate':
|
|
354
|
+
result=compile_project(model,args.reference_zip,args.template_view,
|
|
355
|
+
args.reference_sha256,args.new_project_name,args.out,args.confirm)
|
|
356
|
+
else:
|
|
357
|
+
result=verify_project(model,args.reference_zip,args.template_view,
|
|
358
|
+
args.reference_sha256,args.new_project_name,args.candidate)
|
|
359
|
+
print(json.dumps(result,indent=2,ensure_ascii=False))
|
|
360
|
+
return 0
|
|
361
|
+
except (NativeHold,OSError,ValueError,KeyError,TypeError,zipfile.BadZipFile,RecursionError) as exc:
|
|
362
|
+
print('HOLD=IGNITION_PROJECT: '+str(exc)[:350])
|
|
363
|
+
return 2
|
|
364
|
+
if args.action in ('ignition-list','ignition-candidate','ignition-verify-candidate'):
|
|
365
|
+
from devagent_spatial.ignition_native import (find_views, compile_candidate,
|
|
366
|
+
verify_candidate,NativeHold)
|
|
367
|
+
try:
|
|
368
|
+
if args.action=='ignition-list':
|
|
369
|
+
result=find_views(args.reference_zip)
|
|
370
|
+
else:
|
|
371
|
+
if args.model.stat().st_size>2_000_000:raise NativeHold('MODEL_FILE_TOO_LARGE')
|
|
372
|
+
data=strict_loads(args.model.read_text(encoding='utf-8'))
|
|
373
|
+
if args.action=='ignition-candidate':
|
|
374
|
+
result=compile_candidate(data,args.reference_zip,args.template_view,
|
|
375
|
+
args.reference_sha256,args.out)
|
|
376
|
+
else:
|
|
377
|
+
result=verify_candidate(data,args.reference_zip,args.template_view,
|
|
378
|
+
args.reference_sha256,args.candidate)
|
|
379
|
+
print(json.dumps(result,indent=2,ensure_ascii=False))
|
|
380
|
+
return 0
|
|
381
|
+
except (NativeHold,OSError,ValueError,KeyError,TypeError,zipfile.BadZipFile) as e:
|
|
382
|
+
print('HOLD=IGNITION_NATIVE: '+str(e))
|
|
383
|
+
return 2
|
|
384
|
+
if args.action=='opc-snapshot':
|
|
385
|
+
from devagent_spatial.opc_snapshot import probe_live, SnapshotHold
|
|
386
|
+
import asyncio
|
|
387
|
+
if args.lab_confirm!='ISOLATED_TEST_ONLY':
|
|
388
|
+
print('HOLD=EXPLICIT_ISOLATED_LAB_CONFIRMATION_REQUIRED');return 2
|
|
389
|
+
try:
|
|
390
|
+
if args.model.stat().st_size>2_000_000:raise ValueError('MODEL_FILE_TOO_LARGE')
|
|
391
|
+
model=strict_loads(args.model.read_text(encoding='utf8'))
|
|
392
|
+
result=asyncio.run(probe_live(model,args.endpoint))
|
|
393
|
+
print(json.dumps(result,indent=2,ensure_ascii=False))
|
|
394
|
+
return 0 if result['status']=='PASS_LAB_PROTOCOL_SNAPSHOT_ONLY' else 2
|
|
395
|
+
except (OSError,ValueError,SnapshotHold) as e:
|
|
396
|
+
print('HOLD=OPC_SNAPSHOT: '+str(e));return 2
|
|
397
|
+
if args.action=='qualify':
|
|
398
|
+
from devagent_spatial.qualification import audit
|
|
399
|
+
report=audit(args.project_dir,purpose=args.purpose)
|
|
400
|
+
print(json.dumps(report,indent=2,ensure_ascii=False))
|
|
401
|
+
return 0 if report['status']=='PASS_CONCEPT_PREVIEW_ONLY' else 2
|
|
402
|
+
if args.action=='audit-ignition':
|
|
403
|
+
from devagent_spatial.native_reference import audit_ignition_archive
|
|
404
|
+
report=audit_ignition_archive(args.project_zip)
|
|
405
|
+
print(json.dumps(report,indent=2,ensure_ascii=False))
|
|
406
|
+
return 0 if report['status']=='PASS_ZIP_SAFETY_ONLY' else 2
|
|
407
|
+
if args.action=='examples':
|
|
408
|
+
print('WAREHOUSE_EXAMPLE='+str(ROOT/'examples/warehouse.json'))
|
|
409
|
+
print('WATER_TREATMENT_EXAMPLE='+str(ROOT/'examples/water.json'))
|
|
410
|
+
print('SINGLE_FLOOR_EXAMPLE='+str(ROOT/'examples/single_floor.json'))
|
|
411
|
+
return 0
|
|
412
|
+
if args.action in ('validate','build'):
|
|
413
|
+
try:
|
|
414
|
+
raw_path=args.model.expanduser()
|
|
415
|
+
if raw_path.stat().st_size > 2_000_000:raise ValueError('INPUT_FILE_TOO_LARGE')
|
|
416
|
+
data=strict_loads(raw_path.read_text(encoding='utf-8'))
|
|
417
|
+
except (OSError,ValueError,RecursionError,UnicodeError) as e:
|
|
418
|
+
print('HOLD=INPUT_UNREADABLE_OR_INVALID_JSON: '+str(e))
|
|
419
|
+
return 2
|
|
420
|
+
if args.action=='validate':
|
|
421
|
+
report=qualify(data)
|
|
422
|
+
print(json.dumps(report,indent=2,ensure_ascii=False))
|
|
423
|
+
return 0 if report['status']!='HOLD' else 2
|
|
424
|
+
try:
|
|
425
|
+
result=build(data,args.out,vendor_three=args.vendor_three)
|
|
426
|
+
print(json.dumps(result,indent=2,ensure_ascii=False))
|
|
427
|
+
return 0
|
|
428
|
+
except (BuildHold,OSError,ValueError) as e:
|
|
429
|
+
print('HOLD='+str(e))
|
|
430
|
+
return 2
|
|
431
|
+
if not 1024<=args.port<=65535:
|
|
432
|
+
print('HOLD=INVALID_PORT')
|
|
433
|
+
return 2
|
|
434
|
+
if args.action=='studio':
|
|
435
|
+
from devagent_spatial.studio import StudioServer
|
|
436
|
+
try:
|
|
437
|
+
server=StudioServer(('127.0.0.1',args.port),args.workspace,args.vendor_three)
|
|
438
|
+
except (OSError,ValueError,RecursionError,UnicodeError) as e:
|
|
439
|
+
print('HOLD=STUDIO_START: '+str(e))
|
|
440
|
+
return 2
|
|
441
|
+
print('DEVAGENT_SPATIAL_STUDIO=READY',flush=True)
|
|
442
|
+
print(f'LOCAL_UI=http://127.0.0.1:{args.port}/',flush=True)
|
|
443
|
+
print(f'CUSTOMER_PROJECTS={server.workspace}',flush=True)
|
|
444
|
+
print('WRITE_AUTHORITY=DISABLED; IGNITION_MODIFICATIONS=NONE',flush=True)
|
|
445
|
+
try:server.serve_forever(poll_interval=.2)
|
|
446
|
+
except KeyboardInterrupt:pass
|
|
447
|
+
finally:server.server_close()
|
|
448
|
+
return 0
|
|
449
|
+
if args.action=='preview':
|
|
450
|
+
project=args.project_dir.expanduser().resolve()
|
|
451
|
+
viewer=project/'viewer'
|
|
452
|
+
if not (viewer/'index.html').is_file() or not (project/'BUILD.json').is_file():
|
|
453
|
+
print('HOLD=NOT_A_GENERATED_DEVAGENT_PREVIEW')
|
|
454
|
+
return 2
|
|
455
|
+
class Handler(SimpleHTTPRequestHandler):
|
|
456
|
+
def end_headers(self):
|
|
457
|
+
self.send_header('X-Content-Type-Options','nosniff');self.send_header('Cache-Control','no-store')
|
|
458
|
+
super().end_headers()
|
|
459
|
+
try:
|
|
460
|
+
server=ThreadingHTTPServer(('127.0.0.1',args.port),partial(Handler,directory=str(viewer)))
|
|
461
|
+
except OSError as e:
|
|
462
|
+
print('HOLD=PREVIEW_PORT: '+str(e))
|
|
463
|
+
return 2
|
|
464
|
+
print(f'PREVIEW=http://127.0.0.1:{args.port}/',flush=True)
|
|
465
|
+
try:server.serve_forever(poll_interval=.2)
|
|
466
|
+
except KeyboardInterrupt:pass
|
|
467
|
+
finally:server.server_close()
|
|
468
|
+
return 0
|
|
469
|
+
return 2
|
|
470
|
+
|
|
471
|
+
if __name__=='__main__':raise SystemExit(main())
|