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.
Files changed (133) hide show
  1. devagent_hmi.py +471 -0
  2. devagent_ignition-0.1.0.dist-info/METADATA +204 -0
  3. devagent_ignition-0.1.0.dist-info/RECORD +133 -0
  4. devagent_ignition-0.1.0.dist-info/WHEEL +4 -0
  5. devagent_ignition-0.1.0.dist-info/entry_points.txt +2 -0
  6. devagent_spatial/__init__.py +1 -0
  7. devagent_spatial/_resources/README.md +187 -0
  8. devagent_spatial/_resources/RELEASE_MANIFEST.json +312 -0
  9. devagent_spatial/_resources/SHA256SUMS.txt +270 -0
  10. devagent_spatial/_resources/SOURCE_PROVENANCE.json +186 -0
  11. devagent_spatial/_resources/assets/agent.js +102 -0
  12. devagent_spatial/_resources/assets/creator.css +247 -0
  13. devagent_spatial/_resources/assets/creator_2d.mjs +16 -0
  14. devagent_spatial/_resources/assets/creator_original_r9.mjs +58 -0
  15. devagent_spatial/_resources/assets/creator_original_v8.mjs +120 -0
  16. devagent_spatial/_resources/assets/creator_r9_diagnostics.mjs +53 -0
  17. devagent_spatial/_resources/assets/creator_scene.mjs +128 -0
  18. devagent_spatial/_resources/assets/creator_ui.mjs +60 -0
  19. devagent_spatial/_resources/assets/customer_documents.js +110 -0
  20. devagent_spatial/_resources/assets/equipment_controls.mjs +27 -0
  21. devagent_spatial/_resources/assets/facility2d.css +24 -0
  22. devagent_spatial/_resources/assets/facility2d_app.mjs +45 -0
  23. devagent_spatial/_resources/assets/facility2d_core.mjs +114 -0
  24. devagent_spatial/_resources/assets/facility2d_simulation.mjs +167 -0
  25. devagent_spatial/_resources/assets/facility2d_viewer.html +7 -0
  26. devagent_spatial/_resources/assets/fallback_scene.js +17 -0
  27. devagent_spatial/_resources/assets/flow_geometry.mjs +218 -0
  28. devagent_spatial/_resources/assets/harness.js +78 -0
  29. devagent_spatial/_resources/assets/live_design_review.js +129 -0
  30. devagent_spatial/_resources/assets/operations_ui.js +305 -0
  31. devagent_spatial/_resources/assets/operator_viewer.html +64 -0
  32. devagent_spatial/_resources/assets/project_package.js +52 -0
  33. devagent_spatial/_resources/assets/r9_transport.mjs +70 -0
  34. devagent_spatial/_resources/assets/runtime_feed.js +30 -0
  35. devagent_spatial/_resources/assets/studio.html +139 -0
  36. devagent_spatial/_resources/assets/style.css +191 -0
  37. devagent_spatial/_resources/assets/test_workspace.html +195 -0
  38. devagent_spatial/_resources/assets/three_scene.js +442 -0
  39. devagent_spatial/_resources/assets/unified_engineer.js +101 -0
  40. devagent_spatial/_resources/assets/v076_fat_ui.js +40 -0
  41. devagent_spatial/_resources/assets/v078_command_ui.js +44 -0
  42. devagent_spatial/_resources/assets/viewer.html +45 -0
  43. devagent_spatial/_resources/assets/viewer.js +112 -0
  44. devagent_spatial/_resources/design-system.json +130 -0
  45. devagent_spatial/_resources/examples/CV101-command-contract-PENDING.json +30 -0
  46. devagent_spatial/_resources/examples/cv101_guided.json +272 -0
  47. devagent_spatial/_resources/examples/make_examples.py +36 -0
  48. devagent_spatial/_resources/examples/single_floor.json +444 -0
  49. devagent_spatial/_resources/examples/v0771_training_wizard.json +11 -0
  50. devagent_spatial/_resources/examples/warehouse-3floor-standard-2d.json +708 -0
  51. devagent_spatial/_resources/examples/warehouse-operations-synthetic.json +1186 -0
  52. devagent_spatial/_resources/examples/warehouse-v07-lab-bindings-PENDING.json +372 -0
  53. devagent_spatial/_resources/examples/warehouse.json +708 -0
  54. devagent_spatial/_resources/examples/water.json +288 -0
  55. devagent_spatial/_resources/reference/r9-golden/REFERENCE_SHA256.json +13 -0
  56. devagent_spatial/_resources/reference/r9-golden/web/app.mjs +745 -0
  57. devagent_spatial/_resources/reference/r9-golden/web/index.html +213 -0
  58. devagent_spatial/_resources/reference/r9-golden/web/lab_panel.mjs +53 -0
  59. devagent_spatial/_resources/reference/r9-golden/web/model.mjs +367 -0
  60. devagent_spatial/_resources/reference/r9-golden/web/state_adapter.mjs +78 -0
  61. devagent_spatial/_resources/reference/r9-golden/web/styles.css +76 -0
  62. devagent_spatial/_resources/reference/r9-original/README_R9.md +27 -0
  63. devagent_spatial/_resources/reference/r9-original/SHA256SUMS.txt +63 -0
  64. devagent_spatial/_resources/reference/r9-original/SNAPSHOT_SHA256SUMS.txt +16 -0
  65. devagent_spatial/_resources/reference/r9-original/flow_routes.json +527 -0
  66. devagent_spatial/_resources/reference/r9-original/flow_sim.py +117 -0
  67. devagent_spatial/_resources/reference/r9-original/lab_control.py +73 -0
  68. devagent_spatial/_resources/reference/r9-original/web/app.mjs +745 -0
  69. devagent_spatial/_resources/reference/r9-original/web/index.html +213 -0
  70. devagent_spatial/_resources/reference/r9-original/web/lab_panel.mjs +53 -0
  71. devagent_spatial/_resources/reference/r9-original/web/model.mjs +367 -0
  72. devagent_spatial/_resources/reference/r9-original/web/state_adapter.mjs +78 -0
  73. devagent_spatial/_resources/reference/r9-original/web/styles.css +76 -0
  74. devagent_spatial/_resources/reference/r9-original/web/vendor/LICENSE-three.txt +21 -0
  75. devagent_spatial/_resources/reference/r9-original/web/vendor/SHA256SUMS.txt +4 -0
  76. devagent_spatial/_resources/reference/r9-original/web/vendor/addons/controls/OrbitControls.js +1860 -0
  77. devagent_spatial/_resources/reference/r9-original/web/vendor/three.core.js +58773 -0
  78. devagent_spatial/_resources/reference/r9-original/web/vendor/three.module.js +18251 -0
  79. devagent_spatial/_resources/reference/v8-original/README.md +36 -0
  80. devagent_spatial/_resources/reference/v8-original/SHA256SUMS.txt +12 -0
  81. devagent_spatial/_resources/reference/v8-original/model.py +124 -0
  82. devagent_spatial/_resources/reference/v8-original/run_demo.sh +4 -0
  83. devagent_spatial/_resources/reference/v8-original/server.py +100 -0
  84. devagent_spatial/_resources/reference/v8-original/tests/browser_smoke.py +50 -0
  85. devagent_spatial/_resources/reference/v8-original/tests/test_v8.py +144 -0
  86. devagent_spatial/_resources/reference/v8-original/topology.json +24 -0
  87. devagent_spatial/_resources/reference/v8-original/web/app.js +114 -0
  88. devagent_spatial/_resources/reference/v8-original/web/index.html +22 -0
  89. devagent_spatial/_resources/reference/v8-original/web/simulator.html +1 -0
  90. devagent_spatial/_resources/reference/v8-original/web/simulator.js +6 -0
  91. devagent_spatial/_resources/reference/v8-original/web/style.css +16 -0
  92. devagent_spatial/_resources/sample_customer_inputs/Plant_Equipment_Register.csv +4 -0
  93. devagent_spatial/_resources/sample_customer_inputs/Plant_Flow_Requirements.txt +5 -0
  94. devagent_spatial/_resources/sample_customer_inputs/Raw_PLC_Tag_Export.csv +4 -0
  95. devagent_spatial/_resources/templates/warehouse_connections.csv +12 -0
  96. devagent_spatial/_resources/templates/warehouse_equipment.csv +21 -0
  97. devagent_spatial/_resources/templates/warehouse_signals.csv +34 -0
  98. devagent_spatial/_resources/templates/water_connections.csv +7 -0
  99. devagent_spatial/_resources/templates/water_equipment.csv +10 -0
  100. devagent_spatial/_resources/templates/water_signals.csv +12 -0
  101. devagent_spatial/agent_harness.py +299 -0
  102. devagent_spatial/agent_runtime.py +314 -0
  103. devagent_spatial/contract_opc_fat.py +216 -0
  104. devagent_spatial/contract_opc_source.py +265 -0
  105. devagent_spatial/control_intents.py +38 -0
  106. devagent_spatial/customer_intake.py +235 -0
  107. devagent_spatial/engineering_expertise.py +114 -0
  108. devagent_spatial/generator.py +201 -0
  109. devagent_spatial/ignition_native.py +234 -0
  110. devagent_spatial/ignition_project.py +144 -0
  111. devagent_spatial/live_lab_binding.py +128 -0
  112. devagent_spatial/model.py +297 -0
  113. devagent_spatial/native_reference.py +69 -0
  114. devagent_spatial/opc_snapshot.py +87 -0
  115. devagent_spatial/operations.py +284 -0
  116. devagent_spatial/operations_capture.py +47 -0
  117. devagent_spatial/operations_lab.py +64 -0
  118. devagent_spatial/original_r9.py +169 -0
  119. devagent_spatial/original_v8.py +81 -0
  120. devagent_spatial/preflight.py +140 -0
  121. devagent_spatial/preview_simulation.py +189 -0
  122. devagent_spatial/project_package.py +101 -0
  123. devagent_spatial/qualification.py +152 -0
  124. devagent_spatial/strict_json.py +29 -0
  125. devagent_spatial/studio.py +894 -0
  126. devagent_spatial/studio_workflow.py +277 -0
  127. devagent_spatial/test_reporting.py +87 -0
  128. devagent_spatial/v076_opc_fat.py +180 -0
  129. devagent_spatial/v078_commands.py +277 -0
  130. devagent_spatial/v078_native_commands.py +161 -0
  131. devagent_spatial/v07_fat.py +110 -0
  132. devagent_spatial/v07_native.py +318 -0
  133. devagent_spatial/v083_native.py +349 -0
@@ -0,0 +1,195 @@
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>DevAgent | Studio Test Workspace</title>
2
+ <style>
3
+ :root{color-scheme:dark;--bg:#061520;--panel:#0e293a;--panel2:#123347;--border:#2d5063;--text:#eef8fc;--muted:#9dbfce;--cyan:#72d8f1;--green:#65ddbd;--amber:#efbb66;--red:#f18d91}*{box-sizing:border-box}body{margin:0;background:radial-gradient(ellipse at 70% -25%,#1c5260 0,transparent 57%),var(--bg);color:var(--text);font:14px/1.5 Inter,'Segoe UI',system-ui,sans-serif}button,input{font:inherit}button,a.btn{border:1px solid #3a8ca3;color:#e8faff;background:#155672;border-radius:7px;cursor:pointer;padding:10px 16px;min-height:40px;font-weight:650;text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:7px}button:hover,a.btn:hover{filter:brightness(1.2)}button:focus-visible,a:focus-visible,input:focus-visible,summary:focus-visible{outline:2px solid var(--cyan);outline-offset:3px}button:disabled{opacity:.4;cursor:not-allowed}button.primary{background:#1685a2;border-color:#5ce4e4;font-size:14px;font-weight:800}button.secondary,a.btn.secondary{background:#102e40;border-color:#3b6171}button.slim{min-height:34px;padding:6px 12px;font-size:12px}a{color:#8de6f7}header{background:#071b29;border-bottom:1px solid var(--border);padding:18px max(24px,calc((100vw - 1260px)/2));display:flex;justify-content:space-between;gap:16px;align-items:center;flex-wrap:wrap}.eyebrow{color:var(--cyan);font-size:11px;font-weight:850;letter-spacing:.12em}h1{margin:3px 0;font-size:23px;line-height:1.3}h2{font-size:17px;margin:0 0 6px}h3{font-size:14px;margin:0 0 8px}.muted{color:var(--muted)}small{color:var(--muted)}.page{max-width:1280px;margin:0 auto;padding:22px 24px 45px}.pill{font-size:11px;letter-spacing:.03em;border:1px solid var(--border);color:#b6d7e5;border-radius:5px;padding:4px 8px;font-weight:700;display:inline-block}.pill.hold{color:var(--amber);border-color:#866338;background:#33281466}.pill.ok{color:var(--green);border-color:#347c66}.pill.bad{color:var(--red);border-color:#9a4650}.hero{border:1px solid #327389;background:linear-gradient(110deg,#103548,#102736);border-radius:10px;padding:23px;display:grid;grid-template-columns:minmax(0,1fr) auto;gap:16px;align-items:center}.hero p{margin:4px 0 13px;color:var(--muted)}.actions{display:flex;gap:9px;flex-wrap:wrap;align-items:center}.notice{margin-top:10px;color:var(--muted);font-size:12px;max-width:780px}.notice.hold{color:#f3c989}.cards{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px;margin:15px 0}.card{border:1px solid var(--border);background:#0e283a;border-radius:8px;padding:13px 15px}.card .label{font-size:11px;color:#abd0df;text-transform:uppercase;font-weight:750;letter-spacing:.04em}.card strong{font-size:22px;display:block;line-height:1.65}.card .small{font-size:11px;color:var(--muted)}.tabs{display:flex;gap:7px;border-bottom:1px solid var(--border);margin:18px 0 0;overflow-x:auto}.tabs button{border:0;border-bottom:3px solid transparent;border-radius:0;background:transparent;padding:12px 18px;color:var(--muted);white-space:nowrap}.tabs button[aria-selected="true"]{border-bottom-color:var(--cyan);color:var(--text);background:#123447}.panel{background:#0c2535;border:1px solid var(--border);border-top:0;padding:22px;min-height:260px;border-radius:0 0 9px 9px}.panel[hidden]{display:none}.overview-grid{display:grid;grid-template-columns:1.3fr 1fr;gap:15px}.block{background:#102e40;border:1px solid var(--border);border-radius:7px;padding:16px}.block p{margin:5px 0;color:var(--muted)}.checks{display:grid;gap:10px}.check{display:flex;justify-content:space-between;gap:15px;border-bottom:1px solid #2c5162;padding:7px 0}.check:last-child{border:0}.check b{font-size:12px;white-space:nowrap}.check b.pending{color:var(--amber)}.check b.pass{color:var(--green)}.tablebar{display:flex;gap:8px;justify-content:space-between;align-items:center;flex-wrap:wrap;margin-bottom:14px}.search{width:min(100%,320px);border:1px solid #416b7b;border-radius:6px;background:#061e2b;color:var(--text);padding:10px 12px}.tablewrap{overflow:auto;max-height:580px;border:1px solid var(--border);border-radius:7px}table{border-collapse:collapse;width:100%;min-width:700px;font-size:12px}th{position:sticky;top:0;text-align:left;color:#a9d9e7;background:#173b50;font-size:11px;letter-spacing:.06em;text-transform:uppercase}th,td{padding:11px 12px;border-bottom:1px solid #254458}tr:hover td{background:#153346}code{color:#aadfee;font:11px/1.5 ui-monospace,Consolas,monospace;overflow-wrap:anywhere}.caption{font-size:12px;color:var(--muted);margin:4px 0 12px}.report-status{padding:14px;border:1px solid #406675;border-radius:7px;background:#0d2939;line-height:1.9}.footnote{font-size:11px;color:#90b6c8;margin-top:18px}details{margin-top:18px;border:1px solid #315769;background:#0b2232;border-radius:9px;padding:15px 18px}summary{cursor:pointer;font-weight:750;font-size:13px}details p{color:var(--muted)}details .actions{margin-top:13px}pre{background:#071926;border:1px solid #2a4e60;color:#b8d9e5;padding:13px;border-radius:7px;font:11px/1.65 ui-monospace,Consolas,monospace;white-space:pre-wrap;word-break:break-word;max-height:260px;overflow:auto}footer{text-align:center;color:#759eae;font-size:11px;padding:22px}.sr-only{position:absolute;width:1px;height:1px;clip:rect(0,0,0,0);overflow:hidden}@media(max-width:920px){.cards{grid-template-columns:repeat(3,minmax(0,1fr))}.hero{grid-template-columns:1fr}.overview-grid{grid-template-columns:1fr}}@media(max-width:570px){.page{padding:14px}.cards{grid-template-columns:repeat(2,minmax(0,1fr))}.tabs button{padding:11px 10px}.panel{padding:14px}}@media(prefers-reduced-motion:no-preference){button,a.btn{transition:filter .15s ease}}
4
+ </style></head>
5
+ <body><header><div><div class="eyebrow">DEVAGENT / CUSTOMER DIGITAL TWIN</div><h1>Digital Twin Studio</h1><div class="muted" id="project-name">Loading project...</div></div><nav class="actions" aria-label="Workspace navigation"><span class="pill hold">ISOLATED LAB ONLY</span><a id="preview-link" class="btn secondary" target="_blank" rel="noopener">Open HMI ↗</a><a class="btn secondary" href="/">Creator ↗</a></nav></header>
6
+ <main class="page">
7
+ <section class="hero" aria-labelledby="simulation-title" style="margin-bottom:16px"><div>
8
+ <div class="eyebrow">ONE-CLICK CONCEPT SIMULATION</div>
9
+ <h2 id="simulation-title">Watch your warehouse run</h2>
10
+ <p>Start the conceptual parcel flow, then open the same generated HMI. Pause or inject a fault here—not on the operator screen.</p>
11
+ <div class="actions" style="margin-top:16px">
12
+ <label for="sim-pace" class="muted">Parcel preview pace</label>
13
+ <select id="sim-pace" class="search" aria-label="Conceptual parcel transit duration">
14
+ <option value="slow">Slow · 3 min / route</option>
15
+ <option value="normal" selected>Normal · 2 min / route</option>
16
+ <option value="fast">Fast · 75 sec / route</option>
17
+ </select>
18
+ <button id="sim-start" class="primary">▶ Run parcel simulation</button>
19
+ <button id="sim-pause" class="secondary" disabled>Pause</button>
20
+ <button id="sim-stop" class="secondary" disabled>Stop</button>
21
+ <a class="btn secondary" id="sim-hmi-link" target="_blank" rel="noopener">Open HMI ↗</a>
22
+ </div>
23
+ <div class="actions" style="margin-top:10px">
24
+ <label for="sim-asset" class="muted">Fault test on</label>
25
+ <select id="sim-asset" class="search" style="width:170px" aria-label="Select fault-capable asset"><option value="INFEED01">INFEED01</option></select>
26
+ <button id="sim-fault" class="secondary" disabled>Inject / clear fault</button>
27
+ </div>
28
+ <div id="sim-message" class="notice" role="status" aria-live="polite">Simulation stopped · No PLC/OPC UA writes.</div>
29
+ </div><div><span class="pill hold">SYNTHETIC DEMO · NOT OPC UA</span>
30
+ <p class="notice" style="margin-top:12px">Uses only the customer-defined equipment graph. Parcel positions are illustrative, not physical tracking. Lab FAT, Ignition and production approval remain separate.</p></div></section>
31
+ <details id="real-studio" style="margin-bottom:15px"><summary>OPC UA signal verification (advanced)</summary>
32
+ <section class="hero" aria-labelledby="testing-title"><div><div class="eyebrow">OPTIONAL / REAL OPC UA LAB</div><h2 id="testing-title">Check OPC UA signals</h2><p>Read-only mapping checks remain separate from the conceptual parcel preview. Open only when needed.</p><div class="actions"><button id="autorun" class="primary" disabled>Run Lab Tests →</button><button id="sample" class="secondary">Try Demo (no Studio)</button><button id="refresh" class="slim secondary">Refresh</button></div><div id="autorun-hint" class="notice hold" role="status" aria-live="polite">Checking Studio...</div><div id="action-feedback" class="notice" aria-live="polite"></div></div><div><span class="pill" id="studio-online">CHECKING STUDIO</span><div class="notice" id="studio-hint">Studio must be a separate, approved simulation environment.</div></div></section>
33
+ </details>
34
+ <section class="cards" aria-label="Test coverage metrics"><div class="card"><div class="label">Assets</div><strong id="n-assets">—</strong><div class="small">Customer model</div></div><div class="card"><div class="label">Signal mappings</div><strong id="n-signals">—</strong><div class="small">Proposed, not site verified</div></div><div class="card"><div class="label">OPC lab probe</div><strong id="k-probe">—</strong><div class="small">Batch / lab only</div></div><div class="card"><div class="label">Lab behavior tests</div><strong id="k-cases">—</strong><div class="small">Not full HMI FAT</div></div><div class="card"><div class="label">Ignition HMI</div><strong style="font-size:17px;color:var(--amber)">HOLD</strong><div class="small">Bindings not verified</div></div></section>
35
+ <div class="tabs" role="tablist" aria-label="Test results"><button role="tab" aria-controls="view-overview" id="tab-overview" aria-selected="true" data-tab="overview">Overview</button><button role="tab" aria-controls="view-signals" id="tab-signals" aria-selected="false" data-tab="signals">Signal Tests <span id="tab-signal-count"></span></button><button role="tab" aria-controls="view-cases" id="tab-cases" aria-selected="false" data-tab="cases">Test Cases <span id="tab-case-count"></span></button><button role="tab" aria-controls="view-report" id="tab-report" aria-selected="false" data-tab="report">Report</button></div>
36
+ <section id="view-overview" class="panel" role="tabpanel" aria-labelledby="tab-overview"><div class="overview-grid"><div class="block"><h3>Test progress</h3><div id="progress" class="checks"><p>Run a lab test or refresh to see coverage.</p></div></div><div class="block"><h3>Test status</h3><p id="next-step">Checking test environment...</p><div class="actions" style="margin-top:14px"><a class="btn secondary" id="studio-link" href="http://127.0.0.1:8770/" target="_blank" rel="noopener">Open Studio ↗</a><button class="secondary" data-select-tab="signals">View signals →</button></div></div></div><p class="footnote">Simulation PASS does not prove customer PLC mapping, Perspective HMI behavior or production readiness.</p></section>
37
+ <section id="view-signals" class="panel" role="tabpanel" aria-labelledby="tab-signals" hidden><div class="tablebar"><div><h2>Signal mapping checks</h2><div class="caption" id="signal-note">Mapping inventory; individual OPC evidence is not yet available.</div></div><input class="search" id="filter-signals" type="search" maxlength="120" placeholder="Search signal, asset, NodeId..." aria-label="Search signals"></div><div class="tablewrap"><table><thead><tr><th>Signal</th><th>Asset</th><th>Role</th><th>Datatype</th><th>NodeId</th><th>Lab status</th></tr></thead><tbody id="signal-rows"></tbody></table></div></section>
38
+ <section id="view-cases" class="panel" role="tabpanel" aria-labelledby="tab-cases" hidden><div class="tablebar"><div><h2>FAT test cases</h2><div class="caption" id="cases-note">All cases require HMI/physical approval before full FAT PASS.</div></div><input class="search" id="filter-cases" type="search" maxlength="120" placeholder="Search case, equipment..." aria-label="Search test cases"></div><div class="tablewrap"><table><thead><tr><th>Case</th><th>Equipment</th><th>Expected behavior</th><th>Lab test</th><th>HMI FAT</th></tr></thead><tbody id="case-rows"></tbody></table></div></section>
39
+ <section id="view-report" class="panel" role="tabpanel" aria-labelledby="tab-report" hidden><h2>Qualification report</h2><p class="muted">Live report for this project, including lab coverage, test cases, mapping identity and remaining verification gaps.</p><div id="report-summary" class="report-status">Awaiting test data. Production remains HOLD.</div><div class="actions" style="margin-top:16px"><a class="btn" id="report-download" href="#" download>Download Lab Report ↧</a><a class="btn secondary" id="evidence-probe-link" href="#" download hidden>OPC Probe Evidence ↧</a><a class="btn secondary" id="evidence-link" href="#" download hidden>Fault Evidence ↧</a></div><p class="footnote">This is a LAB-ONLY report. No physical PLC/production approval or Ignition HMI qualification is implied.</p></section>
40
+ <details id="advanced"><summary>Advanced tools · mapping import, OPC probe, fault scenario</summary><p>Use these steps only for troubleshooting or an engineer-approved isolated Studio R2 lab. They are not required for normal one-click testing.</p><div class="actions"><button id="prepare" class="slim secondary">Prepare mapping</button><button id="import" class="slim secondary">Import lab signals</button><button id="probe" class="slim secondary">Run OPC probe</button><button id="scenario" class="slim secondary">Run fault test</button></div><p>Existing unknown Studio tags block import and fault stimulus. No automatic deletion, PLC writes or HMI modifications.</p><h3 style="margin-top:17px">Diagnostic output</h3><pre id="result" role="status" aria-live="polite">No test actions yet.</pre></details>
41
+ </main><footer>DEVAGENT · LAB TEST COVERAGE ONLY · IGNITION / PRODUCTION QUALIFICATION HOLD</footer>
42
+ <script>
43
+ (()=>{'use strict';
44
+ const id=location.pathname.match(/^\/projects\/([a-z0-9-]+)\/test\/index\.html$/)?.[1];
45
+ const $=x=>document.getElementById(x);
46
+ if(!id){$('autorun-hint').textContent='Invalid project URL';return;}
47
+ const base='/api/lab/'+id;
48
+ let prepared=false,imported=false,busy=false,state=null,coverage=null;
49
+ const mark=(val)=>{const b=document.createElement('span');b.className='pill '+(/PASS|CHECKED|BATCH|LAB_FAULT/.test(val)?'ok':/HOLD|ERROR|FAIL/.test(val)?'bad':'hold');b.textContent=val.replaceAll('_',' ');return b;};
50
+ const td=(row,value,code=false)=>{const c=document.createElement('td');if(code){const k=document.createElement('code');k.textContent=String(value??'—');c.appendChild(k);}else c.textContent=String(value??'—');row.appendChild(c);};
51
+ function tab(key){for(const btn of document.querySelectorAll('[data-tab]'))btn.setAttribute('aria-selected',String(btn.dataset.tab===key));for(const k of ['overview','signals','cases','report'])$('view-'+k).hidden=k!==key;}
52
+ for(const btn of document.querySelectorAll('[data-tab]'))btn.onclick=()=>tab(btn.dataset.tab);
53
+ for(const btn of document.querySelectorAll('[data-select-tab]'))btn.onclick=()=>tab(btn.dataset.selectTab);
54
+ function renderRows(){if(!coverage)return;
55
+ const query=$('filter-signals').value.trim().toLowerCase();const tbody=$('signal-rows');tbody.replaceChildren();
56
+ for(const s of coverage.signals){if(query&&!`${s.id} ${s.asset_id} ${s.role} ${s.datatype} ${s.node_id}`.toLowerCase().includes(query))continue;
57
+ const r=document.createElement('tr');td(r,s.id,true);td(r,s.asset_id);td(r,s.role);td(r,s.datatype);td(r,s.node_id,true);const col=document.createElement('td');col.appendChild(mark(s.protocol));r.appendChild(col);tbody.appendChild(r);}
58
+ const fq=$('filter-cases').value.trim().toLowerCase();const cb=$('case-rows');cb.replaceChildren();
59
+ for(const c of coverage.test_cases){if(fq&&!`${c.id} ${c.asset_id} ${c.title} ${c.lab}`.toLowerCase().includes(fq))continue;
60
+ const r=document.createElement('tr');td(r,c.id,true);td(r,c.asset_id||'Requirement');td(r,c.title);const col=document.createElement('td');col.appendChild(mark(c.lab));r.appendChild(col);const h=document.createElement('td');h.appendChild(mark('HOLD'));r.appendChild(h);cb.appendChild(r);}
61
+ }
62
+ $('filter-signals').addEventListener('input',renderRows);$('filter-cases').addEventListener('input',renderRows);
63
+ function renderCoverage(d){coverage=d;
64
+ const t=d.totals;$('n-assets').textContent=t.assets;$('n-signals').textContent=t.signals;
65
+ $('k-probe').textContent=t.protocol_batch_checked+'/'+t.signals;
66
+ $('k-cases').textContent=t.lab_behavior_cases_pass+'/'+t.fat_cases;
67
+ $('tab-signal-count').textContent='('+t.signals+')';$('tab-case-count').textContent='('+t.fat_cases+')';
68
+ $('signal-note').textContent=t.protocol_batch_checked?'Studio checked '+t.protocol_batch_checked+' nodes in one batch. Per-signal receipts and physical identity are NOT verified.':'Source definitions only. No OPC readback completed.';
69
+ $('cases-note').textContent=t.lab_behavior_cases_pass+' lab behavior case(s); 0 full HMI/physical FAT PASS.';
70
+ const p=$('progress');p.replaceChildren();for(const [label,v] of [
71
+ ['Customer mapping','NOT VERIFIED'],['OPC UA lab readback',d.gates.G2_lab_opc_readback],['Simulated fault',d.gates.G3_lab_fault],['Ignition HMI bindings','HOLD'],['Production release','HOLD']]){
72
+ const div=document.createElement('div');div.className='check';const l=document.createElement('span');l.textContent=label;const b=document.createElement('b');b.textContent=v.replaceAll('_',' ');b.className=v.startsWith('PASS')?'pass':'pending';div.append(l,b);p.appendChild(div);
73
+ }
74
+ if(d.demo.status==='PASS_SYNTHETIC_RULE_DEMO_ONLY'){const note=document.createElement('div');note.className='check';note.textContent='KPI / Alarm sample: DEMO PASS (synthetic only)';p.appendChild(note);}
75
+ const r=$('report-summary');r.replaceChildren();for(const txt of [
76
+ 'Lab OPC batch: '+t.protocol_batch_checked+'/'+t.signals+' checked',
77
+ 'Lab behavior: '+t.lab_behavior_cases_pass+'/'+t.fat_cases+' test cases',
78
+ 'Full Ignition FAT: 0/'+t.fat_cases+' (not verified)',
79
+ 'Customer source mapping: NOT VERIFIED', 'Production: HOLD']){
80
+ const line=document.createElement('div');line.textContent=txt;r.appendChild(line);
81
+ }
82
+ const ev=$('evidence-link');const pe=$('evidence-probe-link');
83
+ ev.hidden=!d.evidence.fault;pe.hidden=!d.evidence.protocol;
84
+ if(!ev.hidden)ev.href=base+'/evidence/'+encodeURIComponent(d.evidence.fault);
85
+ if(!pe.hidden)pe.href=base+'/evidence/'+encodeURIComponent(d.evidence.protocol);
86
+ $('report-download').href=base+'/lab-report.json';
87
+ if(d.unbound_readonly)$('next-step').textContent='Read-only probe matched existing Studio tags. Fault stimulation remains blocked until a fresh, Creator-authorized lab session.';
88
+ renderRows();
89
+ }
90
+ async function loadCoverage(){try{const response=await fetch(base+'/test-coverage',{cache:'no-store'});const d=await response.json();if(!response.ok)throw Error(d.reason||'Report unavailable');renderCoverage(d);}catch(e){$('next-step').textContent='Unable to load coverage: '+e.message;}}
91
+ async function refresh(showStatus=true){try{
92
+ const r=await fetch(base+'/status',{cache:'no-store'});const d=await r.json();if(!r.ok)throw Error(d.reason||'Status unavailable');state=d;
93
+ $('project-name').textContent=d.project_name+' · '+id;
94
+ prepared=d.prepared;imported=d.studio.active_project_session===true;
95
+ const tags=d.studio.lab_tags;
96
+ const fresh=d.studio.online&&tags===0;
97
+ const resumed=d.studio.online&&imported;
98
+ const populated=d.studio.online&&tags>0&&!imported;
99
+ $('studio-online').textContent=d.studio.online?'STUDIO CONNECTED':'STUDIO OFFLINE';
100
+ $('studio-online').className='pill '+(d.studio.online?'ok':'hold');
101
+ $('studio-hint').textContent=d.studio.online?tags+' customer nodes loaded'+(d.hmi_lab_feed_attached?' · HMI LAB FEED READY (NOT IGNITION)':''):'Start your existing isolated Studio R2 first.';
102
+ $('autorun').disabled=busy||(!fresh&&!resumed&&!populated);
103
+ $('autorun').textContent=populated?'Check Existing Tags (Read Only) →':resumed?'Resume Lab Tests →':'Run Lab Tests →';
104
+ $('autorun-hint').className='notice '+(populated?'hold':'');
105
+ $('autorun-hint').textContent=populated?'Existing Studio nodes are not bound to this Creator session. Read-only checking is available if the exact project manifest matches; fault simulation remains blocked.':
106
+ resumed?'Verified Creator session — tests can safely resume.':fresh?'Isolated Studio lab ready. You will confirm before any simulated fault.':'Studio offline. You can still run the UI demo.';
107
+ $('next-step').textContent=populated?'Studio has '+tags+' tags from a previous session. Click Check Existing Tags for a safe read-only OPC check. For fault simulation use a fresh, approved lab.':
108
+ resumed?'Click Resume Lab Tests. Results below reflect only verified receipts from this Creator session.':fresh?'Click Run Lab Tests to check mapped nodes and exercise one simulated fault.':'Start your isolated Studio, or select Try Demo to test KPI rules without OPC.';
109
+ $('prepare').disabled=busy; $('sample').disabled=busy;
110
+ $('import').disabled=busy||!prepared||!fresh||imported;
111
+ $('probe').disabled=busy||!imported;
112
+ $('scenario').disabled=busy||!imported;
113
+ if(showStatus)$('result').textContent=JSON.stringify({studio:d.studio,prepared:d.prepared,guard:populated?'HOLD_EXISTING_UNBOUND_TAGS':resumed?'SAME_CREATOR_SESSION_CAN_RESUME':fresh?'FRESH_LAB_READY':'OFFLINE'},null,2);
114
+ }catch(e){for(const x of ['autorun','import','probe','scenario'])$(x).disabled=true;
115
+ $('prepare').disabled=busy;$('sample').disabled=busy;
116
+ $('studio-online').textContent='STUDIO HOLD';$('studio-hint').textContent=e.message;
117
+ $('autorun-hint').textContent='Unable to verify Studio. Do not run lab stimulus.';
118
+ $('next-step').textContent='Check Studio connection or use the sample demo.';
119
+ $('result').textContent=e.message;
120
+ }await loadCoverage();}
121
+ async function action(name){if(busy)return;
122
+ const unboundReadonly=(name==='autorun'&&state?.studio?.online&&state.studio.lab_tags>0&&state.studio.active_project_session!==true);
123
+ if(unboundReadonly){if(!confirm('Read existing Studio customer tags only, after checking exact project NodeIds and datatypes? No import, faults, writes or session adoption.'))return;}
124
+ else if(name==='autorun'&&!confirm('Run this project\'s lab mapping import (only if empty), read-only OPC checks and one simulated CLEAR → FAULT → CLEAR test on approved isolated Studio R2? Production / Ignition are NOT tested.'))return;
125
+ if(name==='import'&&!confirm('Import exact project mappings to an EMPTY isolated lab?'))return;
126
+ if(name==='scenario'&&!confirm('Apply and reset a fault on the isolated Studio simulator only?'))return;
127
+ busy=true;for(const x of ['prepare','import','probe','scenario','sample','autorun'])$(x).disabled=true;
128
+ $('result').textContent='Testing '+name+'…';$('autorun-hint').textContent='Running '+(name==='sample'?'DEMO (no Studio)':'isolated lab test')+'…';
129
+ try{const r=await fetch(base+'/'+(unboundReadonly?'readonly-probe':name),{method:'POST',headers:{'Content-Type':'application/json','X-DevAgent-Lab':'isolated-workspace-v1'},body:JSON.stringify({confirm:'ISOLATED_TEST_ONLY'})});const d=await r.json();$('result').textContent=JSON.stringify(d,null,2);
130
+ if(!r.ok){$('action-feedback').textContent='HOLD: '+(d.reason||'Lab could not finish')+' — see Advanced details.';$('advanced').open=true;}
131
+ else if(name==='sample'){$('action-feedback').textContent='DEMO PASS — synthetic KPI and alarm rules only. Live OPC/HMI FAT still pending.';}
132
+ else if(unboundReadonly){$('action-feedback').textContent='READ-ONLY LAB CHECK completed. Fault stimulus remains blocked; Ignition and physical tags NOT verified.';}
133
+ else {$('action-feedback').textContent='Lab action finished. Check Signal Tests / Test Cases for measured coverage.';}
134
+ }catch(e){$('action-feedback').textContent='HOLD: '+e.message;$('result').textContent=e.message;$('advanced').open=true;}
135
+ finally{busy=false;await refresh(false);}}
136
+ $('preview-link').href='/projects/'+id+'/viewer/index.html';
137
+ $('refresh').onclick=()=>refresh(true);
138
+ for(const x of ['prepare','import','probe','scenario','sample','autorun'])$(x).onclick=()=>action(x);
139
+ // Synthetic simulator is intentionally independent of the guarded OPC UA lab.
140
+ const simBase='/api/preview-sim/'+id;
141
+ $('sim-hmi-link').href='/projects/'+id+'/viewer/index.html';
142
+ let simBusy=false;
143
+ async function simRequest(op){
144
+ if(simBusy)return;
145
+ if(op==='start'&&!confirm('Start project-scoped SYNTHETIC parcel preview? This does not write Studio, OPC UA or PLC.'))return;
146
+ simBusy=true;
147
+ $('sim-message').textContent='Updating synthetic preview...';
148
+ try{
149
+ const payload={confirm:'SYNTHETIC_PREVIEW_ONLY'};
150
+ if(op==='fault')payload.asset_id=$('sim-asset').value;
151
+ if(op==='start')payload.pace=$('sim-pace').value;
152
+ const response=await fetch(simBase+'/'+op,{method:'POST',
153
+ headers:{'Content-Type':'application/json','X-DevAgent-Lab':'isolated-workspace-v1'},
154
+ body:JSON.stringify(payload)});
155
+ const result=await response.json();
156
+ if(!response.ok)throw Error(result.reason||'Simulation could not start');
157
+ $('sim-message').textContent=op==='stop'?'Stopped · HMI returns to UNKNOWN/N/A':
158
+ 'SYNTHETIC ONLY · '+(result.paused?'PAUSED':'RUNNING')+(result.fault_asset?' · FAULT '+result.fault_asset:'')+
159
+ ' · '+(result.path?.length||0)+' connected transport assets. Open HMI to observe.';
160
+ }catch(e){$('sim-message').textContent='HOLD: '+String(e.message||e).slice(0,160);}
161
+ finally{simBusy=false;await updateSimStatus();}
162
+ }
163
+ async function updateSimStatus(){
164
+ try{
165
+ const resp=await fetch(simBase+'/status',{cache:'no-store'});
166
+ const data=await resp.json();if(!resp.ok)throw Error(data.reason||'Unavailable');
167
+ const active=data.status==='SYNTHETIC_PREVIEW_ACTIVE';
168
+ $('sim-start').disabled=simBusy||active;
169
+ $('sim-pace').disabled=simBusy||active;
170
+ $('sim-pause').disabled=simBusy||!active;
171
+ $('sim-stop').disabled=simBusy||!active;
172
+ $('sim-fault').disabled=simBusy||!active;
173
+ $('sim-pause').textContent=data.paused?'Resume':'Pause';
174
+ $('sim-fault').textContent=data.fault_asset?'Clear fault':'Inject fault';
175
+ if(!simBusy&&!active)$('sim-message').textContent='Simulation stopped · No PLC/OPC UA writes.';
176
+ }catch(e){$('sim-message').textContent='Simulator HOLD: '+e.message;}
177
+ }
178
+ $('sim-start').addEventListener('click',()=>simRequest('start'));
179
+ $('sim-pause').addEventListener('click',()=>simRequest('pause'));
180
+ $('sim-stop').addEventListener('click',()=>simRequest('stop'));
181
+ $('sim-fault').addEventListener('click',()=>simRequest('fault'));
182
+ // Fault selection remains derived from mapping inventory, NEVER inferred from device labels.
183
+ async function populateFaultAssets(){
184
+ try{const resp=await fetch(base+'/test-coverage',{cache:'no-store'});const report=await resp.json();
185
+ const assets=[...new Set(report.signals.filter(s=>s.role==='fault'&&s.datatype==='Boolean').map(s=>s.asset_id))].sort();
186
+ const select=$('sim-asset');select.replaceChildren();
187
+ for(const asset of assets){const option=document.createElement('option');option.value=asset;option.textContent=asset;select.append(option);}
188
+ const currentlyRunning=(await (await fetch(simBase+'/status',{cache:'no-store'})).json()).status==='SYNTHETIC_PREVIEW_ACTIVE';
189
+ $('sim-fault').disabled=!assets.length||!currentlyRunning;
190
+ }catch(_){$('sim-fault').disabled=true;}
191
+ }
192
+ populateFaultAssets();updateSimStatus();
193
+ refresh();
194
+ })();
195
+ </script></body></html>