============================================================
LIMBFORGE — 3D MEASUREMENT SAVE AUDIT
============================================================
UTC: 2026-08-05T01:51:22+00:00
------------------------------------------------------------
1. LIVE SERVICES
------------------------------------------------------------
shire-limbforge-workspace.service active
shire-limbforge-canvas-gateway.service active
http://127.0.0.1:8787/health 200
http://127.0.0.1:8786/health 200
http://127.0.0.1:8787/agents/limbforge/measurements 200
------------------------------------------------------------
2. LIVE MEASUREMENT RECORD LINKAGE
------------------------------------------------------------
Measurement sessions: 1
Components: 0
Cover projects: 1
Measurement values: 0
LATEST MEASUREMENT SESSIONS
1. uuid=session-02915477-3775-4868-8c89-fad351ca6ab8 status='Not started' workflow='Osseointegration' progress=0
project_uuid='proj-3581fe0a-cfd5-4341-af75-eaab8186137b' link=VALID
component_uuid=None link=MISSING
saved_values=0
FAULT_CANDIDATE: SESSION_HAS_NO_COMPONENT_UUID
------------------------------------------------------------
3. SAVE-PATH SOURCE CONDITIONS
------------------------------------------------------------
=== src/components/workshop3d/ContextInspectorV2.jsx ===
1 | import { useEffect, useState } from 'react';
2 | import { Ruler, Box, Layers, Footprints, ShieldAlert, Save, RotateCcw, Ban, History, CheckCircle2, AlertTriangle, XCircle } from 'lucide-react';
3 | import { base44 } from '@/api/base44Client';
>> 4 | import { saveStationMeasurement, GEOMETRY_STATUS, STATUS_TONE } from '@/services/measurementGeometryService';
5 |
6 | const STATION_LABELS = {
7 | 'pylon': 'Pylon Diameter',
8 | 'cover-lower': 'Cover Lower Circumference',
9 | 'socket-distal': 'Socket Distal Circumference',
10 | 'socket-middle': 'Socket Middle Circumference',
11 | 'cover-widest': 'Widest Cover / Calf Region',
12 | 'socket-upper': 'Socket Upper Circumference',
13 | 'socket-proximal': 'Socket Proximal Boundary',
24 |
25 | function Pill({ tone, children }) {
26 | const map = { green: 'border-emerald-400/40 bg-emerald-400/10 text-emerald-300', amber: 'border-amber-400/40 bg-amber-400/10 text-amber-300', red: 'border-red-400/40 bg-red-400/10 text-red-300', purple: 'border-violet-400/40 bg-violet-400/10 text-violet-300', slate: 'border-white/15 bg-white/5 text-slate-300' };
27 | return {children};
28 | }
29 |
>> 30 | export default function ContextInspectorV2({ selection, project, sessionId, componentUuid, geometry, onSaved }) {
31 | const [busy, setBusy] = useState(false);
32 | const [form, setForm] = useState({ total_width_mm: '', total_depth_mm: '', circumference_mm: '', height_from_origin_mm: '', notes: '' });
33 | const [existing, setExisting] = useState(null);
34 | const [linkedComponent, setLinkedComponent] = useState(null);
35 |
36 | const kind = selection ? selection.type : null;
37 | const isStation = kind === 'measurementStation';
38 | const isPart = kind === 'prosthesisPart';
39 | const isCover = kind === 'cover';
40 | const partId = isPart ? selection.partId : isCover ? 'cover-shell' : null;
41 |
42 | // Load existing station value when a station is selected
43 | useEffect(() => {
>> 44 | if (!isStation || !sessionId) return;
45 | let alive = true;
46 | const name = selection.stationName || STATION_LABELS[selection.stationId] || selection.stationId;
47 | base44.entities.EnvelopeStation.filter({ session_uuid: sessionId, station_name: name })
48 | .then((list) => {
49 | if (!alive) return;
50 | const rec = list && list[0];
51 | setExisting(rec || null);
52 | if (rec) {
53 | setForm({
54 | total_width_mm: rec.total_width_mm ?? '',
55 | total_depth_mm: rec.total_depth_mm ?? '',
56 | circumference_mm: rec.circumference_mm ?? '',
60 | } else {
61 | setForm({ total_width_mm: '', total_depth_mm: '', circumference_mm: '', height_from_origin_mm: '', notes: '' });
62 | }
63 | })
64 | .catch(() => { if (alive) setExisting(null); });
65 | return () => { alive = false; };
>> 66 | }, [isStation, sessionId, selection && selection.stationId]);
67 |
68 | // Load linked component for hardware parts
69 | useEffect(() => {
70 | if (!isPart || !project) return;
71 | const uuids = project.component_uuids || [];
72 | if (!uuids.length) { setLinkedComponent(null); return; }
73 | let alive = true;
74 | base44.entities.Component.filter({ uuid: { $in: uuids } })
75 | .then((list) => { if (alive) setLinkedComponent((list || [])[0] || null); })
76 | .catch(() => { if (alive) setLinkedComponent(null); });
77 | return () => { alive = false; };
78 | }, [isPart, project && project.uuid]);
79 |
80 | const handleSave = async () => {
>> 81 | if (!isStation || !sessionId) return;
82 | setBusy(true);
83 | try {
84 | const name = selection.stationName || STATION_LABELS[selection.stationId] || selection.stationId;
85 | await saveStationMeasurement({
86 | sessionUuid: sessionId,
87 | componentUuid: componentUuid || (project && (project.component_uuids || [])[0]) || 'generic',
88 | station: {
89 | station_name: name,
90 | total_width_mm: form.total_width_mm,
91 | total_depth_mm: form.total_depth_mm,
92 | circumference_mm: form.circumference_mm,
93 | height_from_origin_mm: form.height_from_origin_mm,
94 | notes: form.notes,
95 | },
96 | });
97 | onSaved && onSaved();
98 | } finally { setBusy(false); }
99 | };
100 |
101 | const handleNotApplicable = async () => {
>> 102 | if (!isStation || !sessionId) return;
103 | setBusy(true);
104 | try {
105 | const name = selection.stationName || STATION_LABELS[selection.stationId] || selection.stationId;
106 | await saveStationMeasurement({ sessionUuid: sessionId, componentUuid: componentUuid || 'generic', station: { station_name: name, notes: 'Not applicable', shape_classification: 'Not applicable' } });
107 | onSaved && onSaved();
108 | } finally { setBusy(false); }
109 | };
110 |
111 | const statusTone = geometry ? STATUS_TONE[geometry.geometryStatus] : 'slate';
112 |
113 | return (
114 |
115 |
139 | setForm((f) => ({ ...f, total_depth_mm: v }))} />
140 | setForm((f) => ({ ...f, circumference_mm: v }))} />
141 | setForm((f) => ({ ...f, height_from_origin_mm: v }))} />
142 |
143 |
150 | )}
151 |
152 | {isPart && partId === 'socket' && (
153 |
154 |
155 |
{GEOMETRY_STATUS[geometry?.geometryStatus || 'generic']}
156 |
157 |
=== src/components/workshop3d/panels/MeasurementInspector.jsx ===
11 |
12 | {children}
13 |
14 | );
15 | }
16 |
>> 17 | export default function MeasurementInspector({ selected, component, stationIndex, onSave, onMeasureAgain, onNotApplicable, previousValue, onAddPhoto }) {
18 | const s = selected?.data || {};
19 | const name = s.name || s.station_name || (stationIndex != null ? `Station ${String(stationIndex + 1).padStart(2, '0')}` : 'Measurement');
20 | const width = s.width ?? s.total_width_mm;
21 | const depth = s.depth ?? s.total_depth_mm;
22 | const height = s.height ?? s.height_from_origin_mm;
23 |
24 | const [value, setValue] = useState('');
25 | const [unit, setUnit] = useState('mm');
26 | const [source, setSource] = useState(SOURCES[1]);
27 | const [verification, setVerification] = useState(VERIFICATIONS[2]);
28 | const [confidence, setConfidence] = useState(95);
29 | const [notes, setNotes] = useState('');
30 |
31 | const diff = previousValue && value ? (Number(value) - previousValue).toFixed(1) : null;
32 |
33 | const save = () => onSave?.({ selected, value, unit, source, verification, confidence, notes });
34 |
35 | return (
36 |
37 | {/* Header dropdown */}
38 |
39 |
40 |
41 |
42 | {stationIndex != null ? `Measurement Station ${String(stationIndex + 1).padStart(2, '0')}` : 'Measurement'}
50 |
51 |
Measurement Name
52 |
{name}
53 |
54 |
55 |
>> 56 | setValue(e.target.value)} placeholder="Measurement required — enter value" className="workshop-input flex-1 !min-h-10" />
57 |
62 |
63 |
64 |
65 | {width != null && (
66 |
67 | Width × Depth
68 | {width.toFixed ? width.toFixed(0) : width} × {depth?.toFixed ? depth.toFixed(0) : depth} mm
69 |
70 | )}
71 |
72 |
>> 73 |
76 |
77 |
78 |
79 |
80 |
83 | Verified
84 |
85 |
86 |
87 |
88 |
89 | setConfidence(parseInt(e.target.value))} className="flex-1 accent-cyan-400" />
90 | High ({confidence}%)
91 |
92 |
93 |
94 |
95 |
97 |
98 | {previousValue != null && (
99 |
100 |
Previous value
101 |
102 | {previousValue.toFixed ? previousValue.toFixed(1) : previousValue} mm
103 | {diff && Number(diff) !== 0 && (
104 | 0 ? 'text-amber-300' : 'text-emerald-300'}`}>
105 | {Number(diff) > 0 ? '+' : ''}{diff} mm
106 |
107 | )}
108 |
109 |
112 |
113 |
114 |
115 |
116 |
117 |
>> 118 |
121 |
124 |
125 |
126 | );
127 | }
=== src/components/workshop3d/workflows/MeasurementStudio3D.jsx ===
12 | 'Knee component': 'lateral',
13 | 'Lower leg': 'lateral',
14 | 'Upper limb': 'lateral',
15 | Osseointegration: 'front',
16 | };
17 |
>> 18 | export default function MeasurementStudio3D({ session, onGuidedView, onHighlightStation, onComplete, actor = 'Ray' }) {
19 | const [definitions, setDefinitions] = useState([]);
20 | const [index, setIndex] = useState(0);
21 | const [value, setValue] = useState('');
22 | const [enteredUnit, setEnteredUnit] = useState('mm');
23 | const [confidence, setConfidence] = useState('High');
24 | const [notes, setNotes] = useState('');
25 | const [saving, setSaving] = useState(false);
26 | const [warning, setWarning] = useState('');
27 |
28 | useEffect(() => {
>> 29 | if (session?.workflow) {
30 | measurementService.definitions(session.workflow).then(defs => {
31 | setDefinitions(defs.filter(d => d.active !== false));
32 | setIndex(0);
33 | setValue('');
34 | setNotes('');
35 | setWarning('');
36 | });
37 | }
38 | }, [session?.uuid, session?.workflow]);
39 |
40 | const current = definitions[index];
41 |
42 | useEffect(() => {
43 | if (current) {
44 | const view = VIEW_BY_CATEGORY[current.category] || VIEW_BY_CATEGORY[current.workflow] || 'perspective';
45 | onGuidedView?.(view);
46 | onHighlightStation?.(index % 5);
47 | }
48 | }, [index, current]);
49 |
>> 50 | if (!session) {
51 | return (
52 |
53 |
54 |
No active measurement session.
55 |
Start a session from the Measurement Studio to begin guided 3D recording.
56 |
57 | );
58 | }
59 |
60 | if (!current) {
61 | return (
62 |
63 |
64 |
All measurements complete.
68 |
69 | const handleSave = async () => {
70 | if (!value) return;
71 | setSaving(true);
72 | setWarning('');
73 | try {
>> 74 | const saved = await measurementService.save(session, current, { value: Number(value), entered_unit: enteredUnit, confidence, notes, photo_url: '' }, actor);
75 | if (saved.warning) setWarning(saved.warning);
76 | setValue('');
77 | setNotes('');
78 | if (index < definitions.length - 1) {
79 | setIndex(index + 1);
80 | } else {
81 | await measurementService.progress(session, definitions.length, definitions.length);
82 | onComplete?.();
83 | }
84 | } catch (e) {
85 | setWarning('Save failed: ' + e.message);
86 | }
87 | setSaving(false);
88 | };
89 |
90 | const handleSkip = async () => {
91 | setSaving(true);
92 | try {
>> 93 | await measurementService.skip(session, current, 'Not applicable for this limb', actor);
94 | if (index < definitions.length - 1) setIndex(index + 1);
95 | else onComplete?.();
96 | } catch (e) { setWarning('Skip failed'); }
97 | setSaving(false);
98 | };
99 |
100 | const progress = definitions.length ? Math.round((index / definitions.length) * 100) : 0;
101 |
102 | return (
152 |
{warning}
153 |
154 | )}
155 |
156 |
157 |
>> 158 |
159 |
160 |
161 |
162 |
163 | );
164 | }
=== src/pages/Workshop3D.jsx ===
7 | import ViewportDiagnostics from '@/components/workshop3d/ViewportDiagnostics';
8 | import SceneComposer from '@/components/workshop3d/SceneComposer';
9 | import ProjectTree from '@/components/workshop3d/panels/ProjectTree';
10 | import ViewportToolbar from '@/components/workshop3d/panels/ViewportToolbar';
11 | import ContextPanel from '@/components/workshop3d/panels/ContextPanel';
12 | import StationTable from '@/components/workshop3d/panels/StationTable';
>> 13 | import MeasurementInspector from '@/components/workshop3d/panels/MeasurementInspector';
14 | import StatusCards from '@/components/workshop3d/panels/StatusCards';
15 | import SafetyBoundary from '@/components/workshop3d/panels/SafetyBoundary';
16 | import TopBar from '@/components/workshop3d/panels/TopBar';
17 | import VerticalToolStrip from '@/components/workshop3d/panels/VerticalToolStrip';
18 | import ViewportErrorBoundary from '@/components/ViewportErrorBoundary';
19 | import MeasurementStudio3D from '@/components/workshop3d/workflows/MeasurementStudio3D';
20 | import SensitiveZoneMapper from '@/components/workshop3d/workflows/SensitiveZoneMapper';
21 | import ProsthesisMapper from '@/components/workshop3d/workflows/ProsthesisMapper';
22 | import CoverBuilder from '@/components/workshop3d/workflows/CoverBuilder';
55 | const [dbTree, setDbTree] = useState([]);
56 | const [loading, setLoading] = useState(true);
57 | const [customers, setCustomers] = useState([]);
58 | const [limbs, setLimbs] = useState([]);
59 | const [prostheses, setProstheses] = useState([]);
60 | const [projects, setProjects] = useState([]);
>> 61 | const [sessions, setSessions] = useState([]);
62 | const [componentsList, setComponentsList] = useState([]);
63 | const [selected, setSelected] = useState(null);
64 | const [selectedId, setSelectedId] = useState(null);
65 |
66 | const [viewPreset, setViewPreset] = useState('perspective');
67 | const [ortho, setOrtho] = useState(false);
68 | const [transparency, setTransparency] = useState(1);
69 | const [sectionCut, setSectionCut] = useState(false);
70 | const [display, setDisplay] = useState({ measurements: true, sensitive: true, keepOut: true, access: true, cover: true, movement: true, logo: true });
>> 71 | const [activeWorkflow, setActiveWorkflow] = useState(null);
72 | const [activeTool, setActiveTool] = useState('select');
73 |
74 | const [component, setComponent] = useState(null);
75 | const [dimensions, setDimensions] = useState([]);
76 | const [stations, setStations] = useState([]);
77 | const [accessPoints, setAccessPoints] = useState([]);
78 | const [keepOutZones, setKeepOutZones] = useState([]);
79 | const [movementPositions, setMovementPositions] = useState([]);
80 | const [missingInfo, setMissingInfo] = useState([]);
>> 81 | const [session, setSession] = useState(null);
82 | const [highlightedStation, setHighlightedStation] = useState(-1);
83 | const [selectedAngle, setSelectedAngle] = useState(null);
84 |
85 | const [sensitiveZones, setSensitiveZones] = useState([]);
86 | const [zoneHistory, setZoneHistory] = useState([]);
87 | const [redoStack, setRedoStack] = useState([]);
88 | const [paintSettings, setPaintSettings] = useState({ type: 'No contact', color: '#ef4444', brushSize: 15 });
89 |
90 | const [assembly, setAssembly] = useState([]);
105 | Promise.all([
106 | customerService.list(),
107 | limbProfileService.list(),
108 | prosthesisService.list(),
109 | projectService.list(),
110 | componentService.list(),
>> 111 | measurementService.sessions().catch(() => []),
112 | ]).then(([custs, limbs, pros, projs, comps, sess]) => {
113 | if (!mounted.current) return;
114 | const activeCusts = (custs || []).filter(c => c.record_state !== 'archived');
115 | const activeLimbs = (limbs || []).filter(l => l.record_state !== 'archived');
116 | const activePros = (pros || []).filter(p => p.record_state !== 'archived');
117 | const activeProjs = (projs || []).filter(p => p.record_state !== 'archived');
118 | const activeComps = (comps || []).filter(c => c.record_state !== 'archived' && c.status !== 'obsolete');
119 | const activeSessions = (sess || []).filter(s => s.record_state !== 'archived');
120 |
223 | } catch { if (mounted.current) setMissingInfo([]); }
224 | }, []);
225 |
226 | useEffect(() => {
227 | if (selected?.type === 'component' && selected.data && !selected.placeholder) {
228 | loadComponent3DData(selected.data);
>> 229 | measurementService.sessions().then(ss => {
230 | if (!mounted.current) return;
231 | const found = ss.find(s => s.component_uuid === selected.data.uuid && s.status !== 'Complete');
232 | if (found) setSession(found);
233 | });
234 | }
235 | }, [selectedId]); // eslint-disable-line react-hooks/exhaustive-deps
236 |
237 | const handleTreeSelect = (node) => {
238 | setSelected(node);
252 | if (view === 'ortho') { setOrtho(!ortho); return; }
253 | setViewPreset(view);
254 | };
255 | const handleToggle = (key) => setDisplay(d => ({ ...d, [key]: !d[key] }));
256 |
257 | const handlePaint = useCallback((point) => {
>> 258 | if (activeWorkflow !== 'sensitive') return;
259 | const zone = { ...paintSettings, position: [point.x, point.y, point.z] };
260 | setZoneHistory(h => [...h, sensitiveZones]);
261 | setSensitiveZones(z => [...z, zone]);
262 | setRedoStack([]);
263 | }, [activeWorkflow, paintSettings, sensitiveZones]);
264 |
265 | const handleUndo = () => {
266 | if (zoneHistory.length === 0) return;
267 | setRedoStack(r => [...r, sensitiveZones]);
268 | setSensitiveZones(zoneHistory[zoneHistory.length - 1]);
269 | setZoneHistory(h => h.slice(0, -1));
270 | };
271 | const handleRedo = () => {
272 | if (redoStack.length === 0) return;
284 |
285 | const mode = component && component.category === 'Knee' ? 'component' : 'limb';
286 |
287 | const isStation = selected?.type === 'station';
288 |
289 | const renderRightPanel = () => {
>> 290 | if (activeWorkflow === 'measure') {
291 | return setActiveWorkflow(null)} />;
292 | }
293 | if (activeWorkflow === 'sensitive') {
294 | return 0} canRedo={redoStack.length > 0} />;
295 | }
296 | if (activeWorkflow === 'assembly') {
297 | return setAssembly(a => [...a, c])} onRemove={(i) => setAssembly(a => a.filter((_, idx) => idx !== i))} onUpdate={(i, d) => setAssembly(a => a.map((x, idx) => idx === i ? { ...x, ...d } : x))} onReorder={(i, dir) => setAssembly(a => { const j = i + dir; if (j < 0 || j >= a.length) return a; const copy = [...a]; [copy[i], copy[j]] = [copy[j], copy[i]]; return copy; })} />;
298 | }
299 | if (activeWorkflow === 'cover') {
300 | return setExploded(!exploded)} exploded={exploded} />;
301 | }
302 | // Browse mode
303 | return (
304 | <>
305 | {isStation ? (
306 | setHighlightedStation(-1)}
311 | onMeasureAgain={() => setHighlightedStation(selected.index)}
312 | onNotApplicable={() => setSelected(null)}
313 | />
314 | ) : (
315 |
316 | )}
317 |
318 |
319 |
320 | View All Measurements
321 |
322 | >
323 | );
324 | };
325 |
>> 326 | const sessionStatus = session?.status === 'Complete' ? 'Session Complete' : session ? 'Live Capture' : 'Missing Measurements';
327 |
328 | return (
329 |
330 |
331 |
332 |
333 | {/* Left panel — Navigation + Project Tree */}
334 |
401 |
402 |
setViewPreset('fit')} onReset={() => setViewPreset('reset')} onSection={() => setSectionCut(!sectionCut)} />
403 |
424 | highlightedStation={highlightedStation}
425 | selectedAngle={selectedAngle}
426 | show={display}
427 | transparency={transparency}
428 | coverClearance={coverParams.clearance}
429 | coverThickness={coverParams.wallThickness}
>> 430 | onPaint={activeWorkflow === 'sensitive' ? handlePaint : null}
431 | paintColor={paintSettings.color}
432 | compatibility={compatibility}
433 | />
434 |
435 |
436 |
437 | {/* DOM label overlay — outside the Canvas, projected from the live camera */}
438 |
439 |
448 | {!component && (
449 |
450 | Select a component from the project tree to load its 3D model — a generic measurement model is shown meanwhile.
451 |
452 | )}
453 |
>> 454 | {activeWorkflow === 'sensitive' && (
455 |
456 | Paint mode active — click on the 3D model to place zones
457 |
458 | )}
459 | {sectionCut && (
460 | Section cut active
461 | )}
462 |
463 |
=== src/services/measurementGeometryService.js ===
85 | };
86 | }
87 |
88 | /**
89 | * Upsert a workshop cross-section station measurement (drives geometry).
90 | */
>> 91 | export async function saveStationMeasurement({ sessionUuid, componentUuid, station }) {
92 | let existing = null;
93 | try {
94 | const list = await base44.entities.EnvelopeStation.filter({ session_uuid: sessionUuid, station_name: station.station_name });
95 | if (list && list.length) existing = list[0];
96 | } catch (_) {}
97 | const today = new Date().toISOString().slice(0, 10);
98 | const payload = {
99 | component_uuid: componentUuid || 'generic',
100 | session_uuid: sessionUuid,
101 | station_name: station.station_name,
102 | height_from_origin_mm: num(station.height_from_origin_mm),
103 | total_width_mm: num(station.total_width_mm),
104 | total_depth_mm: num(station.total_depth_mm),
105 | max_medial_mm: num(station.max_medial_mm),
106 | max_lateral_mm: num(station.max_lateral_mm),
107 | max_anterior_mm: num(station.max_anterior_mm),
108 | max_posterior_mm: num(station.max_posterior_mm),
109 | circumference_mm: num(station.circumference_mm),
110 | clearance_requirement_mm: num(station.clearance_requirement_mm),
111 | shape_classification: station.shape_classification || 'Workshop measured',
112 | entered_by: station.entered_by || 'Workshop',
113 | date_entered: today,
114 | notes: station.notes || '',
115 | };
>> 116 | if (existing) return base44.entities.EnvelopeStation.update(existing.id, payload);
117 | return base44.entities.EnvelopeStation.create(payload);
118 | }
119 |
120 | const num = (v) => (v === '' || v == null ? undefined : Number(v));
------------------------------------------------------------
4. VERIFY CURRENT BUILD CONTAINS SAVE LOGIC
------------------------------------------------------------
Current bundle: /home/shire3d/ARMOR/agents/apps/limbforge/dist/shire-module/assets/shire-index-Bhe3JZ2g.js
Bundle SHA-256: e3dce0bc5ed0348e8c919d2e89001848a9ea7f521775488d3945e96d46b9c423
NOTICE: Bundle does not contain exact text: No active session
NOTICE: Bundle does not contain exact text: No active measurement session
PASS: Bundle contains: MeasurementValue
PASS: Bundle contains: session_uuid
PASS: Bundle contains: component_uuid
------------------------------------------------------------
5. RECENT MEASUREMENT REQUESTS AND ERRORS
------------------------------------------------------------
Aug 05 09:21:45 Work python3[2433881]: 05/Aug/2026 09:21:45 - "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 09:21:45 Work python3[2446335]: limbforge 05/Aug/2026 09:21:45 "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 09:21:45 Work python3[2433881]: 05/Aug/2026 09:21:45 - "POST /api/v1/collections/CoverProject/filter HTTP/1.1" 200 -
Aug 05 09:21:45 Work python3[2446335]: limbforge 05/Aug/2026 09:21:45 "POST /api/v1/collections/CoverProject/filter HTTP/1.1" 200 -
Aug 05 09:21:45 Work python3[2433881]: 05/Aug/2026 09:21:45 - "POST /api/v1/collections/LimbProfile/filter HTTP/1.1" 200 -
Aug 05 09:21:45 Work python3[2446335]: limbforge 05/Aug/2026 09:21:45 "POST /api/v1/collections/LimbProfile/filter HTTP/1.1" 200 -
Aug 05 09:21:45 Work python3[2433881]: 05/Aug/2026 09:21:45 - "POST /api/v1/collections/Customer/filter HTTP/1.1" 200 -
Aug 05 09:21:45 Work python3[2446335]: limbforge 05/Aug/2026 09:21:45 "POST /api/v1/collections/Customer/filter HTTP/1.1" 200 -
Aug 05 09:21:45 Work python3[2433881]: 05/Aug/2026 09:21:45 - "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:21:45 Work python3[2446335]: limbforge 05/Aug/2026 09:21:45 "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:25:31 Work python3[2433881]: 05/Aug/2026 09:25:31 - "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 09:25:31 Work python3[2446335]: limbforge 05/Aug/2026 09:25:31 "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 09:25:31 Work python3[2433881]: 05/Aug/2026 09:25:31 - "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2446335]: limbforge 05/Aug/2026 09:25:31 "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2433881]: 05/Aug/2026 09:25:31 - "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2446335]: limbforge 05/Aug/2026 09:25:31 "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2433881]: 05/Aug/2026 09:25:31 - "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2446335]: limbforge 05/Aug/2026 09:25:31 "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2433881]: 05/Aug/2026 09:25:31 - "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2446335]: limbforge 05/Aug/2026 09:25:31 "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2433881]: 05/Aug/2026 09:25:31 - "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2446335]: limbforge 05/Aug/2026 09:25:31 "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2433881]: 05/Aug/2026 09:25:31 - "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2446335]: limbforge 05/Aug/2026 09:25:31 "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2433881]: 05/Aug/2026 09:25:31 - "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2446335]: limbforge 05/Aug/2026 09:25:31 "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2433881]: 05/Aug/2026 09:25:31 - "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2446335]: limbforge 05/Aug/2026 09:25:31 "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2433881]: 05/Aug/2026 09:25:31 - "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2446335]: limbforge 05/Aug/2026 09:25:31 "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2433881]: 05/Aug/2026 09:25:31 - "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2446335]: limbforge 05/Aug/2026 09:25:31 "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2433881]: 05/Aug/2026 09:25:31 - "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2446335]: limbforge 05/Aug/2026 09:25:31 "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2433881]: 05/Aug/2026 09:25:31 - "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:25:31 Work python3[2446335]: limbforge 05/Aug/2026 09:25:31 "POST /api/v1/collections/MeasurementDefinition HTTP/1.1" 201 -
Aug 05 09:29:25 Work python3[2433881]: 05/Aug/2026 09:29:25 - "POST /api/v1/collections/MeasurementDefinition/filter HTTP/1.1" 200 -
Aug 05 09:29:25 Work python3[2449630]: limbforge 05/Aug/2026 09:29:25 "POST /api/v1/collections/MeasurementDefinition/filter HTTP/1.1" 200 -
Aug 05 09:29:25 Work python3[2433881]: 05/Aug/2026 09:29:25 - "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:29:25 Work python3[2449630]: limbforge 05/Aug/2026 09:29:25 "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:33:03 Work python3[2433881]: 05/Aug/2026 09:33:03 - "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:33:03 Work python3[2449630]: limbforge 05/Aug/2026 09:33:03 "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:33:03 Work python3[2449630]: limbforge 05/Aug/2026 09:33:03 "GET /favicon.ico HTTP/1.1" 404 -
Aug 05 09:33:03 Work python3[2433881]: 05/Aug/2026 09:33:03 - "POST /api/v1/collections/MeasurementDefinition/filter HTTP/1.1" 200 -
Aug 05 09:33:03 Work python3[2449630]: limbforge 05/Aug/2026 09:33:03 "POST /api/v1/collections/MeasurementDefinition/filter HTTP/1.1" 200 -
Aug 05 09:33:03 Work python3[2433881]: 05/Aug/2026 09:33:03 - "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:33:03 Work python3[2449630]: limbforge 05/Aug/2026 09:33:03 "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:38:34 Work python3[2433881]: 05/Aug/2026 09:38:34 - "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 09:38:34 Work python3[2449630]: limbforge 05/Aug/2026 09:38:34 "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 09:46:48 Work python3[2433881]: 05/Aug/2026 09:46:48 - "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:46:48 Work python3[2449630]: limbforge 05/Aug/2026 09:46:48 "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:46:48 Work python3[2449630]: limbforge 05/Aug/2026 09:46:48 "GET /favicon.ico HTTP/1.1" 404 -
Aug 05 09:46:48 Work python3[2433881]: 05/Aug/2026 09:46:48 - "POST /api/v1/collections/MeasurementDefinition/filter HTTP/1.1" 200 -
Aug 05 09:46:48 Work python3[2449630]: limbforge 05/Aug/2026 09:46:48 "POST /api/v1/collections/MeasurementDefinition/filter HTTP/1.1" 200 -
Aug 05 09:46:48 Work python3[2433881]: 05/Aug/2026 09:46:48 - "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:46:48 Work python3[2449630]: limbforge 05/Aug/2026 09:46:48 "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:47:05 Work python3[2433881]: 05/Aug/2026 09:47:05 - "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 09:47:05 Work python3[2449630]: limbforge 05/Aug/2026 09:47:05 "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 09:47:05 Work python3[2449630]: limbforge 05/Aug/2026 09:47:05 "GET /favicon.ico HTTP/1.1" 404 -
Aug 05 09:47:07 Work python3[2433881]: 05/Aug/2026 09:47:07 - "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:47:07 Work python3[2449630]: limbforge 05/Aug/2026 09:47:07 "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:47:07 Work python3[2449630]: limbforge 05/Aug/2026 09:47:07 "GET /favicon.ico HTTP/1.1" 404 -
Aug 05 09:47:07 Work python3[2433881]: 05/Aug/2026 09:47:07 - "POST /api/v1/collections/MeasurementDefinition/filter HTTP/1.1" 200 -
Aug 05 09:47:07 Work python3[2433881]: 05/Aug/2026 09:47:07 - "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:47:07 Work python3[2449630]: limbforge 05/Aug/2026 09:47:07 "POST /api/v1/collections/MeasurementDefinition/filter HTTP/1.1" 200 -
Aug 05 09:47:07 Work python3[2449630]: limbforge 05/Aug/2026 09:47:07 "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:47:21 Work python3[2449630]: limbforge 05/Aug/2026 09:47:21 "GET /favicon.ico HTTP/1.1" 404 -
Aug 05 09:47:21 Work python3[2449630]: limbforge 05/Aug/2026 09:47:21 "GET /favicon.ico HTTP/1.1" 404 -
Aug 05 09:47:21 Work python3[2433881]: 05/Aug/2026 09:47:21 - "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 09:47:21 Work python3[2449630]: limbforge 05/Aug/2026 09:47:21 "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 09:47:22 Work python3[2449630]: limbforge 05/Aug/2026 09:47:22 "GET /favicon.ico HTTP/1.1" 404 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/CoverProject/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/CoverProject/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "GET /favicon.ico HTTP/1.1" 404 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/Customer/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/LimbProfile/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/LimbProfile/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/Customer/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/Prosthesis/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/Prosthesis/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/Quote/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/Quote/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/Order/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/Order/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/ManufacturingJob/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/ManufacturingJob/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/DesignConfiguration/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/DesignConfiguration/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/CompletedCover/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/CompletedCover/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/DesignConfiguration/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/DesignConfiguration/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/EnvelopeStation/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/EnvelopeStation/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2433881]: 05/Aug/2026 09:47:25 - "POST /api/v1/collections/EnvelopeStation/filter HTTP/1.1" 200 -
Aug 05 09:47:25 Work python3[2449630]: limbforge 05/Aug/2026 09:47:25 "POST /api/v1/collections/EnvelopeStation/filter HTTP/1.1" 200 -
Aug 05 09:47:36 Work python3[2449630]: limbforge 05/Aug/2026 09:47:36 "GET /favicon.ico HTTP/1.1" 404 -
Aug 05 09:47:36 Work python3[2449630]: limbforge 05/Aug/2026 09:47:36 "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:47:36 Work python3[2433881]: 05/Aug/2026 09:47:36 - "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:47:37 Work python3[2433881]: 05/Aug/2026 09:47:37 - "POST /api/v1/collections/MeasurementDefinition/filter HTTP/1.1" 200 -
Aug 05 09:47:37 Work python3[2449630]: limbforge 05/Aug/2026 09:47:37 "POST /api/v1/collections/MeasurementDefinition/filter HTTP/1.1" 200 -
Aug 05 09:47:37 Work python3[2433881]: 05/Aug/2026 09:47:37 - "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:47:37 Work python3[2449630]: limbforge 05/Aug/2026 09:47:37 "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:48:35 Work python3[2433881]: 05/Aug/2026 09:48:35 - "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:48:35 Work python3[2449630]: limbforge 05/Aug/2026 09:48:35 "POST /api/v1/collections/MeasurementSession/filter HTTP/1.1" 200 -
Aug 05 09:48:35 Work python3[2449630]: limbforge 05/Aug/2026 09:48:35 "GET /favicon.ico HTTP/1.1" 404 -
Aug 05 09:48:35 Work python3[2433881]: 05/Aug/2026 09:48:35 - "POST /api/v1/collections/MeasurementDefinition/filter HTTP/1.1" 200 -
Aug 05 09:48:35 Work python3[2449630]: limbforge 05/Aug/2026 09:48:35 "POST /api/v1/collections/MeasurementDefinition/filter HTTP/1.1" 200 -
Aug 05 09:48:35 Work python3[2433881]: 05/Aug/2026 09:48:35 - "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:48:35 Work python3[2449630]: limbforge 05/Aug/2026 09:48:35 "POST /api/v1/collections/MeasurementValue/filter HTTP/1.1" 200 -
Aug 05 09:51:22 Work python3[2433881]: 05/Aug/2026 09:51:22 - "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 09:51:22 Work python3[2449630]: limbforge 05/Aug/2026 09:51:22 "GET /api/v1/collections/MeasurementSession?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 09:51:22 Work python3[2433881]: 05/Aug/2026 09:51:22 - "GET /api/v1/collections/MeasurementValue?sort=-updated_date&limit=500 HTTP/1.1" 200 -
Aug 05 09:51:22 Work python3[2449630]: limbforge 05/Aug/2026 09:51:22 "GET /api/v1/collections/MeasurementValue?sort=-updated_date&limit=500 HTTP/1.1" 200 -
------------------------------------------------------------
6. CURRENT COLLECTION READ TESTS
------------------------------------------------------------
MeasurementSession HTTP 200
MeasurementDefinition HTTP 200
MeasurementValue HTTP 200
EnvelopeStation HTTP 200
Component HTTP 200
============================================================
AUDIT COMPLETE — NOTHING WAS CHANGED
============================================================
Evidence: /SHiREVault/Backup/OSBackups/LIMBFORGE-3D-MEASUREMENT-SAVE-AUDIT-20260805T015122Z
No source, database records or services were changed.
No service was restarted.
Sentinel was not modified or restarted.