From 7bd7ff505503af5b1bde53a2299c0e132ff4beb8 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Mon, 17 Aug 2026 17:31:34 -0400 Subject: [PATCH] feat: close hidden link property evidence --- .gitattributes | 1 + chrome-property-linklisthidden-harness.html | 5 + ...-property-linklisthidden-verification.json | 85 ++ config/freecad-active-work-queue.json | 50 +- config/freecad-follow-up-task-progress.json | 52 + config/freecad-native-property-semantics.json | 18 +- ...eecad-property-acceleration-promotion.json | 12 +- config/freecad-property-area-promotion.json | 12 +- .../freecad-property-boollist-promotion.json | 12 +- config/freecad-property-color-promotion.json | 12 +- .../freecad-property-colorlist-promotion.json | 12 +- .../freecad-property-direction-promotion.json | 12 +- config/freecad-property-file-promotion.json | 12 +- ...eecad-property-fileincluded-promotion.json | 12 +- config/freecad-property-font-promotion.json | 12 +- config/freecad-property-force-promotion.json | 12 +- .../freecad-property-heatflux-promotion.json | 12 +- ...freecad-property-integerset-promotion.json | 12 +- ...eecad-property-linklisthidden-failure.json | 928 ++++++++++++++ ...cad-property-linklisthidden-inventory.json | 148 +++ ...ecad-property-linklisthidden-mutation.json | 663 ++++++++++ ...cad-property-linklisthidden-promotion.json | 84 ++ ...cad-property-linklisthidden-roundtrip.json | 375 ++++++ ...eecad-property-linklisthidden-success.json | 691 ++++++++++ ...reecad-property-linksubhidden-failure.json | 1100 ++++++++++++++++ ...ecad-property-linksubhidden-inventory.json | 246 ++++ ...reecad-property-linksubhidden-success.json | 1108 +++++++++++++++++ config/freecad-web-exact-parity-plan.json | 4 +- ...ecad-active-work-status.generated.zh-CN.md | 8 +- package.json | 22 + .../check-chrome-property-linklisthidden.mjs | 16 + scripts/check-facade-boundary.mjs | 1 + ...heck-freecad-native-property-semantics.mjs | 8 +- ...reecad-property-acceleration-promotion.mjs | 2 +- .../check-freecad-property-area-promotion.mjs | 2 +- ...ck-freecad-property-boollist-promotion.mjs | 2 +- ...check-freecad-property-color-promotion.mjs | 2 +- ...k-freecad-property-colorlist-promotion.mjs | 2 +- ...k-freecad-property-direction-promotion.mjs | 2 +- .../check-freecad-property-file-promotion.mjs | 2 +- ...reecad-property-fileincluded-promotion.mjs | 2 +- .../check-freecad-property-font-promotion.mjs | 2 +- ...check-freecad-property-force-promotion.mjs | 2 +- ...ck-freecad-property-heatflux-promotion.mjs | 2 +- ...-freecad-property-integerset-promotion.mjs | 2 +- ...reecad-property-linklisthidden-failure.mjs | 48 + ...ecad-property-linklisthidden-inventory.mjs | 50 + ...eecad-property-linklisthidden-mutation.mjs | 30 + ...ecad-property-linklisthidden-promotion.mjs | 28 + ...ecad-property-linklisthidden-roundtrip.mjs | 26 + ...reecad-property-linklisthidden-success.mjs | 41 + ...freecad-property-linksubhidden-failure.mjs | 57 + ...eecad-property-linksubhidden-inventory.mjs | 36 + ...freecad-property-linksubhidden-success.mjs | 29 + ...freecad-property-linklisthidden-failure.py | 185 +++ ...reecad-property-linklisthidden-mutation.py | 103 ++ ...eecad-property-linklisthidden-roundtrip.py | 108 ++ ...freecad-property-linklisthidden-success.py | 114 ++ .../freecad-property-linksubhidden-failure.py | 219 ++++ .../freecad-property-linksubhidden-success.py | 85 ++ ...reecad-property-acceleration-promotion.mjs | 2 +- ...nerate-freecad-property-area-promotion.mjs | 2 +- ...te-freecad-property-boollist-promotion.mjs | 2 +- ...erate-freecad-property-color-promotion.mjs | 2 +- ...e-freecad-property-colorlist-promotion.mjs | 2 +- ...e-freecad-property-direction-promotion.mjs | 2 +- ...nerate-freecad-property-file-promotion.mjs | 2 +- ...reecad-property-fileincluded-promotion.mjs | 2 +- ...nerate-freecad-property-font-promotion.mjs | 2 +- ...erate-freecad-property-force-promotion.mjs | 2 +- ...te-freecad-property-heatflux-promotion.mjs | 2 +- ...-freecad-property-integerset-promotion.mjs | 2 +- ...ecad-property-linklisthidden-inventory.mjs | 96 ++ ...ecad-property-linklisthidden-promotion.mjs | 66 + ...eecad-property-linksubhidden-inventory.mjs | 47 + .../run-chrome-property-linklisthidden.mjs | 51 + .../run-freecad-native-property-semantics.mjs | 2 +- ...reecad-property-linklisthidden-failure.mjs | 27 + ...eecad-property-linklisthidden-mutation.mjs | 27 + ...eecad-property-linklisthidden-roundtrip.ts | 108 ++ ...reecad-property-linklisthidden-success.mjs | 27 + ...freecad-property-linksubhidden-failure.mjs | 15 + ...freecad-property-linksubhidden-success.mjs | 16 + src/App.tsx | 2 +- src/chromePropertyLinkListHiddenHarness.tsx | 173 +++ src/facade/fcstd.ts | 62 +- src/facade/index.ts | 4 +- src/facade/mockFacade.ts | 7 +- src/facade/types.ts | 5 +- tests/propertyLinkListHiddenFacade.test.ts | 71 ++ .../propertyLinkListHiddenTransaction.test.ts | 79 ++ 后续工作.txt | 129 ++ 92 files changed, 7827 insertions(+), 151 deletions(-) create mode 100644 .gitattributes create mode 100644 chrome-property-linklisthidden-harness.html create mode 100644 config/chrome-property-linklisthidden-verification.json create mode 100644 config/freecad-property-linklisthidden-failure.json create mode 100644 config/freecad-property-linklisthidden-inventory.json create mode 100644 config/freecad-property-linklisthidden-mutation.json create mode 100644 config/freecad-property-linklisthidden-promotion.json create mode 100644 config/freecad-property-linklisthidden-roundtrip.json create mode 100644 config/freecad-property-linklisthidden-success.json create mode 100644 config/freecad-property-linksubhidden-failure.json create mode 100644 config/freecad-property-linksubhidden-inventory.json create mode 100644 config/freecad-property-linksubhidden-success.json create mode 100644 scripts/check-chrome-property-linklisthidden.mjs create mode 100644 scripts/check-freecad-property-linklisthidden-failure.mjs create mode 100644 scripts/check-freecad-property-linklisthidden-inventory.mjs create mode 100644 scripts/check-freecad-property-linklisthidden-mutation.mjs create mode 100644 scripts/check-freecad-property-linklisthidden-promotion.mjs create mode 100644 scripts/check-freecad-property-linklisthidden-roundtrip.mjs create mode 100644 scripts/check-freecad-property-linklisthidden-success.mjs create mode 100644 scripts/check-freecad-property-linksubhidden-failure.mjs create mode 100644 scripts/check-freecad-property-linksubhidden-inventory.mjs create mode 100644 scripts/check-freecad-property-linksubhidden-success.mjs create mode 100644 scripts/freecad-property-linklisthidden-failure.py create mode 100644 scripts/freecad-property-linklisthidden-mutation.py create mode 100644 scripts/freecad-property-linklisthidden-roundtrip.py create mode 100644 scripts/freecad-property-linklisthidden-success.py create mode 100644 scripts/freecad-property-linksubhidden-failure.py create mode 100644 scripts/freecad-property-linksubhidden-success.py create mode 100644 scripts/generate-freecad-property-linklisthidden-inventory.mjs create mode 100644 scripts/generate-freecad-property-linklisthidden-promotion.mjs create mode 100644 scripts/generate-freecad-property-linksubhidden-inventory.mjs create mode 100644 scripts/run-chrome-property-linklisthidden.mjs create mode 100644 scripts/run-freecad-property-linklisthidden-failure.mjs create mode 100644 scripts/run-freecad-property-linklisthidden-mutation.mjs create mode 100644 scripts/run-freecad-property-linklisthidden-roundtrip.ts create mode 100644 scripts/run-freecad-property-linklisthidden-success.mjs create mode 100644 scripts/run-freecad-property-linksubhidden-failure.mjs create mode 100644 scripts/run-freecad-property-linksubhidden-success.mjs create mode 100644 src/chromePropertyLinkListHiddenHarness.tsx create mode 100644 tests/propertyLinkListHiddenFacade.test.ts create mode 100644 tests/propertyLinkListHiddenTransaction.test.ts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..29e83e1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +working_Web_FreeCAD_Bitbybit.zip filter=lfs diff=lfs merge=lfs -text diff --git a/chrome-property-linklisthidden-harness.html b/chrome-property-linklisthidden-harness.html new file mode 100644 index 0000000..ee78222 --- /dev/null +++ b/chrome-property-linklisthidden-harness.html @@ -0,0 +1,5 @@ + + + Chrome PropertyLinkListHidden harness +
Initializing PropertyLinkListHidden harness.
+ diff --git a/config/chrome-property-linklisthidden-verification.json b/config/chrome-property-linklisthidden-verification.json new file mode 100644 index 0000000..13692c6 --- /dev/null +++ b/config/chrome-property-linklisthidden-verification.json @@ -0,0 +1,85 @@ +{ + "schemaVersion": 1, + "status": "pass", + "browserId": "chrome", + "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/150.0.0.0 Safari/537.36", + "crossOriginIsolated": true, + "ui": { + "visibleLabelInput": true, + "hiddenControlCountBefore": 0, + "hiddenControlCountAfter": 0, + "hiddenTextPresentBefore": false, + "hiddenTextPresentAfter": false, + "hiddenStatus": true, + "beforeValue": [ + "ExportA", + "ExportB" + ], + "afterValue": [ + "ExportB", + "ExportA", + "ExportB" + ], + "documentVersion": 2, + "dirty": true, + "objectState": "touched", + "dependencyCount": 0, + "errors": [] + }, + "persistence": { + "mode": "sqlite-opfs", + "sqliteWasm": true, + "opfs": true, + "savedMode": "sqlite-opfs", + "loadedValue": [ + "ExportB", + "ExportA", + "ExportB" + ], + "loadedType": "App::PropertyLinkListHidden", + "loadedDependencyCount": 0, + "checkpointVersion": 2, + "recoveryIntegrity": "ok", + "fcstdElement": "LinkList", + "fcstdValue": [ + "ExportB", + "ExportA", + "ExportB" + ], + "fcstdNativeStatus": 67108864 + }, + "resource": { + "byteLength": 44, + "roundTrip": true, + "released": true, + "markerRemoved": true + }, + "worker": { + "constructed": [ + { + "name": "bitbybit-persistence", + "url": "http://127.0.0.1:37495/src/facade/persistenceWorker.ts?worker_file&type=module" + } + ], + "requestTypes": [ + "initialize", + "save-document", + "load-document", + "load-checkpoint", + "recovery-report", + "put-resource", + "get-resource", + "release-resource", + "get-resource", + "dispose" + ], + "terminated": [ + "bitbybit-persistence" + ] + }, + "release": { + "shapeCount": 0, + "kernelReferenceCount": 0, + "workerTerminated": true + } +} diff --git a/config/freecad-active-work-queue.json b/config/freecad-active-work-queue.json index ff64292..6b5ab3f 100644 --- a/config/freecad-active-work-queue.json +++ b/config/freecad-active-work-queue.json @@ -24,12 +24,12 @@ ], "followUpProgress": "config/freecad-follow-up-task-progress.json" }, - "nextTask": "PROP-app-propertyintegerset-A", + "nextTask": "PROP-app-propertylinksubhidden-D", "summary": { "tasks": 1756, - "completed": 586, + "completed": 607, "inProgress": 1, - "pending": 1169 + "pending": 1148 }, "milestones": [ { @@ -8037,7 +8037,7 @@ { "id": "PROP-app-propertyintegerset-A", "title": "Lock native inventory and preconditions: App::PropertyIntegerSet", - "status": "in_progress", + "status": "completed", "dependencies": [ "PROP-app-propertyheatflux-I" ], @@ -8053,7 +8053,7 @@ { "id": "PROP-app-propertyintegerset-B", "title": "Capture native success evidence: App::PropertyIntegerSet", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertyintegerset-A" ], @@ -8069,7 +8069,7 @@ { "id": "PROP-app-propertyintegerset-C", "title": "Capture native failure and cancel evidence: App::PropertyIntegerSet", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertyintegerset-B" ], @@ -8085,7 +8085,7 @@ { "id": "PROP-app-propertyintegerset-D", "title": "Capture parameter mutation and restore evidence: App::PropertyIntegerSet", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertyintegerset-C" ], @@ -8101,7 +8101,7 @@ { "id": "PROP-app-propertyintegerset-E", "title": "Implement the Facade capability: App::PropertyIntegerSet", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertyintegerset-D" ], @@ -8117,7 +8117,7 @@ { "id": "PROP-app-propertyintegerset-F", "title": "Close transaction and recovery behavior: App::PropertyIntegerSet", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertyintegerset-E" ], @@ -8133,7 +8133,7 @@ { "id": "PROP-app-propertyintegerset-G", "title": "Close FCStd save, reopen and resave: App::PropertyIntegerSet", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertyintegerset-F" ], @@ -8149,7 +8149,7 @@ { "id": "PROP-app-propertyintegerset-H", "title": "Replay in a real browser: App::PropertyIntegerSet", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertyintegerset-G" ], @@ -8165,7 +8165,7 @@ { "id": "PROP-app-propertyintegerset-I", "title": "Promote capability and synchronize blockers: App::PropertyIntegerSet", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertyintegerset-H" ], @@ -8182,7 +8182,7 @@ { "id": "PROP-app-propertylinklisthidden-A", "title": "Lock native inventory and preconditions: App::PropertyLinkListHidden", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertyintegerset-I" ], @@ -8198,7 +8198,7 @@ { "id": "PROP-app-propertylinklisthidden-B", "title": "Capture native success evidence: App::PropertyLinkListHidden", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertylinklisthidden-A" ], @@ -8214,7 +8214,7 @@ { "id": "PROP-app-propertylinklisthidden-C", "title": "Capture native failure and cancel evidence: App::PropertyLinkListHidden", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertylinklisthidden-B" ], @@ -8230,7 +8230,7 @@ { "id": "PROP-app-propertylinklisthidden-D", "title": "Capture parameter mutation and restore evidence: App::PropertyLinkListHidden", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertylinklisthidden-C" ], @@ -8246,7 +8246,7 @@ { "id": "PROP-app-propertylinklisthidden-E", "title": "Implement the Facade capability: App::PropertyLinkListHidden", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertylinklisthidden-D" ], @@ -8262,7 +8262,7 @@ { "id": "PROP-app-propertylinklisthidden-F", "title": "Close transaction and recovery behavior: App::PropertyLinkListHidden", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertylinklisthidden-E" ], @@ -8278,7 +8278,7 @@ { "id": "PROP-app-propertylinklisthidden-G", "title": "Close FCStd save, reopen and resave: App::PropertyLinkListHidden", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertylinklisthidden-F" ], @@ -8294,7 +8294,7 @@ { "id": "PROP-app-propertylinklisthidden-H", "title": "Replay in a real browser: App::PropertyLinkListHidden", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertylinklisthidden-G" ], @@ -8310,7 +8310,7 @@ { "id": "PROP-app-propertylinklisthidden-I", "title": "Promote capability and synchronize blockers: App::PropertyLinkListHidden", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertylinklisthidden-H" ], @@ -8327,7 +8327,7 @@ { "id": "PROP-app-propertylinksubhidden-A", "title": "Lock native inventory and preconditions: App::PropertyLinkSubHidden", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertylinklisthidden-I" ], @@ -8343,7 +8343,7 @@ { "id": "PROP-app-propertylinksubhidden-B", "title": "Capture native success evidence: App::PropertyLinkSubHidden", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertylinksubhidden-A" ], @@ -8359,7 +8359,7 @@ { "id": "PROP-app-propertylinksubhidden-C", "title": "Capture native failure and cancel evidence: App::PropertyLinkSubHidden", - "status": "pending", + "status": "completed", "dependencies": [ "PROP-app-propertylinksubhidden-B" ], @@ -8375,7 +8375,7 @@ { "id": "PROP-app-propertylinksubhidden-D", "title": "Capture parameter mutation and restore evidence: App::PropertyLinkSubHidden", - "status": "pending", + "status": "in_progress", "dependencies": [ "PROP-app-propertylinksubhidden-C" ], diff --git a/config/freecad-follow-up-task-progress.json b/config/freecad-follow-up-task-progress.json index 35cacfc..de31392 100644 --- a/config/freecad-follow-up-task-progress.json +++ b/config/freecad-follow-up-task-progress.json @@ -777,6 +777,58 @@ { "id": "PROP-app-propertyintegerset-H", "evidence": ["config/chrome-property-integerset-verification.json", "check:chrome-property-integerset"] + }, + { + "id": "PROP-app-propertyintegerset-I", + "evidence": ["config/freecad-property-integerset-promotion.json", "check:freecad-property-integerset-promotion"] + }, + { + "id": "PROP-app-propertylinklisthidden-A", + "evidence": ["config/freecad-property-linklisthidden-inventory.json", "check:freecad-property-linklisthidden-inventory"] + }, + { + "id": "PROP-app-propertylinklisthidden-B", + "evidence": ["config/freecad-property-linklisthidden-success.json", "check:freecad-property-linklisthidden-success"] + }, + { + "id": "PROP-app-propertylinklisthidden-C", + "evidence": ["config/freecad-property-linklisthidden-failure.json", "check:freecad-property-linklisthidden-failure"] + }, + { + "id": "PROP-app-propertylinklisthidden-D", + "evidence": ["config/freecad-property-linklisthidden-mutation.json", "check:freecad-property-linklisthidden-mutation"] + }, + { + "id": "PROP-app-propertylinklisthidden-E", + "evidence": ["src/facade/types.ts", "src/facade/fcstd.ts", "src/facade/mockFacade.ts", "src/App.tsx", "tests/propertyLinkListHiddenFacade.test.ts", "test:freecad-property-linklisthidden-facade"] + }, + { + "id": "PROP-app-propertylinklisthidden-F", + "evidence": ["tests/propertyLinkListHiddenTransaction.test.ts", "test:freecad-property-linklisthidden-transaction"] + }, + { + "id": "PROP-app-propertylinklisthidden-G", + "evidence": ["config/freecad-property-linklisthidden-roundtrip.json", "check:freecad-property-linklisthidden-roundtrip"] + }, + { + "id": "PROP-app-propertylinklisthidden-H", + "evidence": ["config/chrome-property-linklisthidden-verification.json", "check:chrome-property-linklisthidden"] + }, + { + "id": "PROP-app-propertylinklisthidden-I", + "evidence": ["config/freecad-property-linklisthidden-promotion.json", "check:freecad-property-linklisthidden-promotion"] + }, + { + "id": "PROP-app-propertylinksubhidden-A", + "evidence": ["config/freecad-property-linksubhidden-inventory.json", "check:freecad-property-linksubhidden-inventory"] + }, + { + "id": "PROP-app-propertylinksubhidden-B", + "evidence": ["config/freecad-property-linksubhidden-success.json", "check:freecad-property-linksubhidden-success"] + }, + { + "id": "PROP-app-propertylinksubhidden-C", + "evidence": ["config/freecad-property-linksubhidden-failure.json", "check:freecad-property-linksubhidden-failure"] } ] } diff --git a/config/freecad-native-property-semantics.json b/config/freecad-native-property-semantics.json index e15dd73..f76e7d2 100644 --- a/config/freecad-native-property-semantics.json +++ b/config/freecad-native-property-semantics.json @@ -37,16 +37,16 @@ }, "supportSummary": { "native-editable-codec": { - "typeCount": 34, - "recordCount": 4370 + "typeCount": 35, + "recordCount": 4372 }, "native-specialized-codec": { "typeCount": 5, "recordCount": 683 }, "opaque-fcstd-proxy": { - "typeCount": 46, - "recordCount": 457 + "typeCount": 45, + "recordCount": 455 } }, "types": [ @@ -327,7 +327,7 @@ }, { "typeId": "App::PropertyLinkListHidden", - "support": "opaque-fcstd-proxy", + "support": "native-editable-codec", "recordCount": 2, "objectTypeCount": 2, "statusNames": [ @@ -902,10 +902,10 @@ }, "harness": { "path": "scripts/run-freecad-native-property-semantics.mjs", - "bytes": 6710, - "sha256": "7362b5e31dfcfba144c78f525cf5e1c94ab687e2a57dd34118d4428e79867804" + "bytes": 6741, + "sha256": "7f97a99a107ac51ddbd96d1b7034e5ad365e04fbd8ad6e49bc6db08b50cb3025" }, "exactPromotionReady": false, - "exactBlocker": "46 runtime property types and 457 records remain opaque-only; complete native document and property semantics", - "generatedAt": "2026-08-17T13:03:27.855Z" + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics", + "generatedAt": "2026-08-17T20:51:42.339Z" } diff --git a/config/freecad-property-acceleration-promotion.json b/config/freecad-property-acceleration-promotion.json index bcc82d6..2e51a1b 100644 --- a/config/freecad-property-acceleration-promotion.json +++ b/config/freecad-property-acceleration-promotion.json @@ -54,13 +54,13 @@ "zeroUnknownDrift": true }, "exactBlockerSync": { - "nativeEditableTypes": 34, - "nativeEditableRecords": 4370, - "opaqueTypes": 46, - "opaqueRecords": 457, + "nativeEditableTypes": 35, + "nativeEditableRecords": 4372, + "opaqueTypes": 45, + "opaqueRecords": 455, "exactPromotionReady": false, - "exactBlocker": "46 runtime property types and 457 records remain opaque-only; complete native document and property semantics" + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics" }, "systemExact": false, - "generatedAt": "2026-08-17T13:03:42.988Z" + "generatedAt": "2026-08-17T20:51:52.484Z" } diff --git a/config/freecad-property-area-promotion.json b/config/freecad-property-area-promotion.json index 7b8f095..f5decff 100644 --- a/config/freecad-property-area-promotion.json +++ b/config/freecad-property-area-promotion.json @@ -59,13 +59,13 @@ "zeroUnknownDrift": true }, "exactBlockerSync": { - "nativeEditableTypes": 34, - "nativeEditableRecords": 4370, - "opaqueTypes": 46, - "opaqueRecords": 457, + "nativeEditableTypes": 35, + "nativeEditableRecords": 4372, + "opaqueTypes": 45, + "opaqueRecords": 455, "exactPromotionReady": false, - "exactBlocker": "46 runtime property types and 457 records remain opaque-only; complete native document and property semantics" + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics" }, "systemExact": false, - "generatedAt": "2026-08-17T13:03:43.029Z" + "generatedAt": "2026-08-17T20:51:52.476Z" } diff --git a/config/freecad-property-boollist-promotion.json b/config/freecad-property-boollist-promotion.json index 7ac6764..eb43ef7 100644 --- a/config/freecad-property-boollist-promotion.json +++ b/config/freecad-property-boollist-promotion.json @@ -68,13 +68,13 @@ "zeroUnknownDrift": true }, "exactBlockerSync": { - "nativeEditableTypes": 34, - "nativeEditableRecords": 4370, - "opaqueTypes": 46, - "opaqueRecords": 457, + "nativeEditableTypes": 35, + "nativeEditableRecords": 4372, + "opaqueTypes": 45, + "opaqueRecords": 455, "exactPromotionReady": false, - "exactBlocker": "46 runtime property types and 457 records remain opaque-only; complete native document and property semantics" + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics" }, "systemExact": false, - "generatedAt": "2026-08-17T13:03:43.058Z" + "generatedAt": "2026-08-17T20:51:52.509Z" } diff --git a/config/freecad-property-color-promotion.json b/config/freecad-property-color-promotion.json index 14c2b35..c6fb397 100644 --- a/config/freecad-property-color-promotion.json +++ b/config/freecad-property-color-promotion.json @@ -68,13 +68,13 @@ "zeroUnknownDrift": true }, "exactBlockerSync": { - "nativeEditableTypes": 34, - "nativeEditableRecords": 4370, - "opaqueTypes": 46, - "opaqueRecords": 457, + "nativeEditableTypes": 35, + "nativeEditableRecords": 4372, + "opaqueTypes": 45, + "opaqueRecords": 455, "exactPromotionReady": false, - "exactBlocker": "46 runtime property types and 457 records remain opaque-only; complete native document and property semantics" + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics" }, "systemExact": false, - "generatedAt": "2026-08-17T13:03:43.033Z" + "generatedAt": "2026-08-17T20:51:52.509Z" } diff --git a/config/freecad-property-colorlist-promotion.json b/config/freecad-property-colorlist-promotion.json index 5b9b80d..2ee1a78 100644 --- a/config/freecad-property-colorlist-promotion.json +++ b/config/freecad-property-colorlist-promotion.json @@ -85,13 +85,13 @@ "zeroUnknownDrift": true }, "exactBlockerSync": { - "nativeEditableTypes": 34, - "nativeEditableRecords": 4370, - "opaqueTypes": 46, - "opaqueRecords": 457, + "nativeEditableTypes": 35, + "nativeEditableRecords": 4372, + "opaqueTypes": 45, + "opaqueRecords": 455, "exactPromotionReady": false, - "exactBlocker": "46 runtime property types and 457 records remain opaque-only; complete native document and property semantics" + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics" }, "systemExact": false, - "generatedAt": "2026-08-17T13:03:42.988Z" + "generatedAt": "2026-08-17T20:51:52.481Z" } diff --git a/config/freecad-property-direction-promotion.json b/config/freecad-property-direction-promotion.json index 589a65f..bc8558d 100644 --- a/config/freecad-property-direction-promotion.json +++ b/config/freecad-property-direction-promotion.json @@ -65,13 +65,13 @@ "zeroUnknownDrift": true }, "exactBlockerSync": { - "nativeEditableTypes": 34, - "nativeEditableRecords": 4370, - "opaqueTypes": 46, - "opaqueRecords": 457, + "nativeEditableTypes": 35, + "nativeEditableRecords": 4372, + "opaqueTypes": 45, + "opaqueRecords": 455, "exactPromotionReady": false, - "exactBlocker": "46 runtime property types and 457 records remain opaque-only; complete native document and property semantics" + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics" }, "systemExact": false, - "generatedAt": "2026-08-17T13:03:43.033Z" + "generatedAt": "2026-08-17T20:51:52.546Z" } diff --git a/config/freecad-property-file-promotion.json b/config/freecad-property-file-promotion.json index b081e5b..f1f58b1 100644 --- a/config/freecad-property-file-promotion.json +++ b/config/freecad-property-file-promotion.json @@ -59,13 +59,13 @@ "zeroUnknownDrift": true }, "exactBlockerSync": { - "nativeEditableTypes": 34, - "nativeEditableRecords": 4370, - "opaqueTypes": 46, - "opaqueRecords": 457, + "nativeEditableTypes": 35, + "nativeEditableRecords": 4372, + "opaqueTypes": 45, + "opaqueRecords": 455, "exactPromotionReady": false, - "exactBlocker": "46 runtime property types and 457 records remain opaque-only; complete native document and property semantics" + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics" }, "systemExact": false, - "generatedAt": "2026-08-17T13:03:43.095Z" + "generatedAt": "2026-08-17T20:51:52.501Z" } diff --git a/config/freecad-property-fileincluded-promotion.json b/config/freecad-property-fileincluded-promotion.json index 485e6d2..1d08c44 100644 --- a/config/freecad-property-fileincluded-promotion.json +++ b/config/freecad-property-fileincluded-promotion.json @@ -64,13 +64,13 @@ "zeroUnknownDrift": true }, "exactBlockerSync": { - "nativeEditableTypes": 34, - "nativeEditableRecords": 4370, - "opaqueTypes": 46, - "opaqueRecords": 457, + "nativeEditableTypes": 35, + "nativeEditableRecords": 4372, + "opaqueTypes": 45, + "opaqueRecords": 455, "exactPromotionReady": false, - "exactBlocker": "46 runtime property types and 457 records remain opaque-only; complete native document and property semantics" + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics" }, "systemExact": false, - "generatedAt": "2026-08-17T13:03:43.103Z" + "generatedAt": "2026-08-17T20:51:52.483Z" } diff --git a/config/freecad-property-font-promotion.json b/config/freecad-property-font-promotion.json index 42e8b18..db0d872 100644 --- a/config/freecad-property-font-promotion.json +++ b/config/freecad-property-font-promotion.json @@ -60,13 +60,13 @@ "zeroUnknownDrift": true }, "exactBlockerSync": { - "nativeEditableTypes": 34, - "nativeEditableRecords": 4370, - "opaqueTypes": 46, - "opaqueRecords": 457, + "nativeEditableTypes": 35, + "nativeEditableRecords": 4372, + "opaqueTypes": 45, + "opaqueRecords": 455, "exactPromotionReady": false, - "exactBlocker": "46 runtime property types and 457 records remain opaque-only; complete native document and property semantics" + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics" }, "systemExact": false, - "generatedAt": "2026-08-17T13:03:43.084Z" + "generatedAt": "2026-08-17T20:51:52.528Z" } diff --git a/config/freecad-property-force-promotion.json b/config/freecad-property-force-promotion.json index 14d2bf9..3980ed5 100644 --- a/config/freecad-property-force-promotion.json +++ b/config/freecad-property-force-promotion.json @@ -67,13 +67,13 @@ "zeroUnknownDrift": true }, "exactBlockerSync": { - "nativeEditableTypes": 34, - "nativeEditableRecords": 4370, - "opaqueTypes": 46, - "opaqueRecords": 457, + "nativeEditableTypes": 35, + "nativeEditableRecords": 4372, + "opaqueTypes": 45, + "opaqueRecords": 455, "exactPromotionReady": false, - "exactBlocker": "46 runtime property types and 457 records remain opaque-only; complete native document and property semantics" + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics" }, "systemExact": false, - "generatedAt": "2026-08-17T13:03:43.093Z" + "generatedAt": "2026-08-17T20:51:52.542Z" } diff --git a/config/freecad-property-heatflux-promotion.json b/config/freecad-property-heatflux-promotion.json index ace9636..3acd2bc 100644 --- a/config/freecad-property-heatflux-promotion.json +++ b/config/freecad-property-heatflux-promotion.json @@ -66,13 +66,13 @@ "zeroUnknownDrift": true }, "exactBlockerSync": { - "nativeEditableTypes": 34, - "nativeEditableRecords": 4370, - "opaqueTypes": 46, - "opaqueRecords": 457, + "nativeEditableTypes": 35, + "nativeEditableRecords": 4372, + "opaqueTypes": 45, + "opaqueRecords": 455, "exactPromotionReady": false, - "exactBlocker": "46 runtime property types and 457 records remain opaque-only; complete native document and property semantics" + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics" }, "systemExact": false, - "generatedAt": "2026-08-17T13:03:43.078Z" + "generatedAt": "2026-08-17T20:51:52.566Z" } diff --git a/config/freecad-property-integerset-promotion.json b/config/freecad-property-integerset-promotion.json index 393dc5c..3390905 100644 --- a/config/freecad-property-integerset-promotion.json +++ b/config/freecad-property-integerset-promotion.json @@ -68,13 +68,13 @@ "zeroUnknownDrift": true }, "exactBlockerSync": { - "nativeEditableTypes": 34, - "nativeEditableRecords": 4370, - "opaqueTypes": 46, - "opaqueRecords": 457, + "nativeEditableTypes": 35, + "nativeEditableRecords": 4372, + "opaqueTypes": 45, + "opaqueRecords": 455, "exactPromotionReady": false, - "exactBlocker": "46 runtime property types and 457 records remain opaque-only; complete native document and property semantics" + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics" }, "systemExact": false, - "generatedAt": "2026-08-17T13:03:43.129Z" + "generatedAt": "2026-08-17T20:51:52.579Z" } diff --git a/config/freecad-property-linklisthidden-failure.json b/config/freecad-property-linklisthidden-failure.json new file mode 100644 index 0000000..1135ce7 --- /dev/null +++ b/config/freecad-property-linklisthidden-failure.json @@ -0,0 +1,928 @@ +{ + "baselineId": "freecad-1.1.1-property-linklisthidden-failure", + "cancellationBoundary": { + "classification": "synchronous-property-setter", + "reason": "no-native-cancel-hook", + "replacement": "abort-active-document-transaction", + "supported": false + }, + "documentIntegrity": { + "finalObjects": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "foreignDocumentObjects": [ + { + "name": "ForeignExport", + "typeId": "Part::Feature" + } + ], + "initialObjects": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "objectsPreserved": true + }, + "editorReadOnly": { + "after": { + "editorMode": [ + "ReadOnly", + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "ReadOnly", + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Touched", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportB" + ] + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "classification": "editor-read-only-python-setter-accepted", + "editorMode": [ + "ReadOnly", + "Hidden" + ], + "exception": null, + "pythonBypassesEditorReadOnly": true, + "restoredEditorMode": [ + "Hidden" + ] + }, + "failures": [ + { + "after": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "documentPreserved": true, + "exception": { + "message": "Type must be App.DocumentObject or None, not str", + "type": "TypeError" + }, + "id": "string-scalar", + "requested": "'ExportA'", + "valuePreserved": true + }, + { + "after": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "documentPreserved": true, + "exception": { + "message": "Type must be App.DocumentObject or None, not str", + "type": "TypeError" + }, + "id": "string-item", + "requested": "['ExportA']", + "valuePreserved": true + }, + { + "after": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "documentPreserved": true, + "exception": { + "message": "Type must be App.DocumentObject or None, not int", + "type": "TypeError" + }, + "id": "integer-scalar", + "requested": "7", + "valuePreserved": true + }, + { + "after": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "documentPreserved": true, + "exception": { + "message": "Type must be App.DocumentObject or None, not int", + "type": "TypeError" + }, + "id": "integer-item", + "requested": "[7]", + "valuePreserved": true + }, + { + "after": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "documentPreserved": true, + "exception": { + "message": "PropertyLinkList does not support external object", + "type": "ValueError" + }, + "id": "cross-document", + "requested": "[]", + "valuePreserved": true + } + ], + "freecadVersion": "1.1.1", + "gitCommit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d", + "immutable": { + "after": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "Immutable", + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Touched", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportB" + ] + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "Immutable", + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "classification": "immutable-python-setter-accepted", + "exception": null, + "pythonBypassesImmutable": true, + "restoredStatus": [ + "26" + ] + }, + "initial": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "object": { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + "property": { + "name": "Exports", + "typeId": "App::PropertyLinkListHidden" + }, + "propertyType": "App::PropertyLinkListHidden", + "schemaVersion": 1, + "status": "pass", + "transaction": { + "activeAfterAbort": { + "id": 0, + "name": "" + }, + "activeAfterEdit": { + "id": 1, + "name": "property-linklisthidden-cancel" + }, + "afterAbort": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "afterAbortBeforeRecompute": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Touched", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "edited": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShapeNull": true, + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Touched", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapeHashes": { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": [ + "ExportB", + "ExportA", + "ExportB" + ] + }, + "pendingAfterAbort": false, + "pendingAfterEdit": true, + "recomputeAfterAbort": true, + "restored": true, + "undoMode": 1 + } +} diff --git a/config/freecad-property-linklisthidden-inventory.json b/config/freecad-property-linklisthidden-inventory.json new file mode 100644 index 0000000..a0f1d9e --- /dev/null +++ b/config/freecad-property-linklisthidden-inventory.json @@ -0,0 +1,148 @@ +{ + "schemaVersion": 1, + "status": "pass", + "baseline": { + "freecadVersion": "1.1.1", + "commit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" + }, + "propertyType": "App::PropertyLinkListHidden", + "recordCount": 2, + "objectTypeCount": 2, + "writableRecordCount": 2, + "records": [ + { + "objectTypeId": "Sketcher::SketchObject", + "objectAvailable": true, + "probeStatus": "available", + "propertyName": "Exports", + "group": "Sketch", + "statusRaw": [ + 26 + ], + "statusNames": [ + "PropHidden" + ], + "defaultRaw": [], + "valueModel": { + "kind": "hidden-document-object-list", + "inheritedFrom": "App::PropertyLinkList", + "elementType": "same-document App::DocumentObject reference", + "orderPreserved": true, + "duplicatesPreserved": true, + "writable": true + }, + "inputs": { + "nativeCppSetValues": "attached same-document DocumentObject list", + "emptyListClears": true, + "singleNullListClears": true, + "detachedObjectRejected": true, + "crossDocumentRejectedWithoutLinkAllowExternal": true + }, + "dependencies": { + "linkScope": "Hidden", + "createsBacklinks": false, + "includedByDefaultGetLinks": false, + "includedByGetLinksAll": true, + "defaultDagDependency": false + }, + "applicability": { + "requiredObjectTypeId": "Sketcher::SketchObject", + "role": "Sketch exported child geometry lookup by object name", + "propertyWriteRequiresShape": false, + "source": "Document.supportedTypes runtime inventory and SketchObject::getSubObject" + } + }, + { + "objectTypeId": "Sketcher::SketchObjectPython", + "objectAvailable": true, + "probeStatus": "available", + "propertyName": "Exports", + "group": "Sketch", + "statusRaw": [ + 26 + ], + "statusNames": [ + "PropHidden" + ], + "defaultRaw": [], + "valueModel": { + "kind": "hidden-document-object-list", + "inheritedFrom": "App::PropertyLinkList", + "elementType": "same-document App::DocumentObject reference", + "orderPreserved": true, + "duplicatesPreserved": true, + "writable": true + }, + "inputs": { + "nativeCppSetValues": "attached same-document DocumentObject list", + "emptyListClears": true, + "singleNullListClears": true, + "detachedObjectRejected": true, + "crossDocumentRejectedWithoutLinkAllowExternal": true + }, + "dependencies": { + "linkScope": "Hidden", + "createsBacklinks": false, + "includedByDefaultGetLinks": false, + "includedByGetLinksAll": true, + "defaultDagDependency": false + }, + "applicability": { + "requiredObjectTypeId": "Sketcher::SketchObjectPython", + "role": "Sketch exported child geometry lookup by object name", + "propertyWriteRequiresShape": false, + "source": "Document.supportedTypes runtime inventory and SketchObject::getSubObject" + } + } + ], + "typeContract": { + "inheritedFrom": "App::PropertyLinkList", + "linkScope": "Hidden", + "propertyStatus": "PropHidden", + "propertyStatusPurpose": "hide the Exports property from the ordinary editor surface", + "linkScopePurpose": "exclude Exports from ordinary backlink and dependency traversal", + "xmlElement": "LinkList", + "xmlChildElement": "Link", + "legacyRestoreType": "App::PropertyLinkList" + }, + "provenance": { + "runtime": { + "path": ".cache/freecad/reference-desktop.json", + "bytes": 2298709, + "sha256": "082400ec7379e8efc2af16709032d45890d7092277967b714acb05761fd1c2a7" + }, + "sources": [ + { + "path": ".cache/freecad/FreeCAD/src/App/PropertyLinks.h", + "bytes": 58046, + "sha256": "03176b14948d43fcae3ff290711a913ae4fa43f3ba286d66ceb610e074555d1c" + }, + { + "path": ".cache/freecad/FreeCAD/src/App/PropertyLinks.cpp", + "bytes": 202779, + "sha256": "b4221dd89d2b0b668dc8a3c2852ba53bc67bcb721830e3a6011ea53c1a32edb0" + }, + { + "path": ".cache/freecad/FreeCAD/src/Mod/Sketcher/App/SketchObject.h", + "bytes": 52959, + "sha256": "a21018ddcfc2bda32f15eeadc5c63015677b87cd441e829beae575323fd0fea0" + }, + { + "path": ".cache/freecad/FreeCAD/src/Mod/Sketcher/App/SketchObject.cpp", + "bytes": 467084, + "sha256": "1762c9bb9d7fc54ff171be638094c5f4a50b8d2bcefe3af5cff6010d2478ef9a" + } + ] + }, + "classification": "opaque-fcstd-proxy", + "nextPhases": [ + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I" + ] +} diff --git a/config/freecad-property-linklisthidden-mutation.json b/config/freecad-property-linklisthidden-mutation.json new file mode 100644 index 0000000..ad8de46 --- /dev/null +++ b/config/freecad-property-linklisthidden-mutation.json @@ -0,0 +1,663 @@ +{ + "baselineId": "freecad-1.1.1-property-linklisthidden-mutation", + "caseCount": 2, + "cases": [ + { + "before": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShape": { + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "editRecomputeResult": true, + "edited": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShape": { + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportB", + "ExportA", + "ExportB" + ] + }, + "objectName": "SketchProbe", + "objectTypeId": "Sketcher::SketchObject", + "propertyName": "Exports", + "restoreRecomputeResult": true, + "restored": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShape": { + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "semanticStateRestored": true, + "touchedAfterEdit": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShape": { + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Touched", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportB", + "ExportA", + "ExportB" + ] + }, + "touchedAfterRestore": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShape": { + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Touched", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "valueRestored": true + }, + { + "before": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchPythonProbe", + "typeId": "Sketcher::SketchObjectPython" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShape": { + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "editRecomputeResult": true, + "edited": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchPythonProbe", + "typeId": "Sketcher::SketchObjectPython" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShape": { + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportB", + "ExportA", + "ExportB" + ] + }, + "objectName": "SketchPythonProbe", + "objectTypeId": "Sketcher::SketchObjectPython", + "propertyName": "Exports", + "restoreRecomputeResult": true, + "restored": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchPythonProbe", + "typeId": "Sketcher::SketchObjectPython" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShape": { + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "semanticStateRestored": true, + "touchedAfterEdit": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchPythonProbe", + "typeId": "Sketcher::SketchObjectPython" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShape": { + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Touched", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportB", + "ExportA", + "ExportB" + ] + }, + "touchedAfterRestore": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchPythonProbe", + "typeId": "Sketcher::SketchObjectPython" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerShape": { + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "statusString": "Touched", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportA", + "ExportB" + ] + }, + "valueRestored": true + } + ], + "freecadVersion": "1.1.1", + "gitCommit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d", + "propertyType": "App::PropertyLinkListHidden", + "schemaVersion": 1, + "status": "pass" +} diff --git a/config/freecad-property-linklisthidden-promotion.json b/config/freecad-property-linklisthidden-promotion.json new file mode 100644 index 0000000..2a7ea8d --- /dev/null +++ b/config/freecad-property-linklisthidden-promotion.json @@ -0,0 +1,84 @@ +{ + "schemaVersion": 1, + "status": "pass", + "taskId": "PROP-app-propertylinklisthidden-I", + "baseline": { + "freecadVersion": "1.1.1", + "commit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" + }, + "propertyType": "App::PropertyLinkListHidden", + "recordCount": 2, + "phaseEvidence": { + "A": [ + "config/freecad-property-linklisthidden-inventory.json", + "check:freecad-property-linklisthidden-inventory" + ], + "B": [ + "config/freecad-property-linklisthidden-success.json", + "check:freecad-property-linklisthidden-success" + ], + "C": [ + "config/freecad-property-linklisthidden-failure.json", + "check:freecad-property-linklisthidden-failure" + ], + "D": [ + "config/freecad-property-linklisthidden-mutation.json", + "check:freecad-property-linklisthidden-mutation" + ], + "E": [ + "src/facade/types.ts", + "src/facade/fcstd.ts", + "src/facade/mockFacade.ts", + "src/App.tsx", + "tests/propertyLinkListHiddenFacade.test.ts", + "test:freecad-property-linklisthidden-facade" + ], + "F": [ + "tests/propertyLinkListHiddenTransaction.test.ts", + "test:freecad-property-linklisthidden-transaction" + ], + "G": [ + "config/freecad-property-linklisthidden-roundtrip.json", + "check:freecad-property-linklisthidden-roundtrip" + ], + "H": [ + "config/chrome-property-linklisthidden-verification.json", + "check:chrome-property-linklisthidden" + ] + }, + "promotion": { + "from": "opaque-fcstd-proxy", + "to": "native-editable-codec", + "facadeValueModel": "ordered-duplicate-preserving-same-document-object-name-array", + "writableRecords": 2, + "objectTypeCount": 2, + "propertyStatus": "PropHidden", + "linkScope": "Hidden", + "runtimeDependencyEdges": 0, + "fcstdElement": "LinkList", + "nativeRoundTripValue": [ + "ExportB", + "ExportA", + "ExportB" + ], + "browserRoundTripValue": [ + "ExportB", + "ExportA", + "ExportB" + ], + "hiddenUiFiltered": true, + "hiddenLinksPreserved": true, + "nativeDependencyMetadataNormalized": true, + "zeroUnknownDrift": true + }, + "exactBlockerSync": { + "nativeEditableTypes": 35, + "nativeEditableRecords": 4372, + "opaqueTypes": 45, + "opaqueRecords": 455, + "exactPromotionReady": false, + "exactBlocker": "45 runtime property types and 455 records remain opaque-only; complete native document and property semantics" + }, + "systemExact": false, + "generatedAt": "2026-08-17T20:51:52.575Z" +} diff --git a/config/freecad-property-linklisthidden-roundtrip.json b/config/freecad-property-linklisthidden-roundtrip.json new file mode 100644 index 0000000..0ecc9db --- /dev/null +++ b/config/freecad-property-linklisthidden-roundtrip.json @@ -0,0 +1,375 @@ +{ + "schemaVersion": 1, + "status": "pass", + "baselineId": "freecad-1.1.1-property-linklisthidden-roundtrip", + "freecadVersion": "1.1.1", + "gitCommit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d", + "propertyType": "App::PropertyLinkListHidden", + "archives": { + "nativeInitial": { + "path": ".cache/freecad/property-linklisthidden-roundtrip/native-initial.FCStd", + "bytes": 3839, + "sha256": "3f9a950c0acadae61581844b35436f0ff0a340a9c1dc358e6659b90092737936" + }, + "webEdited": { + "path": ".cache/freecad/property-linklisthidden-roundtrip/web-edited.FCStd", + "bytes": 3712, + "sha256": "63bbffbee9b7d7e4c9356697251da61cf300f721c8be12027917ab0466a256cc" + }, + "nativeResaved": { + "path": ".cache/freecad/property-linklisthidden-roundtrip/native-resaved.FCStd", + "bytes": 3851, + "sha256": "02329f1ba06f8eaba4e9ec38f632f1cbcbaa342cfe37fca14851f18b74909e41" + } + }, + "nativeInitial": { + "object": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "name": "SketchProbe", + "outList": [], + "propertyName": "Exports", + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "shape": { + "area": 0, + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "shapeType": "Null", + "solids": 0, + "valid": false, + "vertices": 0, + "volume": 0 + }, + "state": [ + "Up-to-date" + ], + "statusString": "Valid", + "typeId": "Sketcher::SketchObject", + "value": [ + "ExportA", + "ExportB" + ] + }, + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "targets": { + "ExportA": { + "inList": [], + "shape": { + "area": 52, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "shapeType": "Solid", + "solids": 1, + "valid": true, + "vertices": 8, + "volume": 24 + }, + "typeId": "Part::Feature" + }, + "ExportB": { + "inList": [], + "shape": { + "area": 61.261056745, + "brepSha256": "82bf07000178ed1f34895ff3e2510f838cd26a6247e2e395fff73feb2117960c", + "edges": 3, + "faces": 3, + "isNull": false, + "shapeType": "Solid", + "solids": 1, + "valid": true, + "vertices": 2, + "volume": 35.342917353 + }, + "typeId": "Part::Feature" + } + } + }, + "web": { + "before": { + "name": "Exports", + "typeId": "App::PropertyLinkListHidden", + "element": "LinkList", + "value": "", + "nativeStatus": 67108864, + "statusNames": [ + "PropHidden" + ], + "links": [ + "ExportA", + "ExportB" + ] + }, + "after": { + "name": "Exports", + "typeId": "App::PropertyLinkListHidden", + "element": "LinkList", + "value": "", + "nativeStatus": 67108864, + "statusNames": [ + "PropHidden" + ], + "links": [ + "ExportB", + "ExportA", + "ExportB" + ] + }, + "targetMarkedTouched": true, + "initialDependencyTargets": [ + "ExportA", + "ExportB" + ], + "webDependencyTargets": [ + "ExportA", + "ExportB" + ], + "hiddenDependencyMetadataPreserved": true, + "webOutputMatches": true, + "objectSetPreserved": true, + "semanticObjectsPreserved": true, + "opaquePathsPreserved": 4, + "opaqueEntriesPreserved": true + }, + "nativeAfter": { + "reopened": { + "object": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "name": "SketchProbe", + "outList": [], + "propertyName": "Exports", + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "shape": { + "area": 0, + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "shapeType": "Null", + "solids": 0, + "valid": false, + "vertices": 0, + "volume": 0 + }, + "state": [ + "Up-to-date" + ], + "statusString": "Valid", + "typeId": "Sketcher::SketchObject", + "value": [ + "ExportB", + "ExportA", + "ExportB" + ] + }, + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "targets": { + "ExportA": { + "inList": [], + "shape": { + "area": 52, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "shapeType": "Solid", + "solids": 1, + "valid": true, + "vertices": 8, + "volume": 24 + }, + "typeId": "Part::Feature" + }, + "ExportB": { + "inList": [], + "shape": { + "area": 61.261056745, + "brepSha256": "82bf07000178ed1f34895ff3e2510f838cd26a6247e2e395fff73feb2117960c", + "edges": 3, + "faces": 3, + "isNull": false, + "shapeType": "Solid", + "solids": 1, + "valid": true, + "vertices": 2, + "volume": 35.342917353 + }, + "typeId": "Part::Feature" + } + } + }, + "resaved": { + "object": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "name": "SketchProbe", + "outList": [], + "propertyName": "Exports", + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "shape": { + "area": 0, + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "shapeType": "Null", + "solids": 0, + "valid": false, + "vertices": 0, + "volume": 0 + }, + "state": [ + "Up-to-date" + ], + "statusString": "Valid", + "typeId": "Sketcher::SketchObject", + "value": [ + "ExportB", + "ExportA", + "ExportB" + ] + }, + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "targets": { + "ExportA": { + "inList": [], + "shape": { + "area": 52, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "shapeType": "Solid", + "solids": 1, + "valid": true, + "vertices": 8, + "volume": 24 + }, + "typeId": "Part::Feature" + }, + "ExportB": { + "inList": [], + "shape": { + "area": 61.261056745, + "brepSha256": "82bf07000178ed1f34895ff3e2510f838cd26a6247e2e395fff73feb2117960c", + "edges": 3, + "faces": 3, + "isNull": false, + "shapeType": "Solid", + "solids": 1, + "valid": true, + "vertices": 2, + "volume": 35.342917353 + }, + "typeId": "Part::Feature" + } + } + } + }, + "classification": { + "requestedValue": [ + "ExportB", + "ExportA", + "ExportB" + ], + "webValue": [ + "ExportB", + "ExportA", + "ExportB" + ], + "reopenedValue": [ + "ExportB", + "ExportA", + "ExportB" + ], + "resavedValue": [ + "ExportB", + "ExportA", + "ExportB" + ], + "nativeOutputMatches": true, + "hiddenLinksPreserved": true, + "nativeResavedDependencyTargets": [ + "ExportB", + "ExportA", + "ExportB" + ], + "nativeDependencyMetadataNormalized": true, + "targetShapeStructurePreserved": true, + "ownerShapePreserved": true, + "nativeTargetBrepHashes": [ + { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "82bf07000178ed1f34895ff3e2510f838cd26a6247e2e395fff73feb2117960c" + }, + { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "82bf07000178ed1f34895ff3e2510f838cd26a6247e2e395fff73feb2117960c" + }, + { + "ExportA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ExportB": "82bf07000178ed1f34895ff3e2510f838cd26a6247e2e395fff73feb2117960c" + } + ], + "unknownSemanticDrift": false, + "zeroUnknownDrift": true + } +} diff --git a/config/freecad-property-linklisthidden-success.json b/config/freecad-property-linklisthidden-success.json new file mode 100644 index 0000000..bd11114 --- /dev/null +++ b/config/freecad-property-linklisthidden-success.json @@ -0,0 +1,691 @@ +{ + "baselineId": "freecad-1.1.1-property-linklisthidden-success", + "bindingBoundary": { + "oracleDefault": [], + "safeFirstRead": "assign-empty-list-before-read" + }, + "caseCount": 2, + "cases": [ + { + "group": "Sketch", + "mode": "direct-native-property-setter", + "objectName": "SketchProbe", + "objectTypeId": "Sketcher::SketchObject", + "phases": { + "duplicates": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "recomputeResult": true, + "shape": { + "applicable": true, + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportA", + "ExportB", + "ExportA" + ] + }, + "explicitEmpty": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "recomputeResult": true, + "shape": { + "applicable": true, + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [] + }, + "ordered": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "recomputeResult": true, + "shape": { + "applicable": true, + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportB", + "ExportA" + ] + }, + "restored": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "recomputeResult": true, + "shape": { + "applicable": true, + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [] + }, + "single": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchProbe", + "typeId": "Sketcher::SketchObject" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "recomputeResult": true, + "shape": { + "applicable": true, + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportA" + ] + } + }, + "propertyName": "Exports" + }, + { + "group": "Sketch", + "mode": "direct-native-property-setter", + "objectName": "SketchPythonProbe", + "objectTypeId": "Sketcher::SketchObjectPython", + "phases": { + "duplicates": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchPythonProbe", + "typeId": "Sketcher::SketchObjectPython" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "recomputeResult": true, + "shape": { + "applicable": true, + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportA", + "ExportB", + "ExportA" + ] + }, + "explicitEmpty": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchPythonProbe", + "typeId": "Sketcher::SketchObjectPython" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "recomputeResult": true, + "shape": { + "applicable": true, + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [] + }, + "ordered": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchPythonProbe", + "typeId": "Sketcher::SketchObjectPython" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "recomputeResult": true, + "shape": { + "applicable": true, + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportB", + "ExportA" + ] + }, + "restored": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchPythonProbe", + "typeId": "Sketcher::SketchObjectPython" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "recomputeResult": true, + "shape": { + "applicable": true, + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [] + }, + "single": { + "editorMode": [ + "Hidden" + ], + "group": "Sketch", + "objectSet": [ + { + "name": "SketchPythonProbe", + "typeId": "Sketcher::SketchObjectPython" + }, + { + "name": "ExportA", + "typeId": "Part::Feature" + }, + { + "name": "ExportB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "26" + ], + "propertyTypeId": "App::PropertyLinkListHidden", + "recomputeResult": true, + "shape": { + "applicable": true, + "brepSha256": null, + "edges": 0, + "faces": 0, + "isNull": true, + "solids": 0, + "vertices": 0 + }, + "statusString": "Valid", + "targetInLists": { + "ExportA": [], + "ExportB": [] + }, + "targetShapes": { + "ExportA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ExportB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": [ + "ExportA" + ] + } + }, + "propertyName": "Exports" + } + ], + "freecadVersion": "1.1.1", + "gitCommit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d", + "propertyType": "App::PropertyLinkListHidden", + "schemaVersion": 1, + "status": "pass" +} diff --git a/config/freecad-property-linksubhidden-failure.json b/config/freecad-property-linksubhidden-failure.json new file mode 100644 index 0000000..511e917 --- /dev/null +++ b/config/freecad-property-linksubhidden-failure.json @@ -0,0 +1,1100 @@ +{ + "baselineId": "freecad-1.1.1-property-linksubhidden-failure", + "cancellationBoundary": { + "classification": "synchronous-property-setter", + "reason": "no-native-cancel-hook", + "replacement": "abort-active-document-transaction", + "supported": false + }, + "case": { + "baseline": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + }, + "crossDocument": { + "accepted": false, + "after": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "documentPreserved": true, + "error": { + "message": "PropertyLinkSub does not support external object", + "type": "ValueError" + }, + "valuePreserved": true + }, + "documentIntegrity": { + "finalObjects": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "foreignDocumentObjects": [ + { + "name": "ExternalSource", + "typeId": "Part::Feature" + } + ], + "initialObjects": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "objectsPreserved": true + }, + "failures": [ + { + "accepted": false, + "after": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "documentPreserved": true, + "error": { + "message": "type must be 'DocumentObject', 'NoneType' or ('DocumentObject',['String',]) not str", + "type": "TypeError" + }, + "name": "scalar", + "requested": "'ColorSourceA'", + "valuePreserved": true + }, + { + "accepted": true, + "after": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Touched", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1" + ] + } + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "documentPreserved": false, + "error": null, + "name": "wrongSubList", + "requested": "(, 'Face1')", + "valuePreserved": false + }, + { + "accepted": true, + "after": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Touched", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "MissingFace" + ] + } + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "documentPreserved": false, + "error": null, + "name": "unknownTuple", + "requested": "(, ['MissingFace'])", + "valuePreserved": false + } + ], + "immutable": { + "accepted": false, + "after": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "Immutable", + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "Immutable", + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "error": { + "message": "Object attribute 'ColoredElements' is read-only", + "type": "AttributeError" + }, + "pythonBypassesImmutable": false, + "status": [ + "Immutable", + "LockDynamic", + "26" + ] + }, + "initial": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "objectName": "LinkGroupProbe", + "objectTypeId": "App::LinkGroup", + "propertyName": "ColoredElements", + "propertyTypeId": "App::PropertyLinkSubHidden", + "readOnly": { + "accepted": true, + "after": { + "editorMode": [ + "ReadOnly", + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "ReadOnly", + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Touched", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceB", + "subElements": [ + "Face2" + ] + } + }, + "before": { + "editorMode": [ + "ReadOnly", + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "ReadOnly", + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "error": null, + "pythonBypassesEditorReadOnly": true, + "status": [ + "ReadOnly", + "LockDynamic", + "26" + ] + }, + "transaction": { + "activeAfterAbort": { + "id": 0, + "name": "" + }, + "activeAfterEdit": { + "id": 1, + "name": "property-linksubhidden-cancel" + }, + "afterAbort": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "afterAbortBeforeRecompute": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Touched", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "edited": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Touched", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceB", + "subElements": [ + "Face5" + ] + } + }, + "pendingAfterAbort": false, + "pendingAfterEdit": true, + "recomputeAfterAbort": true, + "restored": true, + "undoMode": 1 + } + }, + "caseCount": 1, + "disabled": { + "classification": "editor-read-only-python-mutable-until-immutable", + "immutable": { + "accepted": false, + "after": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "Immutable", + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "before": { + "editorMode": [ + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "Immutable", + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "error": { + "message": "Object attribute 'ColoredElements' is read-only", + "type": "AttributeError" + }, + "pythonBypassesImmutable": false, + "status": [ + "Immutable", + "LockDynamic", + "26" + ] + }, + "immutableSetter": "rejected", + "readOnly": { + "accepted": true, + "after": { + "editorMode": [ + "ReadOnly", + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Touched" + ], + "propertyStatus": [ + "ReadOnly", + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Touched", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceB", + "subElements": [ + "Face2" + ] + } + }, + "before": { + "editorMode": [ + "ReadOnly", + "Hidden" + ], + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "ReadOnly", + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapeHashes": { + "ColorSourceA": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "ColorSourceB": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851" + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "error": null, + "pythonBypassesEditorReadOnly": true, + "status": [ + "ReadOnly", + "LockDynamic", + "26" + ] + }, + "readOnlySetter": "accepted" + }, + "freecadVersion": "1.1.1", + "gitCommit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d", + "propertyType": "App::PropertyLinkSubHidden", + "schemaVersion": 1, + "status": "pass" +} diff --git a/config/freecad-property-linksubhidden-inventory.json b/config/freecad-property-linksubhidden-inventory.json new file mode 100644 index 0000000..28e1fca --- /dev/null +++ b/config/freecad-property-linksubhidden-inventory.json @@ -0,0 +1,246 @@ +{ + "schemaVersion": 1, + "status": "pass", + "baseline": { + "freecadVersion": "1.1.1", + "commit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" + }, + "propertyType": "App::PropertyLinkSubHidden", + "recordCount": 4, + "objectTypeCount": 4, + "writableRecordCount": 4, + "records": [ + { + "objectTypeId": "App::Link", + "objectAvailable": true, + "probeStatus": "available", + "propertyName": "ColoredElements", + "group": " Link", + "statusRaw": [ + "LockDynamic", + 26 + ], + "statusNames": [ + "LockDynamic", + "PropHidden" + ], + "defaultRaw": null, + "valueModel": { + "kind": "hidden-document-object-sub-element-reference", + "inheritedFrom": "App::PropertyLinkSub", + "elementType": "same-document App::DocumentObject plus ordered sub-element names", + "orderPreserved": true, + "duplicatesPreserved": false, + "writable": true + }, + "inputs": { + "nativeCppSetValues": "attached same-document DocumentObject plus sub-element string list", + "emptySubListMeansWholeObject": true, + "nullObjectClears": true, + "detachedObjectRejected": true, + "crossDocumentRejectedWithoutLinkAllowExternal": true, + "invalidSubElementAcceptedAsReference": true + }, + "dependencies": { + "linkScope": "Hidden", + "createsBacklinks": false, + "includedByDefaultGetLinks": false, + "includedByGetLinksAll": true, + "defaultDagDependency": false + }, + "applicability": { + "requiredObjectTypeId": "App::Link", + "role": "Link colored element override lookup", + "propertyWriteRequiresShape": false, + "source": "Link extension and Draft colored element implementation" + } + }, + { + "objectTypeId": "App::LinkGroup", + "objectAvailable": true, + "probeStatus": "available", + "propertyName": "ColoredElements", + "group": " Link", + "statusRaw": [ + "LockDynamic", + 26 + ], + "statusNames": [ + "LockDynamic", + "PropHidden" + ], + "defaultRaw": null, + "valueModel": { + "kind": "hidden-document-object-sub-element-reference", + "inheritedFrom": "App::PropertyLinkSub", + "elementType": "same-document App::DocumentObject plus ordered sub-element names", + "orderPreserved": true, + "duplicatesPreserved": false, + "writable": true + }, + "inputs": { + "nativeCppSetValues": "attached same-document DocumentObject plus sub-element string list", + "emptySubListMeansWholeObject": true, + "nullObjectClears": true, + "detachedObjectRejected": true, + "crossDocumentRejectedWithoutLinkAllowExternal": true, + "invalidSubElementAcceptedAsReference": true + }, + "dependencies": { + "linkScope": "Hidden", + "createsBacklinks": false, + "includedByDefaultGetLinks": false, + "includedByGetLinksAll": true, + "defaultDagDependency": false + }, + "applicability": { + "requiredObjectTypeId": "App::LinkGroup", + "role": "Link colored element override lookup", + "propertyWriteRequiresShape": false, + "source": "Link extension and Draft colored element implementation" + } + }, + { + "objectTypeId": "App::LinkGroupPython", + "objectAvailable": true, + "probeStatus": "available", + "propertyName": "ColoredElements", + "group": " Link", + "statusRaw": [ + "LockDynamic", + 26 + ], + "statusNames": [ + "LockDynamic", + "PropHidden" + ], + "defaultRaw": null, + "valueModel": { + "kind": "hidden-document-object-sub-element-reference", + "inheritedFrom": "App::PropertyLinkSub", + "elementType": "same-document App::DocumentObject plus ordered sub-element names", + "orderPreserved": true, + "duplicatesPreserved": false, + "writable": true + }, + "inputs": { + "nativeCppSetValues": "attached same-document DocumentObject plus sub-element string list", + "emptySubListMeansWholeObject": true, + "nullObjectClears": true, + "detachedObjectRejected": true, + "crossDocumentRejectedWithoutLinkAllowExternal": true, + "invalidSubElementAcceptedAsReference": true + }, + "dependencies": { + "linkScope": "Hidden", + "createsBacklinks": false, + "includedByDefaultGetLinks": false, + "includedByGetLinksAll": true, + "defaultDagDependency": false + }, + "applicability": { + "requiredObjectTypeId": "App::LinkGroupPython", + "role": "Link colored element override lookup", + "propertyWriteRequiresShape": false, + "source": "Link extension and Draft colored element implementation" + } + }, + { + "objectTypeId": "App::LinkPython", + "objectAvailable": true, + "probeStatus": "available", + "propertyName": "ColoredElements", + "group": " Link", + "statusRaw": [ + "LockDynamic", + 26 + ], + "statusNames": [ + "LockDynamic", + "PropHidden" + ], + "defaultRaw": null, + "valueModel": { + "kind": "hidden-document-object-sub-element-reference", + "inheritedFrom": "App::PropertyLinkSub", + "elementType": "same-document App::DocumentObject plus ordered sub-element names", + "orderPreserved": true, + "duplicatesPreserved": false, + "writable": true + }, + "inputs": { + "nativeCppSetValues": "attached same-document DocumentObject plus sub-element string list", + "emptySubListMeansWholeObject": true, + "nullObjectClears": true, + "detachedObjectRejected": true, + "crossDocumentRejectedWithoutLinkAllowExternal": true, + "invalidSubElementAcceptedAsReference": true + }, + "dependencies": { + "linkScope": "Hidden", + "createsBacklinks": false, + "includedByDefaultGetLinks": false, + "includedByGetLinksAll": true, + "defaultDagDependency": false + }, + "applicability": { + "requiredObjectTypeId": "App::LinkPython", + "role": "Link colored element override lookup", + "propertyWriteRequiresShape": false, + "source": "Link extension and Draft colored element implementation" + } + } + ], + "typeContract": { + "inheritedFrom": "App::PropertyLinkSub", + "linkScope": "Hidden", + "propertyStatus": "PropHidden", + "dynamicStatus": "LockDynamic", + "propertyStatusPurpose": "hide the ColoredElements property from the ordinary editor surface", + "linkScopePurpose": "exclude ColoredElements from ordinary backlink and dependency traversal", + "xmlElement": "LinkSub", + "xmlChildElement": "Sub", + "legacyRestoreType": "App::PropertyLinkSub", + "emptySubListMeans": "whole-object-reference" + }, + "provenance": { + "runtime": { + "path": ".cache/freecad/reference-desktop.json", + "bytes": 2298709, + "sha256": "082400ec7379e8efc2af16709032d45890d7092277967b714acb05761fd1c2a7" + }, + "sources": [ + { + "path": ".cache/freecad/FreeCAD/src/App/PropertyLinks.h", + "bytes": 58046, + "sha256": "03176b14948d43fcae3ff290711a913ae4fa43f3ba286d66ceb610e074555d1c" + }, + { + "path": ".cache/freecad/FreeCAD/src/App/PropertyLinks.cpp", + "bytes": 202779, + "sha256": "b4221dd89d2b0b668dc8a3c2852ba53bc67bcb721830e3a6011ea53c1a32edb0" + }, + { + "path": ".cache/freecad/FreeCAD/src/App/Link.h", + "bytes": 40417, + "sha256": "57df13bd156ad7f3a1e23a74ae4040fa159bbc3d38c4a49b087d0c92260721e6" + }, + { + "path": ".cache/freecad/FreeCAD/src/Mod/Draft/draftobjects/draftlink.py", + "bytes": 11576, + "sha256": "2b781a0b18dc89a3e07dc576b508b8c331ad33bf64fc9dca53240bf943c05e57" + } + ] + }, + "classification": "opaque-fcstd-proxy", + "nextPhases": [ + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I" + ] +} diff --git a/config/freecad-property-linksubhidden-success.json b/config/freecad-property-linksubhidden-success.json new file mode 100644 index 0000000..c69356d --- /dev/null +++ b/config/freecad-property-linksubhidden-success.json @@ -0,0 +1,1108 @@ +{ + "baselineId": "freecad-1.1.1-property-linksubhidden-success", + "bindingBoundary": { + "emptySubListMeans": "whole-object-reference", + "oracleDefault": null, + "safeFirstRead": "assign-null-object-before-read" + }, + "caseCount": 4, + "cases": [ + { + "group": " Link", + "mode": "direct-native-property-setter", + "objectName": "LinkProbe", + "objectTypeId": "App::Link", + "phases": { + "duplicates": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkProbe", + "typeId": "App::Link" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [ + "ColorSourceA" + ], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [ + "LinkProbe" + ], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face6", + "Face1", + "Face6" + ] + } + }, + "explicitNull": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkProbe", + "typeId": "App::Link" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [ + "ColorSourceA" + ], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [ + "LinkProbe" + ], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": null + }, + "ordered": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkProbe", + "typeId": "App::Link" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [ + "ColorSourceA" + ], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [ + "LinkProbe" + ], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "wholeObject": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkProbe", + "typeId": "App::Link" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [ + "ColorSourceA" + ], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [ + "LinkProbe" + ], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": { + "object": "ColorSourceA", + "subElements": [] + } + } + }, + "propertyName": "ColoredElements" + }, + { + "group": " Link", + "mode": "direct-native-property-setter", + "objectName": "LinkGroupProbe", + "objectTypeId": "App::LinkGroup", + "phases": { + "duplicates": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face6", + "Face1", + "Face6" + ] + } + }, + "explicitNull": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": null + }, + "ordered": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "wholeObject": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkGroupProbe", + "typeId": "App::LinkGroup" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": { + "object": "ColorSourceA", + "subElements": [] + } + } + }, + "propertyName": "ColoredElements" + }, + { + "group": " Link", + "mode": "direct-native-property-setter", + "objectName": "LinkGroupPythonProbe", + "objectTypeId": "App::LinkGroupPython", + "phases": { + "duplicates": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkGroupPythonProbe", + "typeId": "App::LinkGroupPython" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face6", + "Face1", + "Face6" + ] + } + }, + "explicitNull": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkGroupPythonProbe", + "typeId": "App::LinkGroupPython" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": null + }, + "ordered": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkGroupPythonProbe", + "typeId": "App::LinkGroupPython" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "wholeObject": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkGroupPythonProbe", + "typeId": "App::LinkGroupPython" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": { + "object": "ColorSourceA", + "subElements": [] + } + } + }, + "propertyName": "ColoredElements" + }, + { + "group": " Link", + "mode": "direct-native-property-setter", + "objectName": "LinkPythonProbe", + "objectTypeId": "App::LinkPython", + "phases": { + "duplicates": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkPythonProbe", + "typeId": "App::LinkPython" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [ + "ColorSourceA" + ], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [ + "LinkPythonProbe" + ], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face6", + "Face1", + "Face6" + ] + } + }, + "explicitNull": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkPythonProbe", + "typeId": "App::LinkPython" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [ + "ColorSourceA" + ], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [ + "LinkPythonProbe" + ], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": null + }, + "ordered": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkPythonProbe", + "typeId": "App::LinkPython" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [ + "ColorSourceA" + ], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [ + "LinkPythonProbe" + ], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": { + "object": "ColorSourceA", + "subElements": [ + "Face1", + "Face3" + ] + } + }, + "wholeObject": { + "editorMode": [ + "Hidden" + ], + "group": " Link", + "objectSet": [ + { + "name": "LinkPythonProbe", + "typeId": "App::LinkPython" + }, + { + "name": "ColorSourceA", + "typeId": "Part::Feature" + }, + { + "name": "ColorSourceB", + "typeId": "Part::Feature" + } + ], + "ownerOutList": [ + "ColorSourceA" + ], + "ownerState": [ + "Up-to-date" + ], + "propertyStatus": [ + "LockDynamic", + "26" + ], + "propertyTypeId": "App::PropertyLinkSubHidden", + "recomputeResult": true, + "shape": { + "applicable": false, + "reason": "host-has-no-shape-property" + }, + "statusString": "Valid", + "targetInLists": { + "ColorSourceA": [ + "LinkPythonProbe" + ], + "ColorSourceB": [] + }, + "targetShapes": { + "ColorSourceA": { + "applicable": true, + "brepSha256": "8c42068f05129bb15610da52f3fef1132d8f4459ae9662549de0c14dbd6ee44b", + "edges": 12, + "faces": 6, + "isNull": false, + "solids": 1, + "vertices": 8 + }, + "ColorSourceB": { + "applicable": true, + "brepSha256": "c3786ce59c730f9b18585c41062b02591e323238119986649a96ef34eb439851", + "edges": 3, + "faces": 3, + "isNull": false, + "solids": 1, + "vertices": 2 + } + }, + "value": { + "object": "ColorSourceA", + "subElements": [] + } + } + }, + "propertyName": "ColoredElements" + } + ], + "freecadVersion": "1.1.1", + "gitCommit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d", + "propertyType": "App::PropertyLinkSubHidden", + "schemaVersion": 1, + "status": "pass" +} diff --git a/config/freecad-web-exact-parity-plan.json b/config/freecad-web-exact-parity-plan.json index b363b2b..ec03688 100644 --- a/config/freecad-web-exact-parity-plan.json +++ b/config/freecad-web-exact-parity-plan.json @@ -41,7 +41,7 @@ "id": "EX03", "title": "Application document, property and recompute semantics", "tasks": [ - { "id": "EX-DOC-01", "title": "Implement every native property type, flag, editor and serialization rule", "priority": "P0", "status": "in_progress", "dependencies": ["EX-ORA-01"], "deliverables": ["Complete Property schema", "Status and editor metadata", "Binary and XML codecs"], "acceptance": ["Every runtime property fixture round-trips without type coercion or unknown loss"], "evidence": ["check:freecad-type-properties", "check:freecad-native-property-semantics", "check:freecad-property-status", "check:freecad-fcstd-roundtrip"], "exactBlockedBy": ["Of 85 runtime property types and 5510 records, 46 types/457 records remain opaque-only; all 15 observed status classes now have typed Facade behavior, including LockDynamic dynamic mutation and PartialTrigger partial-document observer transactions, but the remaining codecs, editors and complete document observer semantics are not closed"] }, + { "id": "EX-DOC-01", "title": "Implement every native property type, flag, editor and serialization rule", "priority": "P0", "status": "in_progress", "dependencies": ["EX-ORA-01"], "deliverables": ["Complete Property schema", "Status and editor metadata", "Binary and XML codecs"], "acceptance": ["Every runtime property fixture round-trips without type coercion or unknown loss"], "evidence": ["check:freecad-type-properties", "check:freecad-native-property-semantics", "check:freecad-property-status", "check:freecad-fcstd-roundtrip"], "exactBlockedBy": ["Of 85 runtime property types and 5510 records, 45 types/455 records remain opaque-only; all 15 observed status classes now have typed Facade behavior, including LockDynamic dynamic mutation and PartialTrigger partial-document observer transactions, but the remaining codecs, editors and complete document observer semantics are not closed"] }, { "id": "EX-DOC-02", "title": "Match transactions, DAG recompute, partial failure and document observers", "priority": "P0", "status": "in_progress", "dependencies": ["EX-DOC-01", "EX-TSN-04"], "deliverables": ["Transaction and observer event oracle", "Partial recompute scheduler", "Failure recovery contract"], "acceptance": ["Undo, redo, abort, stale and cyclic dependency behavior matches FreeCAD"], "evidence": ["check:document-closure", "test:facade"], "exactBlockedBy": ["Observer ordering and every partial-recompute branch are not yet oracle-backed"] }, { "id": "EX-DOC-03", "title": "Complete expressions, links, extensions, groups and App object lifecycle", "priority": "P0", "status": "in_progress", "dependencies": ["EX-DOC-01"], "deliverables": ["Expression dependency grammar", "Link and extension lifecycle", "Group and clone semantics"], "acceptance": ["Copy, clone, delete, relink and expression edits survive native round-trip"], "evidence": ["check:fcstd-closure", "check:chrome-production-document"], "exactBlockedBy": ["FeaturePython-backed extensions remain non-executable"] }, { "id": "EX-DOC-04", "title": "Complete multi-document, external link, merge and recovery workflows", "priority": "P1", "status": "pending", "dependencies": ["EX-DOC-02", "EX-DOC-03"], "deliverables": ["Cross-document Link corpus", "Merge and conflict semantics", "Crash and autosave recovery"], "acceptance": ["Open, close, relink, merge and recovery match the native document graph"], "evidence": ["check:chrome-persistence", "check:project-migration"], "exactBlockedBy": ["Current application is project-first and does not implement every desktop multi-document workflow"] }, @@ -63,7 +63,7 @@ "id": "EX05", "title": "FCStd and all import/export entrypoints", "tasks": [ - { "id": "EX-FC-01", "title": "Round-trip every native FCStd object, property, extension and resource", "priority": "P0", "status": "in_progress", "dependencies": ["EX-DOC-04", "EX-TSN-04"], "deliverables": ["Complete native archive model", "Unknown object preservation", "FreeCAD-Web-FreeCAD mutation corpus"], "acceptance": ["No supported object, property, GUI state or resource drifts after mutation and resave"], "evidence": ["check:freecad-native-property-semantics", "check:freecad-isomorphic-provenance", "check:fcstd-closure", "check:chrome-fcstd-roundtrip"], "exactBlockedBy": ["FCStd supports 35 native property codecs plus opaque preservation for 50 runtime types; exhaustive native mutation and resave remains open"] }, + { "id": "EX-FC-01", "title": "Round-trip every native FCStd object, property, extension and resource", "priority": "P0", "status": "in_progress", "dependencies": ["EX-DOC-04", "EX-TSN-04"], "deliverables": ["Complete native archive model", "Unknown object preservation", "FreeCAD-Web-FreeCAD mutation corpus"], "acceptance": ["No supported object, property, GUI state or resource drifts after mutation and resave"], "evidence": ["check:freecad-native-property-semantics", "check:freecad-isomorphic-provenance", "check:fcstd-closure", "check:chrome-fcstd-roundtrip"], "exactBlockedBy": ["FCStd supports 36 native property codecs plus opaque preservation for 49 runtime types; exhaustive native mutation and resave remains open"] }, { "id": "EX-FMT-01", "title": "Close BREP, STEP and IGES import/export semantics", "priority": "P0", "status": "in_progress", "dependencies": ["EX-KER-01"], "deliverables": ["Assembly/color/name import", "Unit and tolerance handling", "Deterministic exports"], "acceptance": ["Native and Web topology, metadata and diagnostics match for each format"] , "evidence": ["check:chrome-secondary-formats", "check:browser-occt"], "exactBlockedBy": ["Advanced STEP/IGES metadata and assembly cases are incomplete"] }, { "id": "EX-FMT-02", "title": "Close STL, OBJ, PLY, DXF, SVG and PDF round-trips", "priority": "P1", "status": "in_progress", "dependencies": ["EX-MESH-01", "EX-DRAFT-01", "EX-TD-01"], "deliverables": ["Mesh and 2D format codecs", "Style/layer metadata", "Import-export failure corpus"], "acceptance": ["All supported entities and metadata round-trip or fail with a registered difference"] , "evidence": ["check:chrome-secondary-formats", "check:document-closure"], "exactBlockedBy": ["Several formats remain read-only or omit advanced entity metadata"] }, { "id": "EX-FMT-03", "title": "Close IFC, CSV and G-code native object semantics", "priority": "P1", "status": "in_progress", "dependencies": ["EX-BIM-01", "EX-SS-01", "EX-CAM-01"], "deliverables": ["IFC schema and property sets", "Spreadsheet CSV dialect parity", "Path/G-code object round-trip"], "acceptance": ["Import, edit, export and native reopen preserve the declared object graph"] , "evidence": ["check:chrome-bim", "check:document-closure", "check:cam-parity"], "exactBlockedBy": ["IFC and G-code entrypoint inventory still records unsupported desktop semantics"] }, diff --git a/docs/freecad-active-work-status.generated.zh-CN.md b/docs/freecad-active-work-status.generated.zh-CN.md index d788b90..f164daa 100644 --- a/docs/freecad-active-work-status.generated.zh-CN.md +++ b/docs/freecad-active-work-status.generated.zh-CN.md @@ -3,10 +3,10 @@ 生成日期:2026-08-15。基线:FreeCAD 1.1.1 / `0108fd4b4850cc46e625b60e53cea7a7bbe69f8d`。 -- 唯一活动任务:`PROP-app-propertyintegerset-A` -- 微任务:1756 total / 586 completed / 1 in_progress / 1169 pending +- 唯一活动任务:`PROP-app-propertylinksubhidden-D` +- 微任务:1756 total / 607 completed / 1 in_progress / 1148 pending - 有序操作对:361/361 classified / 0 unknown -- Property:38/85 native types / 47 opaque types +- Property:40/85 native types / 45 opaque types - exact 任务:5/52 completed - 模块能力:0 exact / 26 compatible / 5 proxy / 3 development @@ -16,7 +16,7 @@ | `ORA-GUI-WORKFLOWS` | 7 | 7 | 0 | 0 | `-` | | `TSN-RECOVERY-DRIFT` | 13 | 13 | 0 | 0 | `-` | | `TSN-ORDERED-PAIRS` | 361 | 361 | 0 | 0 | `-` | -| `PROPERTY-CODECS` | 522 | 99 | 1 | 422 | `PROP-app-propertyintegerset-A` | +| `PROPERTY-CODECS` | 522 | 120 | 1 | 401 | `PROP-app-propertylinksubhidden-D` | | `DOCUMENT-SEMANTICS` | 90 | 0 | 0 | 90 | `-` | | `CORE-MODELING` | 171 | 0 | 0 | 171 | `-` | | `FCSTD-AND-FORMATS` | 72 | 0 | 0 | 72 | `-` | diff --git a/package.json b/package.json index ca0272b..0affe9a 100644 --- a/package.json +++ b/package.json @@ -188,6 +188,28 @@ "check:chrome-property-integerset": "node scripts/check-chrome-property-integerset.mjs", "generate:freecad-property-integerset-promotion": "node scripts/generate-freecad-property-integerset-promotion.mjs", "check:freecad-property-integerset-promotion": "node scripts/check-freecad-property-integerset-promotion.mjs", + "generate:freecad-property-linklisthidden-inventory": "node scripts/generate-freecad-property-linklisthidden-inventory.mjs", + "check:freecad-property-linklisthidden-inventory": "node scripts/check-freecad-property-linklisthidden-inventory.mjs", + "probe:freecad-property-linklisthidden-success": "node scripts/run-freecad-property-linklisthidden-success.mjs", + "check:freecad-property-linklisthidden-success": "node scripts/check-freecad-property-linklisthidden-success.mjs", + "probe:freecad-property-linklisthidden-failure": "node scripts/run-freecad-property-linklisthidden-failure.mjs", + "check:freecad-property-linklisthidden-failure": "node scripts/check-freecad-property-linklisthidden-failure.mjs", + "probe:freecad-property-linklisthidden-mutation": "node scripts/run-freecad-property-linklisthidden-mutation.mjs", + "check:freecad-property-linklisthidden-mutation": "node scripts/check-freecad-property-linklisthidden-mutation.mjs", + "test:freecad-property-linklisthidden-facade": "tsx --test tests/propertyLinkListHiddenFacade.test.ts", + "test:freecad-property-linklisthidden-transaction": "tsx --test tests/propertyLinkListHiddenTransaction.test.ts", + "probe:freecad-property-linklisthidden-roundtrip": "tsx scripts/run-freecad-property-linklisthidden-roundtrip.ts", + "check:freecad-property-linklisthidden-roundtrip": "node scripts/check-freecad-property-linklisthidden-roundtrip.mjs", + "test:chrome-property-linklisthidden": "node scripts/run-chrome-property-linklisthidden.mjs", + "check:chrome-property-linklisthidden": "node scripts/check-chrome-property-linklisthidden.mjs", + "generate:freecad-property-linklisthidden-promotion": "node scripts/generate-freecad-property-linklisthidden-promotion.mjs", + "check:freecad-property-linklisthidden-promotion": "node scripts/check-freecad-property-linklisthidden-promotion.mjs", + "generate:freecad-property-linksubhidden-inventory": "node scripts/generate-freecad-property-linksubhidden-inventory.mjs", + "check:freecad-property-linksubhidden-inventory": "node scripts/check-freecad-property-linksubhidden-inventory.mjs", + "probe:freecad-property-linksubhidden-success": "node scripts/run-freecad-property-linksubhidden-success.mjs", + "check:freecad-property-linksubhidden-success": "node scripts/check-freecad-property-linksubhidden-success.mjs", + "probe:freecad-property-linksubhidden-failure": "node scripts/run-freecad-property-linksubhidden-failure.mjs", + "check:freecad-property-linksubhidden-failure": "node scripts/check-freecad-property-linksubhidden-failure.mjs", "probe:freecad-property-file-success": "node scripts/run-freecad-property-file-success.mjs", "check:freecad-property-file-success": "node scripts/check-freecad-property-file-success.mjs", "probe:freecad-property-file-failure": "node scripts/run-freecad-property-file-failure.mjs", diff --git a/scripts/check-chrome-property-linklisthidden.mjs b/scripts/check-chrome-property-linklisthidden.mjs new file mode 100644 index 0000000..6360c2c --- /dev/null +++ b/scripts/check-chrome-property-linklisthidden.mjs @@ -0,0 +1,16 @@ +import { isDeepStrictEqual } from 'node:util' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const report = JSON.parse(await readFile(resolve(root, 'config/chrome-property-linklisthidden-verification.json'), 'utf8')) +const fail = (message) => { throw new Error(`Chrome PropertyLinkListHidden check failed: ${message}`) } +if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome\//.test(report.userAgent || '') || report.crossOriginIsolated !== true) fail('browser boundary is invalid') +const requiredWorkerRequests = ['initialize', 'save-document', 'load-document', 'load-checkpoint', 'recovery-report', 'put-resource', 'get-resource', 'release-resource', 'dispose'] +if (!report.worker?.constructed?.some(({ name }) => name === 'bitbybit-persistence') || !requiredWorkerRequests.every((type) => report.worker.requestTypes?.includes(type)) || report.worker.terminated?.includes('bitbybit-persistence') !== true) fail('persistence Worker lifecycle is incomplete') +const initial = ['ExportA', 'ExportB'] +const target = ['ExportB', 'ExportA', 'ExportB'] +if (report.ui?.visibleLabelInput !== true || report.ui.hiddenControlCountBefore !== 0 || report.ui.hiddenControlCountAfter !== 0 || report.ui.hiddenTextPresentBefore !== false || report.ui.hiddenTextPresentAfter !== false || report.ui.hiddenStatus !== true || !isDeepStrictEqual(report.ui.beforeValue, initial) || !isDeepStrictEqual(report.ui.afterValue, target) || report.ui.documentVersion !== 2 || report.ui.dirty !== true || report.ui.objectState !== 'touched' || report.ui.dependencyCount !== 0 || report.ui.errors?.length !== 0) fail('production hidden-property UI state is incomplete') +if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.sqliteWasm !== true || report.persistence.opfs !== true || report.persistence.savedMode !== 'sqlite-opfs' || !isDeepStrictEqual(report.persistence.loadedValue, target) || report.persistence.loadedType !== 'App::PropertyLinkListHidden' || report.persistence.loadedDependencyCount !== 0 || report.persistence.checkpointVersion !== 2 || report.persistence.recoveryIntegrity !== 'ok' || report.persistence.fcstdElement !== 'LinkList' || !isDeepStrictEqual(report.persistence.fcstdValue, target) || report.persistence.fcstdNativeStatus !== 67108864) fail('OPFS/FCStd persistence evidence is incomplete') +if (!(report.resource?.byteLength > 0) || report.resource.roundTrip !== true || report.resource.released !== true || report.resource.markerRemoved !== true || report.release?.shapeCount !== 0 || report.release.kernelReferenceCount !== 0 || report.release.workerTerminated !== true) fail('resource release evidence is incomplete') +console.log(JSON.stringify({ status: 'chrome-property-linklisthidden-pass', ui: report.ui, persistence: report.persistence, workerRequests: report.worker.requestTypes, resource: report.resource, release: report.release }, null, 2)) diff --git a/scripts/check-facade-boundary.mjs b/scripts/check-facade-boundary.mjs index bf075dc..906df87 100644 --- a/scripts/check-facade-boundary.mjs +++ b/scripts/check-facade-boundary.mjs @@ -23,6 +23,7 @@ const allowWorkerMessaging = new Set([ 'chromePropertyForceHarness.tsx', 'chromePropertyHeatFluxHarness.tsx', 'chromePropertyIntegerSetHarness.tsx', + 'chromePropertyLinkListHiddenHarness.tsx', 'facade/camPipeline.ts', ]) diff --git a/scripts/check-freecad-native-property-semantics.mjs b/scripts/check-freecad-native-property-semantics.mjs index 044f70d..e32c509 100644 --- a/scripts/check-freecad-native-property-semantics.mjs +++ b/scripts/check-freecad-native-property-semantics.mjs @@ -5,10 +5,10 @@ import { resolve } from 'node:path' const root = resolve(new URL('..', import.meta.url).pathname) const report = JSON.parse(await readFile(resolve(root, 'config/freecad-native-property-semantics.json'), 'utf8')) const fail = (message) => { throw new Error(`FreeCAD native property semantics check failed: ${message}`) } -if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baseline?.freecadVersion !== '1.1.1' || report.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.exactPromotionReady !== false || report.exactBlocker !== '46 runtime property types and 457 records remain opaque-only; complete native document and property semantics') fail('report boundary is invalid') +if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baseline?.freecadVersion !== '1.1.1' || report.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.exactPromotionReady !== false || report.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('report boundary is invalid') if (report.runtime?.registeredObjectTypes !== 352 || report.runtime.instantiableObjectTypes !== 348 || report.runtime.unavailableObjectTypes !== 4 || report.runtime.propertyRecords !== 5510 || report.runtime.propertyTypes !== 85 || report.runtime.unavailableObjects?.length !== 4) fail('runtime inventory counts changed') const support = report.supportSummary -if (support?.['native-editable-codec']?.typeCount !== 34 || support['native-editable-codec'].recordCount !== 4370 || support?.['native-specialized-codec']?.typeCount !== 5 || support['native-specialized-codec'].recordCount !== 683 || support?.['opaque-fcstd-proxy']?.typeCount !== 46 || support['opaque-fcstd-proxy'].recordCount !== 457) fail('property support partition is stale') +if (support?.['native-editable-codec']?.typeCount !== 35 || support['native-editable-codec'].recordCount !== 4372 || support?.['native-specialized-codec']?.typeCount !== 5 || support['native-specialized-codec'].recordCount !== 683 || support?.['opaque-fcstd-proxy']?.typeCount !== 45 || support['opaque-fcstd-proxy'].recordCount !== 455) fail('property support partition is stale') if (report.types?.length !== 85 || new Set(report.types.map(({ typeId }) => typeId)).size !== 85 || report.types.reduce((total, entry) => total + entry.recordCount, 0) !== 5510) fail('per-TypeId inventory is invalid') const acceleration = report.types.find(({ typeId }) => typeId === 'App::PropertyAcceleration') if (acceleration?.support !== 'native-editable-codec' || acceleration.recordCount !== 1 || acceleration.objectTypeCount !== 1) fail('App::PropertyAcceleration promotion is missing') @@ -34,6 +34,8 @@ const heatFlux = report.types.find(({ typeId }) => typeId === 'App::PropertyHeat if (heatFlux?.support !== 'native-editable-codec' || heatFlux.recordCount !== 1 || heatFlux.objectTypeCount !== 1 || heatFlux.statusNames?.length !== 0) fail('App::PropertyHeatFlux promotion is missing') const integerSet = report.types.find(({ typeId }) => typeId === 'App::PropertyIntegerSet') if (integerSet?.support !== 'native-editable-codec' || integerSet.recordCount !== 2 || integerSet.objectTypeCount !== 2 || integerSet.statusNames?.length !== 0) fail('App::PropertyIntegerSet promotion is missing') +const linkListHidden = report.types.find(({ typeId }) => typeId === 'App::PropertyLinkListHidden') +if (linkListHidden?.support !== 'native-editable-codec' || linkListHidden.recordCount !== 2 || linkListHidden.objectTypeCount !== 2 || linkListHidden.statusNames?.join(',') !== 'PropHidden') fail('App::PropertyLinkListHidden promotion is missing') if (report.propertyStatus?.unknownNumericBits?.length !== 0 || report.propertyStatus.proxyOnlyRecordCount !== 0 || report.propertyStatus.proxyOnly?.length !== 0 || report.propertyStatus.facadeNative?.join(',') !== report.propertyStatus.observed?.join(',')) fail('property status representation coverage is stale') if (report.propertyStatus.behaviorNative?.join(',') !== 'Hidden,Immutable,LockDynamic,NoModify,Ordered,Output,PartialTrigger,PropHidden,PropNoPersist,PropNoRecompute,PropOutput,PropReadOnly,PropTransient,ReadOnly,Transient' || report.propertyStatus.preservedOnly?.length !== 0 || report.propertyStatus.preservedOnlyRecordCount !== 0) fail('property status behavior boundary is stale') for (const locked of [report.source, report.harness]) { @@ -41,4 +43,4 @@ for (const locked of [report.source, report.harness]) { const [content, bytes] = await Promise.all([readFile(path), stat(path).then(({ size }) => size)]) if (bytes !== locked.bytes || createHash('sha256').update(content).digest('hex') !== locked.sha256) fail(`report is stale for ${locked.path}`) } -console.log(JSON.stringify({ status: 'freecad-native-property-semantics-pass', propertyTypes: 85, propertyRecords: 5510, nativeCodecRecords: 5053, opaqueProxyRecords: 457, promotedTypes: [acceleration.typeId, area.typeId, boolList.typeId, color.typeId, colorList.typeId, direction.typeId, file.typeId, fileIncluded.typeId, font.typeId, force.typeId, heatFlux.typeId, integerSet.typeId], representedStatusRecords: 5510, preservedOnlyStatusRecords: 0, exactPromotionReady: false, exactBlocker: report.exactBlocker }, null, 2)) +console.log(JSON.stringify({ status: 'freecad-native-property-semantics-pass', propertyTypes: 85, propertyRecords: 5510, nativeCodecRecords: 5055, opaqueProxyRecords: 455, promotedTypes: [acceleration.typeId, area.typeId, boolList.typeId, color.typeId, colorList.typeId, direction.typeId, file.typeId, fileIncluded.typeId, font.typeId, force.typeId, heatFlux.typeId, integerSet.typeId, linkListHidden.typeId], representedStatusRecords: 5510, preservedOnlyStatusRecords: 0, exactPromotionReady: false, exactBlocker: report.exactBlocker }, null, 2)) diff --git a/scripts/check-freecad-property-acceleration-promotion.mjs b/scripts/check-freecad-property-acceleration-promotion.mjs index eb86b0c..ce22760 100644 --- a/scripts/check-freecad-property-acceleration-promotion.mjs +++ b/scripts/check-freecad-property-acceleration-promotion.mjs @@ -21,7 +21,7 @@ for (const phase of requiredPhases) { const propertyType = semantics.types?.find(({ typeId }) => typeId === report.propertyType) if (report.promotion?.from !== 'opaque-fcstd-proxy' || report.promotion.to !== 'native-editable-codec' || propertyType?.support !== report.promotion.to || report.promotion.facadeValueModel !== 'finite-number-mm/s^2' || report.promotion.fcstdElement !== 'Float' || report.promotion.nativeRoundTripValue !== 500 || report.promotion.browserRoundTripValue !== 500 || report.promotion.zeroUnknownDrift !== true) fail('capability promotion is incomplete') const sync = report.exactBlockerSync -if (sync?.nativeEditableTypes !== 34 || sync.nativeEditableRecords !== 4370 || sync.opaqueTypes !== 46 || sync.opaqueRecords !== 457 || sync.exactPromotionReady !== false || sync.exactBlocker !== '46 runtime property types and 457 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') +if (sync?.nativeEditableTypes !== 35 || sync.nativeEditableRecords !== 4372 || sync.opaqueTypes !== 45 || sync.opaqueRecords !== 455 || sync.exactPromotionReady !== false || sync.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') if (JSON.stringify(sync) !== JSON.stringify({ nativeEditableTypes: semantics.supportSummary['native-editable-codec'].typeCount, nativeEditableRecords: semantics.supportSummary['native-editable-codec'].recordCount, opaqueTypes: semantics.supportSummary['opaque-fcstd-proxy'].typeCount, opaqueRecords: semantics.supportSummary['opaque-fcstd-proxy'].recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker })) fail('promotion report diverges from global property semantics') if (roundTrip.classification?.zeroUnknownDrift !== true || chrome.status !== 'pass' || chrome.persistence?.fcstdElement !== 'Float' || chrome.persistence.loadedValue !== 500 || chrome.resource?.released !== true || chrome.release?.workerTerminated !== true) fail('G/H closure evidence regressed') console.log(JSON.stringify({ status: 'freecad-property-acceleration-promotion-pass', propertyType: report.propertyType, promotion: report.promotion, completedPhases: requiredPhases, exactBlockerSync: sync, systemExact: report.systemExact }, null, 2)) diff --git a/scripts/check-freecad-property-area-promotion.mjs b/scripts/check-freecad-property-area-promotion.mjs index abe3e6a..70498b2 100644 --- a/scripts/check-freecad-property-area-promotion.mjs +++ b/scripts/check-freecad-property-area-promotion.mjs @@ -21,7 +21,7 @@ for (const phase of requiredPhases) { const propertyType = semantics.types?.find(({ typeId }) => typeId === report.propertyType) if (report.promotion?.from !== 'opaque-fcstd-proxy' || report.promotion.to !== 'native-editable-codec' || propertyType?.support !== report.promotion.to || propertyType.statusNames?.join(',') !== 'PropOutput,PropReadOnly' || report.promotion.facadeValueModel !== 'read-only-derived-mm^2-with-link-sub-list-input' || report.promotion.inputType !== 'App::PropertyLinkSubList' || report.promotion.fcstdAreaElement !== 'Float' || report.promotion.fcstdElementsElement !== 'LinkSubList' || report.promotion.nativeRoundTripArea !== 20 || report.promotion.browserRoundTripArea !== 0 || report.promotion.browserRoundTripElementCount !== 0 || report.promotion.sourceGeometryPreserved !== true || report.promotion.zeroUnknownDrift !== true) fail('capability promotion is incomplete') const sync = report.exactBlockerSync -if (sync?.nativeEditableTypes !== 34 || sync.nativeEditableRecords !== 4370 || sync.opaqueTypes !== 46 || sync.opaqueRecords !== 457 || sync.exactPromotionReady !== false || sync.exactBlocker !== '46 runtime property types and 457 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') +if (sync?.nativeEditableTypes !== 35 || sync.nativeEditableRecords !== 4372 || sync.opaqueTypes !== 45 || sync.opaqueRecords !== 455 || sync.exactPromotionReady !== false || sync.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') if (JSON.stringify(sync) !== JSON.stringify({ nativeEditableTypes: semantics.supportSummary['native-editable-codec'].typeCount, nativeEditableRecords: semantics.supportSummary['native-editable-codec'].recordCount, opaqueTypes: semantics.supportSummary['opaque-fcstd-proxy'].typeCount, opaqueRecords: semantics.supportSummary['opaque-fcstd-proxy'].recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker })) fail('promotion report diverges from global property semantics') if (roundTrip.classification?.zeroUnknownDrift !== true || roundTrip.classification.sourceGeometryPreserved !== true || chrome.status !== 'pass' || chrome.ui?.areaReadOnly !== true || chrome.persistence?.fcstdAreaElement !== 'Float' || chrome.persistence.fcstdElementsElement !== 'LinkSubList' || chrome.persistence.loadedArea !== 0 || chrome.persistence.loadedElementCount !== 0 || chrome.resource?.released !== true || chrome.release?.workerTerminated !== true) fail('G/H closure evidence regressed') console.log(JSON.stringify({ status: 'freecad-property-area-promotion-pass', propertyType: report.propertyType, promotion: report.promotion, completedPhases: requiredPhases, exactBlockerSync: sync, systemExact: report.systemExact }, null, 2)) diff --git a/scripts/check-freecad-property-boollist-promotion.mjs b/scripts/check-freecad-property-boollist-promotion.mjs index b21ca46..9cf7a29 100644 --- a/scripts/check-freecad-property-boollist-promotion.mjs +++ b/scripts/check-freecad-property-boollist-promotion.mjs @@ -23,7 +23,7 @@ const target = [true, false, true, false] const propertyType = semantics.types?.find(({ typeId }) => typeId === report.propertyType) if (report.promotion?.from !== 'opaque-fcstd-proxy' || report.promotion.to !== 'native-editable-codec' || propertyType?.support !== report.promotion.to || propertyType.statusNames?.join(',') !== 'Hidden,Immutable,LockDynamic' || report.promotion.facadeValueModel !== 'variable-boolean-array' || report.promotion.writableRecords !== 3 || report.promotion.immutableHiddenRecords !== 4 || report.promotion.fcstdElement !== 'BoolList' || !isDeepStrictEqual(report.promotion.nativeRoundTripValue, target) || !isDeepStrictEqual(report.promotion.browserRoundTripValue, target) || report.promotion.zeroUnknownDrift !== true) fail('capability promotion is incomplete') const sync = report.exactBlockerSync -if (sync?.nativeEditableTypes !== 34 || sync.nativeEditableRecords !== 4370 || sync.opaqueTypes !== 46 || sync.opaqueRecords !== 457 || sync.exactPromotionReady !== false || sync.exactBlocker !== '46 runtime property types and 457 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') +if (sync?.nativeEditableTypes !== 35 || sync.nativeEditableRecords !== 4372 || sync.opaqueTypes !== 45 || sync.opaqueRecords !== 455 || sync.exactPromotionReady !== false || sync.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') if (!isDeepStrictEqual(sync, { nativeEditableTypes: semantics.supportSummary['native-editable-codec'].typeCount, nativeEditableRecords: semantics.supportSummary['native-editable-codec'].recordCount, opaqueTypes: semantics.supportSummary['opaque-fcstd-proxy'].typeCount, opaqueRecords: semantics.supportSummary['opaque-fcstd-proxy'].recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker })) fail('promotion report diverges from global property semantics') if (roundTrip.classification?.zeroUnknownDrift !== true || !isDeepStrictEqual(roundTrip.classification.resavedValue, target) || chrome.status !== 'pass' || chrome.persistence?.fcstdElement !== 'BoolList' || !isDeepStrictEqual(chrome.persistence.loadedValue, target) || chrome.resource?.released !== true || chrome.release?.workerTerminated !== true) fail('G/H closure evidence regressed') console.log(JSON.stringify({ status: 'freecad-property-boollist-promotion-pass', propertyType: report.propertyType, promotion: report.promotion, completedPhases: requiredPhases, exactBlockerSync: sync, systemExact: report.systemExact }, null, 2)) diff --git a/scripts/check-freecad-property-color-promotion.mjs b/scripts/check-freecad-property-color-promotion.mjs index a72fbcf..9a2c5c0 100644 --- a/scripts/check-freecad-property-color-promotion.mjs +++ b/scripts/check-freecad-property-color-promotion.mjs @@ -24,7 +24,7 @@ const approximatelyTarget = (value) => Array.isArray(value) && value.length === const propertyType = semantics.types?.find(({ typeId }) => typeId === report.propertyType) if (report.promotion?.from !== 'opaque-fcstd-proxy' || report.promotion.to !== 'native-editable-codec' || propertyType?.support !== report.promotion.to || propertyType.statusNames?.length !== 0 || report.promotion.facadeValueModel !== 'four-finite-rgba-channels-with-8-bit-fcstd-packing' || report.promotion.writableRecords !== 8 || report.promotion.fcstdElement !== 'PropertyColor' || report.promotion.fcstdPackedValue !== '862362060' || !approximatelyTarget(report.promotion.nativeRoundTripValue) || !isDeepStrictEqual(report.promotion.browserRoundTripValue, target) || report.promotion.zeroUnknownDrift !== true) fail('capability promotion is incomplete') const sync = report.exactBlockerSync -if (sync?.nativeEditableTypes !== 34 || sync.nativeEditableRecords !== 4370 || sync.opaqueTypes !== 46 || sync.opaqueRecords !== 457 || sync.exactPromotionReady !== false || sync.exactBlocker !== '46 runtime property types and 457 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') +if (sync?.nativeEditableTypes !== 35 || sync.nativeEditableRecords !== 4372 || sync.opaqueTypes !== 45 || sync.opaqueRecords !== 455 || sync.exactPromotionReady !== false || sync.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') if (!isDeepStrictEqual(sync, { nativeEditableTypes: semantics.supportSummary['native-editable-codec'].typeCount, nativeEditableRecords: semantics.supportSummary['native-editable-codec'].recordCount, opaqueTypes: semantics.supportSummary['opaque-fcstd-proxy'].typeCount, opaqueRecords: semantics.supportSummary['opaque-fcstd-proxy'].recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker })) fail('promotion report diverges from global property semantics') if (roundTrip.classification?.zeroUnknownDrift !== true || !approximatelyTarget(roundTrip.classification.resavedValue) || chrome.status !== 'pass' || chrome.persistence?.fcstdElement !== 'PropertyColor' || chrome.persistence.fcstdValue !== '862362060' || !isDeepStrictEqual(chrome.persistence.loadedValue, target) || chrome.resource?.released !== true || chrome.release?.workerTerminated !== true) fail('G/H closure evidence regressed') console.log(JSON.stringify({ status: 'freecad-property-color-promotion-pass', propertyType: report.propertyType, promotion: report.promotion, completedPhases: requiredPhases, exactBlockerSync: sync, systemExact: report.systemExact }, null, 2)) diff --git a/scripts/check-freecad-property-colorlist-promotion.mjs b/scripts/check-freecad-property-colorlist-promotion.mjs index d6e40a0..868fa15 100644 --- a/scripts/check-freecad-property-colorlist-promotion.mjs +++ b/scripts/check-freecad-property-colorlist-promotion.mjs @@ -25,7 +25,7 @@ const approximately = (value, target, tolerance = 1e-7) => Array.isArray(value) const propertyType = semantics.types?.find(({ typeId }) => typeId === report.propertyType) if (report.promotion?.from !== 'opaque-fcstd-proxy' || report.promotion.to !== 'native-editable-codec' || propertyType?.support !== report.promotion.to || propertyType.statusNames?.length !== 0 || report.promotion.facadeValueModel !== 'ordered-variable-rgba-array-with-external-8-bit-fcstd-packing' || report.promotion.writableRecords !== 2 || report.promotion.fcstdElement !== 'ColorList' || report.promotion.fcstdResourcePath !== 'ColourList' || report.promotion.fcstdResourceBytes !== 12 || !approximately(report.promotion.nativeRoundTripValue, nativeTarget) || !isDeepStrictEqual(report.promotion.browserRoundTripValue, browserTarget) || report.promotion.zeroUnknownDrift !== true) fail('capability promotion is incomplete') const sync = report.exactBlockerSync -if (sync?.nativeEditableTypes !== 34 || sync.nativeEditableRecords !== 4370 || sync.opaqueTypes !== 46 || sync.opaqueRecords !== 457 || sync.exactPromotionReady !== false || sync.exactBlocker !== '46 runtime property types and 457 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') +if (sync?.nativeEditableTypes !== 35 || sync.nativeEditableRecords !== 4372 || sync.opaqueTypes !== 45 || sync.opaqueRecords !== 455 || sync.exactPromotionReady !== false || sync.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') if (!isDeepStrictEqual(sync, { nativeEditableTypes: semantics.supportSummary['native-editable-codec'].typeCount, nativeEditableRecords: semantics.supportSummary['native-editable-codec'].recordCount, opaqueTypes: semantics.supportSummary['opaque-fcstd-proxy'].typeCount, opaqueRecords: semantics.supportSummary['opaque-fcstd-proxy'].recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker })) fail('promotion report diverges from global property semantics') if (roundTrip.classification?.zeroUnknownDrift !== true || !approximately(roundTrip.classification.resavedValue, nativeTarget) || chrome.status !== 'pass' || chrome.persistence?.fcstdElement !== 'ColorList' || chrome.persistence.fcstdResourcePath !== 'ColourList' || chrome.persistence.fcstdResourceBytes !== 12 || !isDeepStrictEqual(chrome.persistence.loadedValue, browserTarget) || chrome.resource?.released !== true || chrome.release?.workerTerminated !== true) fail('G/H closure evidence regressed') console.log(JSON.stringify({ status: 'freecad-property-colorlist-promotion-pass', propertyType: report.propertyType, promotion: report.promotion, completedPhases: requiredPhases, exactBlockerSync: sync, systemExact: report.systemExact }, null, 2)) diff --git a/scripts/check-freecad-property-direction-promotion.mjs b/scripts/check-freecad-property-direction-promotion.mjs index 6265c00..c0a8633 100644 --- a/scripts/check-freecad-property-direction-promotion.mjs +++ b/scripts/check-freecad-property-direction-promotion.mjs @@ -24,7 +24,7 @@ const vectorEquals = (left, right) => !!left && (['x', 'y', 'z'].every((axis) => const propertyType = semantics.types?.find(({ typeId }) => typeId === report.propertyType) if (report.promotion?.from !== 'opaque-fcstd-proxy' || report.promotion.to !== 'native-editable-codec' || propertyType?.support !== report.promotion.to || propertyType.recordCount !== 2 || propertyType.objectTypeCount !== 2 || report.promotion.facadeValueModel !== 'three-finite-vector-components-with-native-propertyvector-fcstd-payload' || report.promotion.writableRecords !== 2 || report.promotion.fcstdElement !== 'PropertyVector' || !vectorEquals(report.promotion.nativeRoundTripValue, target) || !vectorEquals(report.promotion.browserRoundTripValue, target) || report.promotion.zeroUnknownDrift !== true) fail('capability promotion is incomplete') const sync = report.exactBlockerSync -if (sync?.nativeEditableTypes !== 34 || sync.nativeEditableRecords !== 4370 || sync.opaqueTypes !== 46 || sync.opaqueRecords !== 457 || sync.exactPromotionReady !== false || sync.exactBlocker !== '46 runtime property types and 457 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') +if (sync?.nativeEditableTypes !== 35 || sync.nativeEditableRecords !== 4372 || sync.opaqueTypes !== 45 || sync.opaqueRecords !== 455 || sync.exactPromotionReady !== false || sync.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') if (!isDeepStrictEqual(sync, { nativeEditableTypes: semantics.supportSummary['native-editable-codec'].typeCount, nativeEditableRecords: semantics.supportSummary['native-editable-codec'].recordCount, opaqueTypes: semantics.supportSummary['opaque-fcstd-proxy'].typeCount, opaqueRecords: semantics.supportSummary['opaque-fcstd-proxy'].recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker })) fail('promotion report diverges from global property semantics') if (roundTrip.classification?.zeroUnknownDrift !== true || !vectorEquals(roundTrip.classification.resavedValue, target) || chrome.status !== 'pass' || chrome.persistence?.fcstdElement !== 'PropertyVector' || chrome.persistence.fcstdValue !== JSON.stringify(target) || !vectorEquals(chrome.persistence.loadedValue, target) || chrome.resource?.released !== true || chrome.release?.workerTerminated !== true) fail('G/H closure evidence regressed') console.log(JSON.stringify({ status: 'freecad-property-direction-promotion-pass', propertyType: report.propertyType, promotion: report.promotion, completedPhases: requiredPhases, exactBlockerSync: sync, systemExact: report.systemExact }, null, 2)) diff --git a/scripts/check-freecad-property-file-promotion.mjs b/scripts/check-freecad-property-file-promotion.mjs index 1690e44..38d06b5 100644 --- a/scripts/check-freecad-property-file-promotion.mjs +++ b/scripts/check-freecad-property-file-promotion.mjs @@ -24,7 +24,7 @@ const browserTarget = 'project-files/chrome-updated.obj' const propertyType = semantics.types?.find(({ typeId }) => typeId === report.propertyType) if (report.promotion?.from !== 'opaque-fcstd-proxy' || report.promotion.to !== 'native-editable-codec' || propertyType?.support !== report.promotion.to || propertyType.recordCount !== 16 || propertyType.objectTypeCount !== 12 || propertyType.statusNames?.length !== 0 || report.promotion.facadeValueModel !== 'safe-project-relative-path-string-with-native-string-fcstd-payload' || report.promotion.writableRecords !== 16 || report.promotion.objectTypeCount !== 12 || report.promotion.fcstdElement !== 'String' || report.promotion.nativeRoundTripValue !== nativeTarget || report.promotion.browserRoundTripValue !== browserTarget || report.promotion.externalResourceBoundary !== 'project-relative-path; asset bytes remain external to FCStd' || report.promotion.zeroUnknownDrift !== true) fail('capability promotion is incomplete') const sync = report.exactBlockerSync -if (sync?.nativeEditableTypes !== 34 || sync.nativeEditableRecords !== 4370 || sync.opaqueTypes !== 46 || sync.opaqueRecords !== 457 || sync.exactPromotionReady !== false || sync.exactBlocker !== '46 runtime property types and 457 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') +if (sync?.nativeEditableTypes !== 35 || sync.nativeEditableRecords !== 4372 || sync.opaqueTypes !== 45 || sync.opaqueRecords !== 455 || sync.exactPromotionReady !== false || sync.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') if (!isDeepStrictEqual(sync, { nativeEditableTypes: semantics.supportSummary['native-editable-codec'].typeCount, nativeEditableRecords: semantics.supportSummary['native-editable-codec'].recordCount, opaqueTypes: semantics.supportSummary['opaque-fcstd-proxy'].typeCount, opaqueRecords: semantics.supportSummary['opaque-fcstd-proxy'].recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker })) fail('promotion report diverges from global property semantics') if (roundTrip.classification?.zeroUnknownDrift !== true || roundTrip.classification.resavedValue !== nativeTarget || roundTrip.classification.externalResourceBoundary !== 'project-relative-path; asset bytes remain external to FCStd' || chrome.status !== 'pass' || chrome.persistence?.fcstdElement !== 'String' || chrome.persistence.fcstdValue !== browserTarget || chrome.persistence.loadedValue !== browserTarget || chrome.resource?.released !== true || chrome.resource.markerRemoved !== true || chrome.release?.workerTerminated !== true) fail('G/H closure evidence regressed') console.log(JSON.stringify({ status: 'freecad-property-file-promotion-pass', propertyType: report.propertyType, promotion: report.promotion, completedPhases: requiredPhases, exactBlockerSync: sync, systemExact: report.systemExact }, null, 2)) diff --git a/scripts/check-freecad-property-fileincluded-promotion.mjs b/scripts/check-freecad-property-fileincluded-promotion.mjs index bbdbbb6..3ea88de 100644 --- a/scripts/check-freecad-property-fileincluded-promotion.mjs +++ b/scripts/check-freecad-property-fileincluded-promotion.mjs @@ -23,7 +23,7 @@ const nativeTarget = '09f4e4853de4277e25871d26f2e953c6ea9c13a73c7a01bdfcb5209d17 const propertyType = semantics.types?.find(({ typeId }) => typeId === report.propertyType) if (report.promotion?.from !== 'opaque-fcstd-proxy' || report.promotion.to !== 'native-editable-codec' || propertyType?.support !== report.promotion.to || propertyType.recordCount !== 20 || propertyType.objectTypeCount !== 15 || propertyType.statusNames?.join(',') !== 'PropOutput,ReadOnly' || report.promotion.facadeValueModel !== 'project-resource-identity-and-authoritative-bytes-with-native-fileincluded-fcstd-resource' || report.promotion.writableRecords !== 11 || report.promotion.readOnlyRecords !== 8 || report.promotion.outputRecords !== 1 || report.promotion.objectTypeCount !== 15 || report.promotion.fcstdElement !== 'FileIncluded' || report.promotion.nativeArchiveName !== 'payload.bin' || report.promotion.nativeRoundTripSha256 !== nativeTarget || report.promotion.browserArchiveName !== 'chrome-resource.bin' || report.promotion.browserRoundTripBytes !== 16 || report.promotion.externalResourceBoundary !== 'embedded FCStd ZIP entry restored to a read-only document transient file' || report.promotion.zeroUnknownDrift !== true) fail('capability promotion is incomplete') const sync = report.exactBlockerSync -if (sync?.nativeEditableTypes !== 34 || sync.nativeEditableRecords !== 4370 || sync.opaqueTypes !== 46 || sync.opaqueRecords !== 457 || sync.exactPromotionReady !== false || sync.exactBlocker !== '46 runtime property types and 457 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') +if (sync?.nativeEditableTypes !== 35 || sync.nativeEditableRecords !== 4372 || sync.opaqueTypes !== 45 || sync.opaqueRecords !== 455 || sync.exactPromotionReady !== false || sync.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') if (!isDeepStrictEqual(sync, { nativeEditableTypes: semantics.supportSummary['native-editable-codec'].typeCount, nativeEditableRecords: semantics.supportSummary['native-editable-codec'].recordCount, opaqueTypes: semantics.supportSummary['opaque-fcstd-proxy'].typeCount, opaqueRecords: semantics.supportSummary['opaque-fcstd-proxy'].recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker })) fail('promotion report diverges from global property semantics') if (roundTrip.classification?.zeroUnknownDrift !== true || roundTrip.classification.resavedSha256 !== nativeTarget || roundTrip.classification.resavedResourceMatches !== true || roundTrip.classification.externalResourceBoundary !== 'embedded FCStd ZIP entry restored to a read-only document transient file' || chrome.status !== 'pass' || chrome.persistence?.fcstdElement !== 'FileIncluded' || chrome.persistence.fcstdResourcePath !== 'chrome-resource.bin' || chrome.persistence.fcstdResourceBytes !== 16 || chrome.persistence.fcstdResourceMatches !== true || chrome.resource?.documentRoundTrip !== true || chrome.resource.independentReleased !== true || chrome.resource.markerRemoved !== true || chrome.release?.workerTerminated !== true) fail('G/H closure evidence regressed') console.log(JSON.stringify({ status: 'freecad-property-fileincluded-promotion-pass', propertyType: report.propertyType, promotion: report.promotion, completedPhases: requiredPhases, exactBlockerSync: sync, systemExact: report.systemExact }, null, 2)) diff --git a/scripts/check-freecad-property-font-promotion.mjs b/scripts/check-freecad-property-font-promotion.mjs index 070090c..611721f 100644 --- a/scripts/check-freecad-property-font-promotion.mjs +++ b/scripts/check-freecad-property-font-promotion.mjs @@ -23,7 +23,7 @@ const target = 'Noto Sans CJK SC' const propertyType = semantics.types?.find(({ typeId }) => typeId === report.propertyType) if (report.promotion?.from !== 'opaque-fcstd-proxy' || report.promotion.to !== 'native-editable-codec' || propertyType?.support !== report.promotion.to || propertyType.recordCount !== 2 || propertyType.objectTypeCount !== 2 || propertyType.statusNames?.length !== 0 || report.promotion.facadeValueModel !== 'bounded-utf8-font-family-string-with-native-string-fcstd-payload' || report.promotion.writableRecords !== 2 || report.promotion.objectTypeCount !== 2 || report.promotion.fcstdElement !== 'String' || report.promotion.defaultValue !== 'osifont' || report.promotion.nativeRoundTripValue !== target || report.promotion.browserRoundTripValue !== target || report.promotion.fontResourceBoundary !== 'font family name persists in FCStd; font files and availability remain presentation-only' || report.promotion.zeroUnknownDrift !== true) fail('capability promotion is incomplete') const sync = report.exactBlockerSync -if (sync?.nativeEditableTypes !== 34 || sync.nativeEditableRecords !== 4370 || sync.opaqueTypes !== 46 || sync.opaqueRecords !== 457 || sync.exactPromotionReady !== false || sync.exactBlocker !== '46 runtime property types and 457 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') +if (sync?.nativeEditableTypes !== 35 || sync.nativeEditableRecords !== 4372 || sync.opaqueTypes !== 45 || sync.opaqueRecords !== 455 || sync.exactPromotionReady !== false || sync.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') if (!isDeepStrictEqual(sync, { nativeEditableTypes: semantics.supportSummary['native-editable-codec'].typeCount, nativeEditableRecords: semantics.supportSummary['native-editable-codec'].recordCount, opaqueTypes: semantics.supportSummary['opaque-fcstd-proxy'].typeCount, opaqueRecords: semantics.supportSummary['opaque-fcstd-proxy'].recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker })) fail('promotion report diverges from global property semantics') if (roundTrip.classification?.zeroUnknownDrift !== true || roundTrip.classification.resavedValue !== target || roundTrip.classification.fontResourceBoundary !== 'font family name persists in FCStd; font files and availability remain presentation-only' || chrome.status !== 'pass' || chrome.persistence?.fcstdElement !== 'String' || chrome.persistence.fcstdValue !== target || chrome.persistence.loadedValue !== target || chrome.resource?.released !== true || chrome.resource.markerRemoved !== true || chrome.release?.workerTerminated !== true) fail('G/H closure evidence regressed') console.log(JSON.stringify({ status: 'freecad-property-font-promotion-pass', propertyType: report.propertyType, promotion: report.promotion, completedPhases: requiredPhases, exactBlockerSync: sync, systemExact: report.systemExact }, null, 2)) diff --git a/scripts/check-freecad-property-force-promotion.mjs b/scripts/check-freecad-property-force-promotion.mjs index 5a9e62b..10b9836 100644 --- a/scripts/check-freecad-property-force-promotion.mjs +++ b/scripts/check-freecad-property-force-promotion.mjs @@ -23,7 +23,7 @@ const promotion = report.promotion if (promotion?.from !== 'opaque-fcstd-proxy' || promotion.to !== 'native-editable-codec' || propertyType?.support !== promotion.to || propertyType.recordCount !== 4 || propertyType.objectTypeCount !== 2 || propertyType.statusNames?.join(',') !== 'PropOutput') fail('capability identity is incomplete') if (promotion.facadeValueModel !== 'finite-number-internal-mm-kg-per-s2-with-registered-force-units' || promotion.internalUnit !== 'mm*kg/s^2' || promotion.displayUnits?.join(',') !== 'N,kN,lbf' || promotion.newtonScale !== 1000 || promotion.writableRecords !== 1 || promotion.outputRecords !== 3 || promotion.fcstdElement !== 'Float' || promotion.nativeRoundTripValue !== 500000 || promotion.browserRoundTripValue !== 500000 || promotion.outputPropertiesPreserved !== true || promotion.zeroUnknownDrift !== true) fail('capability promotion is incomplete') const sync = report.exactBlockerSync -if (sync?.nativeEditableTypes !== 34 || sync.nativeEditableRecords !== 4370 || sync.opaqueTypes !== 46 || sync.opaqueRecords !== 457 || sync.exactPromotionReady !== false || sync.exactBlocker !== '46 runtime property types and 457 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') +if (sync?.nativeEditableTypes !== 35 || sync.nativeEditableRecords !== 4372 || sync.opaqueTypes !== 45 || sync.opaqueRecords !== 455 || sync.exactPromotionReady !== false || sync.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') if (JSON.stringify(sync) !== JSON.stringify({ nativeEditableTypes: semantics.supportSummary['native-editable-codec'].typeCount, nativeEditableRecords: semantics.supportSummary['native-editable-codec'].recordCount, opaqueTypes: semantics.supportSummary['opaque-fcstd-proxy'].typeCount, opaqueRecords: semantics.supportSummary['opaque-fcstd-proxy'].recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker })) fail('promotion report diverges from global property semantics') if (roundTrip.classification?.zeroUnknownDrift !== true || roundTrip.classification.outputPropertiesPreserved !== true || chrome.status !== 'pass' || chrome.persistence?.fcstdElement !== 'Float' || chrome.persistence.loadedValue !== 500000 || chrome.resource?.released !== true || chrome.release?.workerTerminated !== true) fail('G/H closure evidence regressed') console.log(JSON.stringify({ status: 'freecad-property-force-promotion-pass', propertyType: report.propertyType, promotion, completedPhases: requiredPhases, exactBlockerSync: sync, systemExact: report.systemExact }, null, 2)) diff --git a/scripts/check-freecad-property-heatflux-promotion.mjs b/scripts/check-freecad-property-heatflux-promotion.mjs index 6c4b5f2..20c8f49 100644 --- a/scripts/check-freecad-property-heatflux-promotion.mjs +++ b/scripts/check-freecad-property-heatflux-promotion.mjs @@ -23,7 +23,7 @@ const promotion = report.promotion if (promotion?.from !== 'opaque-fcstd-proxy' || promotion.to !== 'native-editable-codec' || propertyType?.support !== promotion.to || propertyType.recordCount !== 1 || propertyType.objectTypeCount !== 1 || propertyType.statusNames?.length !== 0) fail('capability identity is incomplete') if (promotion.facadeValueModel !== 'finite-number-internal-kg-per-s3-with-registered-heat-flux-units' || promotion.internalUnit !== 'kg/s^3' || promotion.displayUnits?.join(',') !== 'W/m^2,kW/m^2,W/mm^2' || promotion.wattsPerSquareMeterScale !== 1 || promotion.writableRecords !== 1 || promotion.outputRecords !== 0 || promotion.fcstdElement !== 'Float' || promotion.nativeRoundTripValue !== 500 || promotion.browserRoundTripValue !== 500 || promotion.zeroUnknownDrift !== true) fail('capability promotion is incomplete') const sync = report.exactBlockerSync -if (sync?.nativeEditableTypes !== 34 || sync.nativeEditableRecords !== 4370 || sync.opaqueTypes !== 46 || sync.opaqueRecords !== 457 || sync.exactPromotionReady !== false || sync.exactBlocker !== '46 runtime property types and 457 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') +if (sync?.nativeEditableTypes !== 35 || sync.nativeEditableRecords !== 4372 || sync.opaqueTypes !== 45 || sync.opaqueRecords !== 455 || sync.exactPromotionReady !== false || sync.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') if (JSON.stringify(sync) !== JSON.stringify({ nativeEditableTypes: semantics.supportSummary['native-editable-codec'].typeCount, nativeEditableRecords: semantics.supportSummary['native-editable-codec'].recordCount, opaqueTypes: semantics.supportSummary['opaque-fcstd-proxy'].typeCount, opaqueRecords: semantics.supportSummary['opaque-fcstd-proxy'].recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker })) fail('promotion report diverges from global property semantics') if (roundTrip.classification?.zeroUnknownDrift !== true || roundTrip.classification.nativeValueMatches !== true || chrome.status !== 'pass' || chrome.persistence?.fcstdElement !== 'Float' || chrome.persistence.loadedValue !== 500 || chrome.resource?.released !== true || chrome.release?.workerTerminated !== true) fail('G/H closure evidence regressed') console.log(JSON.stringify({ status: 'freecad-property-heatflux-promotion-pass', propertyType: report.propertyType, promotion, completedPhases: requiredPhases, exactBlockerSync: sync, systemExact: report.systemExact }, null, 2)) diff --git a/scripts/check-freecad-property-integerset-promotion.mjs b/scripts/check-freecad-property-integerset-promotion.mjs index 036eb74..848e4a2 100644 --- a/scripts/check-freecad-property-integerset-promotion.mjs +++ b/scripts/check-freecad-property-integerset-promotion.mjs @@ -15,7 +15,7 @@ const propertyType = semantics.types?.find(({ typeId }) => typeId === report.pro const promotion = report.promotion if (promotion?.from !== 'opaque-fcstd-proxy' || promotion.to !== 'native-editable-codec' || propertyType?.support !== promotion.to || propertyType.recordCount !== 2 || propertyType.objectTypeCount !== 2 || propertyType.statusNames?.length !== 0 || promotion.facadeValueModel !== 'sorted-unique-safe-integer-array' || promotion.writableRecords !== 2 || promotion.nativeEmptyBindingBoundary?.neverAssignedEmptyRead !== 'native-process-termination' || promotion.fcstdElement !== 'IntegerSet' || !isDeepStrictEqual(promotion.nativeRoundTripValue, target) || !isDeepStrictEqual(promotion.browserRoundTripValue, target) || promotion.zeroUnknownDrift !== true) fail('capability promotion is incomplete') const sync = report.exactBlockerSync -if (sync?.nativeEditableTypes !== 34 || sync.nativeEditableRecords !== 4370 || sync.opaqueTypes !== 46 || sync.opaqueRecords !== 457 || sync.exactPromotionReady !== false || sync.exactBlocker !== '46 runtime property types and 457 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') +if (sync?.nativeEditableTypes !== 35 || sync.nativeEditableRecords !== 4372 || sync.opaqueTypes !== 45 || sync.opaqueRecords !== 455 || sync.exactPromotionReady !== false || sync.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') if (!isDeepStrictEqual(sync, { nativeEditableTypes: semantics.supportSummary['native-editable-codec'].typeCount, nativeEditableRecords: semantics.supportSummary['native-editable-codec'].recordCount, opaqueTypes: semantics.supportSummary['opaque-fcstd-proxy'].typeCount, opaqueRecords: semantics.supportSummary['opaque-fcstd-proxy'].recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker })) fail('promotion report diverges from global property semantics') if (roundTrip.classification?.zeroUnknownDrift !== true || !isDeepStrictEqual(roundTrip.classification.resavedValue, target) || chrome.status !== 'pass' || chrome.persistence?.fcstdElement !== 'IntegerSet' || !isDeepStrictEqual(chrome.persistence.loadedValue, target) || chrome.resource?.released !== true || chrome.release?.workerTerminated !== true) fail('G/H closure evidence regressed') console.log(JSON.stringify({ status: 'freecad-property-integerset-promotion-pass', propertyType: report.propertyType, promotion, completedPhases: requiredPhases, exactBlockerSync: sync, systemExact: report.systemExact }, null, 2)) diff --git a/scripts/check-freecad-property-linklisthidden-failure.mjs b/scripts/check-freecad-property-linklisthidden-failure.mjs new file mode 100644 index 0000000..8768af6 --- /dev/null +++ b/scripts/check-freecad-property-linklisthidden-failure.mjs @@ -0,0 +1,48 @@ +import { isDeepStrictEqual } from 'node:util' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const report = JSON.parse(await readFile(resolve(root, 'config/freecad-property-linklisthidden-failure.json'), 'utf8')) +const fail = (message) => { throw new Error(`FreeCAD PropertyLinkListHidden failure check failed: ${message}`) } +if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baselineId !== 'freecad-1.1.1-property-linklisthidden-failure' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.propertyType !== 'App::PropertyLinkListHidden') fail('baseline is invalid') +if (report.object?.name !== 'SketchProbe' || report.object.typeId !== 'Sketcher::SketchObject' || report.property?.name !== 'Exports' || report.property.typeId !== 'App::PropertyLinkListHidden') fail('fixture identity changed') +const expectedObjects = [ + { name: 'SketchProbe', typeId: 'Sketcher::SketchObject' }, + { name: 'ExportA', typeId: 'Part::Feature' }, + { name: 'ExportB', typeId: 'Part::Feature' }, +] +const initial = report.initial +if (!isDeepStrictEqual(initial?.value, ['ExportA', 'ExportB']) || initial.propertyTypeId !== 'App::PropertyLinkListHidden' || !isDeepStrictEqual(initial.propertyStatus, ['26']) || !isDeepStrictEqual(initial.editorMode, ['Hidden']) || !isDeepStrictEqual(initial.ownerState, ['Up-to-date']) || initial.statusString !== 'Valid' || initial.ownerShapeNull !== true || !isDeepStrictEqual(initial.ownerOutList, []) || !isDeepStrictEqual(initial.targetInLists, { ExportA: [], ExportB: [] }) || typeof initial.targetShapeHashes?.ExportA !== 'string' || typeof initial.targetShapeHashes?.ExportB !== 'string' || !isDeepStrictEqual(initial.objectSet, expectedObjects)) fail('initial hidden-link state is invalid') +const assertBoundary = (snapshot, label) => { + if (snapshot?.propertyTypeId !== 'App::PropertyLinkListHidden' || snapshot.ownerShapeNull !== true || !isDeepStrictEqual(snapshot.ownerOutList, []) || !isDeepStrictEqual(snapshot.targetInLists, { ExportA: [], ExportB: [] }) || !isDeepStrictEqual(snapshot.targetShapeHashes, initial.targetShapeHashes) || !isDeepStrictEqual(snapshot.objectSet, expectedObjects)) fail(`${label} polluted hidden dependencies, Shapes, or objects`) +} +const expectedFailures = new Map([ + ['string-scalar', ['TypeError', 'Type must be App.DocumentObject or None, not str']], + ['string-item', ['TypeError', 'Type must be App.DocumentObject or None, not str']], + ['integer-scalar', ['TypeError', 'Type must be App.DocumentObject or None, not int']], + ['integer-item', ['TypeError', 'Type must be App.DocumentObject or None, not int']], + ['cross-document', ['ValueError', 'PropertyLinkList does not support external object']], +]) +if (report.failures?.length !== expectedFailures.size) fail('failure case count changed') +for (const entry of report.failures) { + const expected = expectedFailures.get(entry.id) + if (!expected || entry.exception?.type !== expected[0] || entry.exception.message !== expected[1] || entry.valuePreserved !== true || entry.documentPreserved !== true || !isDeepStrictEqual(entry.before, initial) || !isDeepStrictEqual(entry.after, initial)) fail(`${entry.id} did not reject atomically`) +} + +const editor = report.editorReadOnly +if (editor?.classification !== 'editor-read-only-python-setter-accepted' || !isDeepStrictEqual(editor.editorMode, ['ReadOnly', 'Hidden']) || editor.exception !== null || editor.pythonBypassesEditorReadOnly !== true || !isDeepStrictEqual(editor.before.value, ['ExportA', 'ExportB']) || !isDeepStrictEqual(editor.after.value, ['ExportB']) || !isDeepStrictEqual(editor.after.propertyStatus, ['ReadOnly', '26']) || !isDeepStrictEqual(editor.after.editorMode, ['ReadOnly', 'Hidden']) || !isDeepStrictEqual(editor.after.ownerState, ['Touched']) || !isDeepStrictEqual(editor.restoredEditorMode, ['Hidden'])) fail('ReadOnly Python acceptance boundary changed') +assertBoundary(editor.before, 'ReadOnly before') +assertBoundary(editor.after, 'ReadOnly after') +const immutable = report.immutable +if (immutable?.classification !== 'immutable-python-setter-accepted' || immutable.exception !== null || immutable.pythonBypassesImmutable !== true || !isDeepStrictEqual(immutable.before.value, ['ExportA', 'ExportB']) || !isDeepStrictEqual(immutable.after.value, ['ExportB']) || !isDeepStrictEqual(immutable.before.propertyStatus, ['Immutable', '26']) || !isDeepStrictEqual(immutable.after.propertyStatus, ['Immutable', '26']) || !isDeepStrictEqual(immutable.after.ownerState, ['Touched']) || !isDeepStrictEqual(immutable.restoredStatus, ['26'])) fail('Immutable Python acceptance boundary changed') +assertBoundary(immutable.before, 'Immutable before') +assertBoundary(immutable.after, 'Immutable after') + +const transaction = report.transaction +if (transaction?.undoMode !== 1 || transaction.pendingAfterEdit !== true || transaction.pendingAfterAbort !== false || transaction.activeAfterEdit?.name !== 'property-linklisthidden-cancel' || !(transaction.activeAfterEdit.id > 0) || transaction.activeAfterAbort?.name !== '' || transaction.activeAfterAbort.id !== 0 || !isDeepStrictEqual(transaction.before?.value, ['ExportA', 'ExportB']) || !isDeepStrictEqual(transaction.edited?.value, ['ExportB', 'ExportA', 'ExportB']) || !isDeepStrictEqual(transaction.afterAbortBeforeRecompute?.value, ['ExportA', 'ExportB']) || !isDeepStrictEqual(transaction.afterAbortBeforeRecompute.ownerState, ['Touched']) || transaction.recomputeAfterAbort !== true || !isDeepStrictEqual(transaction.afterAbort?.ownerState, ['Up-to-date']) || transaction.afterAbort.statusString !== 'Valid' || transaction.restored !== true || !isDeepStrictEqual(transaction.before, transaction.afterAbort)) fail('transaction abort and recompute recovery changed') +for (const [name, snapshot] of Object.entries({ before: transaction.before, edited: transaction.edited, afterAbortBeforeRecompute: transaction.afterAbortBeforeRecompute, afterAbort: transaction.afterAbort })) assertBoundary(snapshot, `transaction ${name}`) +if (report.cancellationBoundary?.supported !== false || report.cancellationBoundary.classification !== 'synchronous-property-setter' || report.cancellationBoundary.reason !== 'no-native-cancel-hook' || report.cancellationBoundary.replacement !== 'abort-active-document-transaction') fail('cancellation boundary is incomplete') +if (report.documentIntegrity?.objectsPreserved !== true || !isDeepStrictEqual(report.documentIntegrity.initialObjects, expectedObjects) || !isDeepStrictEqual(report.documentIntegrity.finalObjects, expectedObjects) || !isDeepStrictEqual(report.documentIntegrity.foreignDocumentObjects, [{ name: 'ForeignExport', typeId: 'Part::Feature' }])) fail('failure cases polluted either document') + +console.log(JSON.stringify({ status: 'freecad-property-linklisthidden-failure-pass', failures: report.failures.map(({ id, exception }) => ({ id, exception })), readOnlyPythonAccepted: editor.pythonBypassesEditorReadOnly, immutablePythonAccepted: immutable.pythonBypassesImmutable, transactionRestored: transaction.restored, cancellationBoundary: report.cancellationBoundary }, null, 2)) diff --git a/scripts/check-freecad-property-linklisthidden-inventory.mjs b/scripts/check-freecad-property-linklisthidden-inventory.mjs new file mode 100644 index 0000000..70b8783 --- /dev/null +++ b/scripts/check-freecad-property-linklisthidden-inventory.mjs @@ -0,0 +1,50 @@ +import { createHash } from 'node:crypto' +import { isDeepStrictEqual } from 'node:util' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const runtimePath = '.cache/freecad/reference-desktop.json' +const reportPath = 'config/freecad-property-linklisthidden-inventory.json' +const [runtimeContent, reportContent] = await Promise.all([readFile(resolve(root, runtimePath)), readFile(resolve(root, reportPath))]) +const runtime = JSON.parse(runtimeContent) +const report = JSON.parse(reportContent) +const fail = (message) => { throw new Error(`FreeCAD PropertyLinkListHidden inventory check failed: ${message}`) } +if (report.schemaVersion !== 1 || report.status !== 'pass' || report.propertyType !== 'App::PropertyLinkListHidden' || report.classification !== 'opaque-fcstd-proxy' || report.baseline?.freecadVersion !== '1.1.1' || report.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('report boundary is invalid') +if (report.recordCount !== 2 || report.objectTypeCount !== 2 || report.writableRecordCount !== 2 || report.records?.length !== 2) fail('runtime record or host count changed') +const typeContract = report.typeContract +if (typeContract?.inheritedFrom !== 'App::PropertyLinkList' || typeContract.linkScope !== 'Hidden' || typeContract.propertyStatus !== 'PropHidden' || typeContract.xmlElement !== 'LinkList' || typeContract.xmlChildElement !== 'Link' || typeContract.legacyRestoreType !== 'App::PropertyLinkList') fail('type contract is incomplete') +if (report.provenance?.runtime?.path !== runtimePath || report.provenance.runtime.bytes !== runtimeContent.length || report.provenance.runtime.sha256 !== createHash('sha256').update(runtimeContent).digest('hex')) fail('runtime provenance is stale') +for (const locked of report.provenance?.sources ?? []) { + const content = await readFile(resolve(root, locked.path)) + if (content.length !== locked.bytes || createHash('sha256').update(content).digest('hex') !== locked.sha256) fail(`source provenance is stale for ${locked.path}`) +} + +const nativeMatches = [] +for (const objectType of runtime.runtimeObjects?.types ?? []) for (const property of objectType.properties ?? []) { + if (property.typeId === 'App::PropertyLinkListHidden') nativeMatches.push({ objectTypeId: objectType.typeId, objectAvailable: objectType.available === true, probeStatus: objectType.probeStatus, propertyName: property.name, group: property.group, statusRaw: property.status, defaultRaw: property.default }) +} +const identity = ({ objectTypeId, objectAvailable, probeStatus, propertyName, group, statusRaw, defaultRaw }) => ({ objectTypeId, objectAvailable, probeStatus, propertyName, group, statusRaw, defaultRaw }) +const sorted = (records) => records.map(identity).sort((left, right) => `${left.objectTypeId}.${left.propertyName}`.localeCompare(`${right.objectTypeId}.${right.propertyName}`)) +if (!isDeepStrictEqual(sorted(nativeMatches), sorted(report.records))) fail('report diverges from the locked runtime oracle') +const expectedKeys = ['Sketcher::SketchObject.Exports', 'Sketcher::SketchObjectPython.Exports'] +if (!isDeepStrictEqual(report.records.map(({ objectTypeId, propertyName }) => `${objectTypeId}.${propertyName}`), expectedKeys)) fail('native host/property identities changed') +for (const record of report.records) { + const model = record.valueModel + const inputs = record.inputs + const dependencies = record.dependencies + if (record.objectAvailable !== true || record.probeStatus !== 'available' || record.group !== 'Sketch' || !isDeepStrictEqual(record.statusRaw, [26]) || !isDeepStrictEqual(record.statusNames, ['PropHidden']) || !isDeepStrictEqual(record.defaultRaw, [])) fail(`native metadata changed for ${record.objectTypeId}.Exports`) + if (model?.kind !== 'hidden-document-object-list' || model.inheritedFrom !== 'App::PropertyLinkList' || model.elementType !== 'same-document App::DocumentObject reference' || model.orderPreserved !== true || model.duplicatesPreserved !== true || model.writable !== true) fail(`value model changed for ${record.objectTypeId}.Exports`) + if (inputs?.nativeCppSetValues !== 'attached same-document DocumentObject list' || inputs.emptyListClears !== true || inputs.singleNullListClears !== true || inputs.detachedObjectRejected !== true || inputs.crossDocumentRejectedWithoutLinkAllowExternal !== true) fail(`input preconditions changed for ${record.objectTypeId}.Exports`) + if (dependencies?.linkScope !== 'Hidden' || dependencies.createsBacklinks !== false || dependencies.includedByDefaultGetLinks !== false || dependencies.includedByGetLinksAll !== true || dependencies.defaultDagDependency !== false) fail(`hidden dependency scope changed for ${record.objectTypeId}.Exports`) + if (record.applicability?.requiredObjectTypeId !== record.objectTypeId || record.applicability.role !== 'Sketch exported child geometry lookup by object name' || record.applicability.propertyWriteRequiresShape !== false) fail(`applicability changed for ${record.objectTypeId}.Exports`) +} + +const sources = Object.fromEntries(await Promise.all(report.provenance.sources.map(async ({ path }) => [path, await readFile(resolve(root, path), 'utf8')]))) +const linksHeader = sources['.cache/freecad/FreeCAD/src/App/PropertyLinks.h'] +const linksSource = sources['.cache/freecad/FreeCAD/src/App/PropertyLinks.cpp'] +const sketchHeader = sources['.cache/freecad/FreeCAD/src/Mod/Sketcher/App/SketchObject.h'] +const sketchSource = sources['.cache/freecad/FreeCAD/src/Mod/Sketcher/App/SketchObject.cpp'] +if (!linksHeader.includes('class AppExport PropertyLinkListHidden: public PropertyLinkList') || !linksHeader.includes('_pcScope = LinkScope::Hidden;') || !linksSource.includes('TYPESYSTEM_SOURCE(App::PropertyLinkListHidden, App::PropertyLinkList)') || !linksSource.includes('if (all || _pcScope != LinkScope::Hidden)') || !linksSource.includes(' objectTypeId), propertyStatus: typeContract.propertyStatus, linkScope: typeContract.linkScope, classification: report.classification }, null, 2)) diff --git a/scripts/check-freecad-property-linklisthidden-mutation.mjs b/scripts/check-freecad-property-linklisthidden-mutation.mjs new file mode 100644 index 0000000..907304a --- /dev/null +++ b/scripts/check-freecad-property-linklisthidden-mutation.mjs @@ -0,0 +1,30 @@ +import { isDeepStrictEqual } from 'node:util' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const report = JSON.parse(await readFile(resolve(root, 'config/freecad-property-linklisthidden-mutation.json'), 'utf8')) +const fail = (message) => { throw new Error(`FreeCAD PropertyLinkListHidden mutation check failed: ${message}`) } +if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baselineId !== 'freecad-1.1.1-property-linklisthidden-mutation' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.propertyType !== 'App::PropertyLinkListHidden' || report.caseCount !== 2 || report.cases?.length !== 2) fail('baseline is invalid') +const expectedHosts = new Map([ + ['Sketcher::SketchObject', 'SketchProbe'], + ['Sketcher::SketchObjectPython', 'SketchPythonProbe'], +]) +const expectedObjects = (ownerName, ownerTypeId) => [ + { name: ownerName, typeId: ownerTypeId }, + { name: 'ExportA', typeId: 'Part::Feature' }, + { name: 'ExportB', typeId: 'Part::Feature' }, +] +for (const entry of report.cases) { + const ownerName = expectedHosts.get(entry.objectTypeId) + if (!ownerName || entry.objectName !== ownerName || entry.propertyName !== 'Exports' || entry.editRecomputeResult !== true || entry.restoreRecomputeResult !== true || entry.valueRestored !== true || entry.semanticStateRestored !== true) fail(`${entry.objectTypeId} mutation identity or recovery changed`) + if (!isDeepStrictEqual(entry.before?.value, ['ExportA', 'ExportB']) || !isDeepStrictEqual(entry.touchedAfterEdit?.value, ['ExportB', 'ExportA', 'ExportB']) || !isDeepStrictEqual(entry.edited?.value, ['ExportB', 'ExportA', 'ExportB']) || !isDeepStrictEqual(entry.touchedAfterRestore?.value, ['ExportA', 'ExportB']) || !isDeepStrictEqual(entry.restored?.value, ['ExportA', 'ExportB'])) fail(`${entry.objectTypeId} value mutation changed`) + if (!isDeepStrictEqual(entry.before.ownerState, ['Up-to-date']) || entry.before.statusString !== 'Valid' || !isDeepStrictEqual(entry.touchedAfterEdit.ownerState, ['Touched']) || entry.touchedAfterEdit.statusString !== 'Touched' || !isDeepStrictEqual(entry.edited.ownerState, ['Up-to-date']) || entry.edited.statusString !== 'Valid' || !isDeepStrictEqual(entry.touchedAfterRestore.ownerState, ['Touched']) || entry.touchedAfterRestore.statusString !== 'Touched' || !isDeepStrictEqual(entry.restored.ownerState, ['Up-to-date']) || entry.restored.statusString !== 'Valid') fail(`${entry.objectTypeId} state transition changed`) + const baselineTargets = entry.before.targetShapes + for (const [phase, snapshot] of Object.entries({ before: entry.before, touchedAfterEdit: entry.touchedAfterEdit, edited: entry.edited, touchedAfterRestore: entry.touchedAfterRestore, restored: entry.restored })) { + if (snapshot.propertyTypeId !== 'App::PropertyLinkListHidden' || snapshot.group !== 'Sketch' || !isDeepStrictEqual(snapshot.propertyStatus, ['26']) || !isDeepStrictEqual(snapshot.editorMode, ['Hidden']) || snapshot.ownerShape?.isNull !== true || snapshot.ownerShape.solids !== 0 || snapshot.ownerShape.faces !== 0 || snapshot.ownerShape.edges !== 0 || snapshot.ownerShape.vertices !== 0 || snapshot.ownerShape.brepSha256 !== null) fail(`${entry.objectTypeId} ${phase} metadata or host Shape changed`) + if (!isDeepStrictEqual(snapshot.ownerOutList, []) || !isDeepStrictEqual(snapshot.targetInLists, { ExportA: [], ExportB: [] }) || !isDeepStrictEqual(snapshot.targetShapes, baselineTargets) || !isDeepStrictEqual(snapshot.objectSet, expectedObjects(ownerName, entry.objectTypeId))) fail(`${entry.objectTypeId} ${phase} leaked dependencies or changed targets/objects`) + } + if (baselineTargets?.ExportA?.solids !== 1 || baselineTargets.ExportA.faces !== 6 || baselineTargets.ExportA.edges !== 12 || baselineTargets.ExportA.vertices !== 8 || baselineTargets.ExportB?.solids !== 1 || baselineTargets.ExportB.faces !== 3 || baselineTargets.ExportB.edges !== 3 || baselineTargets.ExportB.vertices !== 2) fail(`${entry.objectTypeId} target topology changed`) +} +console.log(JSON.stringify({ status: 'freecad-property-linklisthidden-mutation-pass', cases: report.cases.map(({ objectTypeId, before, edited, restored, semanticStateRestored }) => ({ objectTypeId, before: before.value, edited: edited.value, restored: restored.value, semanticStateRestored })) }, null, 2)) diff --git a/scripts/check-freecad-property-linklisthidden-promotion.mjs b/scripts/check-freecad-property-linklisthidden-promotion.mjs new file mode 100644 index 0000000..53cd9d3 --- /dev/null +++ b/scripts/check-freecad-property-linklisthidden-promotion.mjs @@ -0,0 +1,28 @@ +import { isDeepStrictEqual } from 'node:util' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse) +const [report, semantics, progress, roundTrip, chrome] = await Promise.all([ + load('config/freecad-property-linklisthidden-promotion.json'), + load('config/freecad-native-property-semantics.json'), + load('config/freecad-follow-up-task-progress.json'), + load('config/freecad-property-linklisthidden-roundtrip.json'), + load('config/chrome-property-linklisthidden-verification.json'), +]) +const fail = (message) => { throw new Error(`FreeCAD PropertyLinkListHidden promotion check failed: ${message}`) } +if (report.schemaVersion !== 1 || report.status !== 'pass' || report.taskId !== 'PROP-app-propertylinklisthidden-I' || report.propertyType !== 'App::PropertyLinkListHidden' || report.recordCount !== 2 || report.baseline?.freecadVersion !== '1.1.1' || report.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.systemExact !== false) fail('promotion report boundary is invalid') +const requiredPhases = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] +const completed = new Map(progress.completedTasks?.map((entry) => [entry.id, entry]) ?? []) +for (const phase of requiredPhases) { const taskId = `PROP-app-propertylinklisthidden-${phase}`; if (!completed.has(taskId) || !Array.isArray(report.phaseEvidence?.[phase]) || report.phaseEvidence[phase].length === 0 || !isDeepStrictEqual(report.phaseEvidence[phase], completed.get(taskId).evidence)) fail(`phase ${phase} evidence is incomplete`) } +const initial = ['ExportA', 'ExportB'] +const target = ['ExportB', 'ExportA', 'ExportB'] +const propertyType = semantics.types?.find(({ typeId }) => typeId === report.propertyType) +const promotion = report.promotion +if (promotion?.from !== 'opaque-fcstd-proxy' || promotion.to !== 'native-editable-codec' || propertyType?.support !== promotion.to || propertyType.recordCount !== 2 || propertyType.objectTypeCount !== 2 || propertyType.statusNames?.join(',') !== 'PropHidden' || promotion.facadeValueModel !== 'ordered-duplicate-preserving-same-document-object-name-array' || promotion.writableRecords !== 2 || promotion.objectTypeCount !== 2 || promotion.propertyStatus !== 'PropHidden' || promotion.linkScope !== 'Hidden' || promotion.runtimeDependencyEdges !== 0 || promotion.fcstdElement !== 'LinkList' || !isDeepStrictEqual(promotion.nativeRoundTripValue, target) || !isDeepStrictEqual(promotion.browserRoundTripValue, target) || promotion.hiddenUiFiltered !== true || promotion.hiddenLinksPreserved !== true || promotion.nativeDependencyMetadataNormalized !== true || promotion.zeroUnknownDrift !== true) fail('capability promotion is incomplete') +const sync = report.exactBlockerSync +if (sync?.nativeEditableTypes !== 35 || sync.nativeEditableRecords !== 4372 || sync.opaqueTypes !== 45 || sync.opaqueRecords !== 455 || sync.exactPromotionReady !== false || sync.exactBlocker !== '45 runtime property types and 455 records remain opaque-only; complete native document and property semantics') fail('exact blocker synchronization is stale') +if (!isDeepStrictEqual(sync, { nativeEditableTypes: semantics.supportSummary['native-editable-codec'].typeCount, nativeEditableRecords: semantics.supportSummary['native-editable-codec'].recordCount, opaqueTypes: semantics.supportSummary['opaque-fcstd-proxy'].typeCount, opaqueRecords: semantics.supportSummary['opaque-fcstd-proxy'].recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker })) fail('promotion report diverges from global property semantics') +if (roundTrip.classification?.zeroUnknownDrift !== true || !isDeepStrictEqual(roundTrip.classification.resavedValue, target) || roundTrip.classification.hiddenLinksPreserved !== true || roundTrip.classification.nativeDependencyMetadataNormalized !== true || chrome.status !== 'pass' || !isDeepStrictEqual(chrome.persistence?.loadedValue, target) || chrome.persistence?.fcstdElement !== 'LinkList' || chrome.ui?.hiddenControlCountBefore !== 0 || chrome.ui?.hiddenControlCountAfter !== 0 || chrome.resource?.released !== true || chrome.release?.workerTerminated !== true) fail('G/H closure evidence regressed') +console.log(JSON.stringify({ status: 'freecad-property-linklisthidden-promotion-pass', propertyType: report.propertyType, promotion, completedPhases: requiredPhases, exactBlockerSync: sync, systemExact: report.systemExact }, null, 2)) diff --git a/scripts/check-freecad-property-linklisthidden-roundtrip.mjs b/scripts/check-freecad-property-linklisthidden-roundtrip.mjs new file mode 100644 index 0000000..118e5a4 --- /dev/null +++ b/scripts/check-freecad-property-linklisthidden-roundtrip.mjs @@ -0,0 +1,26 @@ +import { createHash } from 'node:crypto' +import { isDeepStrictEqual } from 'node:util' +import { readFile, stat } from 'node:fs/promises' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const report = JSON.parse(await readFile(resolve(root, 'config/freecad-property-linklisthidden-roundtrip.json'), 'utf8')) +const fail = (message) => { throw new Error(`FreeCAD PropertyLinkListHidden roundtrip check failed: ${message}`) } +if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baselineId !== 'freecad-1.1.1-property-linklisthidden-roundtrip' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.propertyType !== 'App::PropertyLinkListHidden') fail('baseline is invalid') +const initial = ['ExportA', 'ExportB'] +const target = ['ExportB', 'ExportA', 'ExportB'] +const objectSet = [{ name: 'SketchProbe', typeId: 'Sketcher::SketchObject' }, { name: 'ExportA', typeId: 'Part::Feature' }, { name: 'ExportB', typeId: 'Part::Feature' }] +for (const [phase, snapshot, value] of [['nativeInitial', report.nativeInitial, initial], ['nativeReopened', report.nativeAfter?.reopened, target], ['nativeResaved', report.nativeAfter?.resaved, target]]) { + if (!isDeepStrictEqual(snapshot?.objectSet, objectSet) || snapshot.object?.name !== 'SketchProbe' || snapshot.object.typeId !== 'Sketcher::SketchObject' || snapshot.object.propertyName !== 'Exports' || snapshot.object.propertyTypeId !== 'App::PropertyLinkListHidden' || snapshot.object.group !== 'Sketch' || !isDeepStrictEqual(snapshot.object.value, value) || !isDeepStrictEqual(snapshot.object.propertyStatus, ['26']) || !isDeepStrictEqual(snapshot.object.editorMode, ['Hidden']) || !isDeepStrictEqual(snapshot.object.state, ['Up-to-date']) || snapshot.object.statusString !== 'Valid' || !isDeepStrictEqual(snapshot.object.outList, [])) fail(`${phase} native hidden-link semantics changed`) + const ownerShape = snapshot.object.shape + if (ownerShape?.isNull !== true || ownerShape.valid !== false || ownerShape.shapeType !== 'Null' || ownerShape.solids !== 0 || ownerShape.faces !== 0 || ownerShape.edges !== 0 || ownerShape.vertices !== 0 || ownerShape.brepSha256 !== null) fail(`${phase} owner Shape boundary changed`) + const box = snapshot.targets?.ExportA + const cylinder = snapshot.targets?.ExportB + if (box?.typeId !== 'Part::Feature' || !isDeepStrictEqual(box.inList, []) || box.shape?.isNull !== false || box.shape.valid !== true || box.shape.shapeType !== 'Solid' || box.shape.solids !== 1 || box.shape.faces !== 6 || box.shape.edges !== 12 || box.shape.vertices !== 8 || box.shape.area !== 52 || box.shape.volume !== 24 || !/^[0-9a-f]{64}$/.test(box.shape.brepSha256)) fail(`${phase} box target changed`) + if (cylinder?.typeId !== 'Part::Feature' || !isDeepStrictEqual(cylinder.inList, []) || cylinder.shape?.isNull !== false || cylinder.shape.valid !== true || cylinder.shape.shapeType !== 'Solid' || cylinder.shape.solids !== 1 || cylinder.shape.faces !== 3 || cylinder.shape.edges !== 3 || cylinder.shape.vertices !== 2 || cylinder.shape.area !== 61.261056745 || cylinder.shape.volume !== 35.342917353 || !/^[0-9a-f]{64}$/.test(cylinder.shape.brepSha256)) fail(`${phase} cylinder target changed`) +} +if (report.web.before?.typeId !== 'App::PropertyLinkListHidden' || report.web.before.element !== 'LinkList' || report.web.after?.typeId !== 'App::PropertyLinkListHidden' || report.web.after.element !== 'LinkList' || report.web.targetMarkedTouched !== true || !isDeepStrictEqual(report.web.initialDependencyTargets, initial) || !isDeepStrictEqual(report.web.webDependencyTargets, initial) || report.web.hiddenDependencyMetadataPreserved !== true || report.web.webOutputMatches !== true || report.web.objectSetPreserved !== true || report.web.semanticObjectsPreserved !== true || report.web.opaqueEntriesPreserved !== true || !Number.isSafeInteger(report.web.opaquePathsPreserved) || report.web.opaquePathsPreserved < 1) fail('Web edit did not preserve the native archive boundary') +if (!isDeepStrictEqual(report.classification?.requestedValue, target) || !isDeepStrictEqual(report.classification.webValue, target) || !isDeepStrictEqual(report.classification.reopenedValue, target) || !isDeepStrictEqual(report.classification.resavedValue, target) || report.classification.nativeOutputMatches !== true || report.classification.hiddenLinksPreserved !== true || !isDeepStrictEqual(report.classification.nativeResavedDependencyTargets, target) || report.classification.nativeDependencyMetadataNormalized !== true || report.classification.targetShapeStructurePreserved !== true || report.classification.ownerShapePreserved !== true || report.classification.unknownSemanticDrift !== false || report.classification.zeroUnknownDrift !== true || report.classification.nativeTargetBrepHashes?.length !== 3) fail('roundtrip classification is incomplete') +for (const hashes of report.classification.nativeTargetBrepHashes) if (!/^[0-9a-f]{64}$/.test(hashes.ExportA) || !/^[0-9a-f]{64}$/.test(hashes.ExportB)) fail('native target BREP evidence is incomplete') +for (const archive of Object.values(report.archives ?? {})) { const path = resolve(root, archive.path ?? ''); const content = await readFile(path); if ((await stat(path)).size !== archive.bytes || createHash('sha256').update(content).digest('hex') !== archive.sha256) fail(`archive is stale: ${archive.path}`) } +console.log(JSON.stringify({ status: 'freecad-property-linklisthidden-roundtrip-pass', values: { nativeInitial: initial, webEdited: report.classification.webValue, nativeReopened: report.classification.reopenedValue, nativeResaved: report.classification.resavedValue }, hiddenLinksPreserved: report.classification.hiddenLinksPreserved, targetShapeStructurePreserved: report.classification.targetShapeStructurePreserved, opaquePathsPreserved: report.web.opaquePathsPreserved, zeroUnknownDrift: report.classification.zeroUnknownDrift }, null, 2)) diff --git a/scripts/check-freecad-property-linklisthidden-success.mjs b/scripts/check-freecad-property-linklisthidden-success.mjs new file mode 100644 index 0000000..753394c --- /dev/null +++ b/scripts/check-freecad-property-linklisthidden-success.mjs @@ -0,0 +1,41 @@ +import { isDeepStrictEqual } from 'node:util' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const report = JSON.parse(await readFile(resolve(root, 'config/freecad-property-linklisthidden-success.json'), 'utf8')) +const fail = (message) => { throw new Error(`FreeCAD PropertyLinkListHidden success check failed: ${message}`) } +if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baselineId !== 'freecad-1.1.1-property-linklisthidden-success' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.propertyType !== 'App::PropertyLinkListHidden') fail('baseline is invalid') +if (report.caseCount !== 2 || report.cases?.length !== 2 || !isDeepStrictEqual(report.bindingBoundary?.oracleDefault, []) || report.bindingBoundary.safeFirstRead !== 'assign-empty-list-before-read') fail('case inventory or first-read boundary changed') +const expectedHosts = new Map([ + ['Sketcher::SketchObject', 'SketchProbe'], + ['Sketcher::SketchObjectPython', 'SketchPythonProbe'], +]) +const expectedValues = { + explicitEmpty: [], + single: ['ExportA'], + ordered: ['ExportB', 'ExportA'], + duplicates: ['ExportA', 'ExportB', 'ExportA'], + restored: [], +} +const expectedObjects = (ownerName, ownerTypeId) => [ + { name: ownerName, typeId: ownerTypeId }, + { name: 'ExportA', typeId: 'Part::Feature' }, + { name: 'ExportB', typeId: 'Part::Feature' }, +] +for (const entry of report.cases) { + const objectName = expectedHosts.get(entry.objectTypeId) + if (!objectName || entry.objectName !== objectName || entry.propertyName !== 'Exports' || entry.group !== 'Sketch' || entry.mode !== 'direct-native-property-setter') fail(`${entry.objectTypeId} identity changed`) + const baselineTargets = entry.phases?.explicitEmpty?.targetShapes + if (baselineTargets?.ExportA?.isNull !== false || baselineTargets.ExportA.solids !== 1 || baselineTargets.ExportA.faces !== 6 || baselineTargets.ExportA.edges !== 12 || baselineTargets.ExportA.vertices !== 8 || typeof baselineTargets.ExportA.brepSha256 !== 'string') fail(`${entry.objectTypeId} box target is invalid`) + if (baselineTargets?.ExportB?.isNull !== false || baselineTargets.ExportB.solids !== 1 || baselineTargets.ExportB.faces !== 3 || baselineTargets.ExportB.edges !== 3 || baselineTargets.ExportB.vertices !== 2 || typeof baselineTargets.ExportB.brepSha256 !== 'string') fail(`${entry.objectTypeId} cylinder target is invalid`) + for (const [phase, expectedValue] of Object.entries(expectedValues)) { + const snapshot = entry.phases?.[phase] + if (!isDeepStrictEqual(snapshot?.value, expectedValue) || snapshot.propertyTypeId !== 'App::PropertyLinkListHidden' || snapshot.group !== 'Sketch' || !isDeepStrictEqual(snapshot.propertyStatus, ['26']) || !isDeepStrictEqual(snapshot.editorMode, ['Hidden'])) fail(`${entry.objectTypeId} ${phase} property semantics changed`) + if (!isDeepStrictEqual(snapshot.ownerState, ['Up-to-date']) || snapshot.statusString !== 'Valid' || snapshot.recomputeResult !== true || snapshot.shape?.applicable !== true || snapshot.shape.isNull !== true || snapshot.shape.solids !== 0 || snapshot.shape.faces !== 0 || snapshot.shape.edges !== 0 || snapshot.shape.vertices !== 0 || snapshot.shape.brepSha256 !== null) fail(`${entry.objectTypeId} ${phase} host state changed`) + if (!isDeepStrictEqual(snapshot.ownerOutList, []) || !isDeepStrictEqual(snapshot.targetInLists, { ExportA: [], ExportB: [] })) fail(`${entry.objectTypeId} ${phase} leaked a hidden dependency or backlink`) + if (!isDeepStrictEqual(snapshot.targetShapes, baselineTargets)) fail(`${entry.objectTypeId} ${phase} changed a target Shape`) + if (!isDeepStrictEqual(snapshot.objectSet, expectedObjects(objectName, entry.objectTypeId))) fail(`${entry.objectTypeId} ${phase} changed the document object set`) + } +} +console.log(JSON.stringify({ status: 'freecad-property-linklisthidden-success-pass', cases: report.caseCount, phases: Object.keys(expectedValues), orderedValue: expectedValues.ordered, duplicateValue: expectedValues.duplicates, hiddenDependency: { ownerOutList: [], targetInLists: { ExportA: [], ExportB: [] } } }, null, 2)) diff --git a/scripts/check-freecad-property-linksubhidden-failure.mjs b/scripts/check-freecad-property-linksubhidden-failure.mjs new file mode 100644 index 0000000..85f85fe --- /dev/null +++ b/scripts/check-freecad-property-linksubhidden-failure.mjs @@ -0,0 +1,57 @@ +import { isDeepStrictEqual } from 'node:util' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const report = JSON.parse(await readFile(resolve(root, 'config/freecad-property-linksubhidden-failure.json'), 'utf8')) +const fail = (message) => { throw new Error(`FreeCAD PropertyLinkSubHidden failure check failed: ${message}`) } +if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baselineId !== 'freecad-1.1.1-property-linksubhidden-failure' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.propertyType !== 'App::PropertyLinkSubHidden' || report.caseCount !== 1) fail('baseline is invalid') + +const entry = report.case +const expectedObjects = [ + { name: 'LinkGroupProbe', typeId: 'App::LinkGroup' }, + { name: 'ColorSourceA', typeId: 'Part::Feature' }, + { name: 'ColorSourceB', typeId: 'Part::Feature' }, +] +if (entry?.objectTypeId !== 'App::LinkGroup' || entry.objectName !== 'LinkGroupProbe' || entry.propertyName !== 'ColoredElements' || entry.propertyTypeId !== 'App::PropertyLinkSubHidden' || !isDeepStrictEqual(entry.baseline, { object: 'ColorSourceA', subElements: ['Face1', 'Face3'] })) fail('native case identity changed') + +const initial = entry.initial +if (!isDeepStrictEqual(initial?.value, entry.baseline) || initial.propertyTypeId !== 'App::PropertyLinkSubHidden' || !isDeepStrictEqual(initial.propertyStatus, ['LockDynamic', '26']) || !isDeepStrictEqual(initial.editorMode, ['Hidden']) || !isDeepStrictEqual(initial.ownerState, ['Up-to-date']) || initial.statusString !== 'Valid' || !isDeepStrictEqual(initial.ownerOutList, []) || !isDeepStrictEqual(initial.targetInLists, { ColorSourceA: [], ColorSourceB: [] }) || !isDeepStrictEqual(initial.objectSet, expectedObjects) || typeof initial.targetShapeHashes?.ColorSourceA !== 'string' || typeof initial.targetShapeHashes?.ColorSourceB !== 'string') fail('initial hidden sub-link state is invalid') + +const assertBoundary = (snapshot, label) => { + if (snapshot?.propertyTypeId !== 'App::PropertyLinkSubHidden' || !isDeepStrictEqual(snapshot.ownerOutList, []) || !isDeepStrictEqual(snapshot.targetInLists, { ColorSourceA: [], ColorSourceB: [] }) || !isDeepStrictEqual(snapshot.targetShapeHashes, initial.targetShapeHashes) || !isDeepStrictEqual(snapshot.objectSet, expectedObjects)) fail(`${label} polluted hidden dependencies, Shapes, or objects`) +} + +const failureNames = new Set(entry.failures?.map(({ name }) => name)) +if (!['scalar', 'wrongSubList', 'unknownTuple'].every((name) => failureNames.has(name)) || entry.failures.length !== 3) fail('invalid input case inventory changed') +const scalar = entry.failures.find(({ name }) => name === 'scalar') +if (scalar?.accepted !== false || scalar.error?.type !== 'TypeError' || !/DocumentObject|NoneType/.test(scalar.error.message) || scalar.valuePreserved !== true || scalar.documentPreserved !== true || !isDeepStrictEqual(scalar.before, initial) || !isDeepStrictEqual(scalar.after, initial)) fail('scalar input was not rejected atomically') +const wrongSubList = entry.failures.find(({ name }) => name === 'wrongSubList') +if (wrongSubList?.accepted !== true || wrongSubList.error !== null || wrongSubList.valuePreserved !== false || !isDeepStrictEqual(wrongSubList.after.value, { object: 'ColorSourceA', subElements: ['Face1'] }) || wrongSubList.after.ownerState?.join(',') !== 'Touched') fail('tuple shorthand boundary changed') +const unknownTuple = entry.failures.find(({ name }) => name === 'unknownTuple') +if (unknownTuple?.accepted !== true || unknownTuple.error !== null || unknownTuple.valuePreserved !== false || !isDeepStrictEqual(unknownTuple.after.value, { object: 'ColorSourceA', subElements: ['MissingFace'] }) || unknownTuple.after.ownerState?.join(',') !== 'Touched') fail('unknown sub-element reference boundary changed') +for (const [label, snapshot] of [['scalar before', scalar.before], ['scalar after', scalar.after], ['wrongSubList before', wrongSubList.before], ['wrongSubList after', wrongSubList.after], ['unknownTuple before', unknownTuple.before], ['unknownTuple after', unknownTuple.after]]) assertBoundary(snapshot, label) + +if (entry.crossDocument?.accepted !== false || entry.crossDocument.error?.type !== 'ValueError' || entry.crossDocument.error.message !== 'PropertyLinkSub does not support external object' || entry.crossDocument.valuePreserved !== true || entry.crossDocument.documentPreserved !== true || !isDeepStrictEqual(entry.crossDocument.before, initial) || !isDeepStrictEqual(entry.crossDocument.after, initial)) fail('cross-document input boundary changed') +assertBoundary(entry.crossDocument.before, 'cross-document before') +assertBoundary(entry.crossDocument.after, 'cross-document after') + +const editor = entry.readOnly +if (editor?.accepted !== true || editor.error !== null || editor.pythonBypassesEditorReadOnly !== true || !isDeepStrictEqual(editor.before.value, entry.baseline) || !isDeepStrictEqual(editor.after.value, { object: 'ColorSourceB', subElements: ['Face2'] }) || !isDeepStrictEqual(editor.after.propertyStatus, ['ReadOnly', 'LockDynamic', '26']) || !isDeepStrictEqual(editor.after.editorMode, ['ReadOnly', 'Hidden']) || !isDeepStrictEqual(editor.after.ownerState, ['Touched'])) fail('ReadOnly Python acceptance boundary changed') +assertBoundary(editor.before, 'ReadOnly before') +assertBoundary(editor.after, 'ReadOnly after') + +const immutable = entry.immutable +if (immutable?.accepted !== false || immutable.error?.type !== 'AttributeError' || immutable.error.message !== "Object attribute 'ColoredElements' is read-only" || immutable.pythonBypassesImmutable !== false || !isDeepStrictEqual(immutable.before.value, entry.baseline) || !isDeepStrictEqual(immutable.after.value, entry.baseline) || !isDeepStrictEqual(immutable.before.propertyStatus, ['Immutable', 'LockDynamic', '26']) || !isDeepStrictEqual(immutable.after.propertyStatus, ['Immutable', 'LockDynamic', '26']) || !isDeepStrictEqual(immutable.after.ownerState, ['Up-to-date'])) fail('Immutable Python rejection boundary changed') +assertBoundary(immutable.before, 'Immutable before') +assertBoundary(immutable.after, 'Immutable after') + +const transaction = entry.transaction +if (transaction?.undoMode !== 1 || transaction.pendingAfterEdit !== true || transaction.pendingAfterAbort !== false || transaction.activeAfterEdit?.name !== 'property-linksubhidden-cancel' || !(transaction.activeAfterEdit.id > 0) || transaction.activeAfterAbort?.name !== '' || transaction.activeAfterAbort.id !== 0 || !isDeepStrictEqual(transaction.before?.value, entry.baseline) || !isDeepStrictEqual(transaction.edited?.value, { object: 'ColorSourceB', subElements: ['Face5'] }) || !isDeepStrictEqual(transaction.afterAbortBeforeRecompute?.value, entry.baseline) || !isDeepStrictEqual(transaction.afterAbortBeforeRecompute.ownerState, ['Touched']) || transaction.recomputeAfterAbort !== true || !isDeepStrictEqual(transaction.afterAbort?.ownerState, ['Up-to-date']) || transaction.afterAbort.statusString !== 'Valid' || transaction.restored !== true) fail('transaction abort and recompute recovery changed') +for (const [name, snapshot] of Object.entries({ before: transaction.before, edited: transaction.edited, afterAbortBeforeRecompute: transaction.afterAbortBeforeRecompute, afterAbort: transaction.afterAbort })) assertBoundary(snapshot, `transaction ${name}`) + +if (report.disabled?.classification !== 'editor-read-only-python-mutable-until-immutable' || report.disabled.readOnlySetter !== 'accepted' || report.disabled.immutableSetter !== 'rejected') fail('disabled state boundary is incomplete') +if (report.cancellationBoundary?.supported !== false || report.cancellationBoundary.classification !== 'synchronous-property-setter' || report.cancellationBoundary.reason !== 'no-native-cancel-hook' || report.cancellationBoundary.replacement !== 'abort-active-document-transaction') fail('cancellation boundary is incomplete') +if (entry.documentIntegrity?.objectsPreserved !== true || !isDeepStrictEqual(entry.documentIntegrity.initialObjects, expectedObjects) || !isDeepStrictEqual(entry.documentIntegrity.finalObjects, expectedObjects) || !isDeepStrictEqual(entry.documentIntegrity.foreignDocumentObjects, [{ name: 'ExternalSource', typeId: 'Part::Feature' }])) fail('failure cases polluted either document') + +console.log(JSON.stringify({ status: 'freecad-property-linksubhidden-failure-pass', invalidCases: [...failureNames], crossDocument: entry.crossDocument, readOnlyPythonAccepted: editor.pythonBypassesEditorReadOnly, immutableRejected: !immutable.pythonBypassesImmutable, transactionRestored: transaction.restored, cancellationBoundary: report.cancellationBoundary }, null, 2)) diff --git a/scripts/check-freecad-property-linksubhidden-inventory.mjs b/scripts/check-freecad-property-linksubhidden-inventory.mjs new file mode 100644 index 0000000..3f44de2 --- /dev/null +++ b/scripts/check-freecad-property-linksubhidden-inventory.mjs @@ -0,0 +1,36 @@ +import { createHash } from 'node:crypto' +import { isDeepStrictEqual } from 'node:util' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const runtimePath = '.cache/freecad/reference-desktop.json' +const [runtimeContent, reportContent] = await Promise.all([readFile(resolve(root, runtimePath)), readFile(resolve(root, 'config/freecad-property-linksubhidden-inventory.json'))]) +const runtime = JSON.parse(runtimeContent) +const report = JSON.parse(reportContent) +const fail = (message) => { throw new Error(`FreeCAD PropertyLinkSubHidden inventory check failed: ${message}`) } +if (report.schemaVersion !== 1 || report.status !== 'pass' || report.propertyType !== 'App::PropertyLinkSubHidden' || report.classification !== 'opaque-fcstd-proxy' || report.baseline?.freecadVersion !== '1.1.1' || report.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('report boundary is invalid') +if (report.recordCount !== 4 || report.objectTypeCount !== 4 || report.writableRecordCount !== 4 || report.records?.length !== 4) fail('runtime record or host count changed') +const contract = report.typeContract +if (contract?.inheritedFrom !== 'App::PropertyLinkSub' || contract.linkScope !== 'Hidden' || contract.propertyStatus !== 'PropHidden' || contract.dynamicStatus !== 'LockDynamic' || contract.xmlElement !== 'LinkSub' || contract.xmlChildElement !== 'Sub' || contract.legacyRestoreType !== 'App::PropertyLinkSub' || contract.emptySubListMeans !== 'whole-object-reference') fail('type contract is incomplete') +if (report.provenance?.runtime?.path !== runtimePath || report.provenance.runtime.bytes !== runtimeContent.length || report.provenance.runtime.sha256 !== createHash('sha256').update(runtimeContent).digest('hex')) fail('runtime provenance is stale') +for (const locked of report.provenance?.sources ?? []) { const content = await readFile(resolve(root, locked.path)); if (content.length !== locked.bytes || createHash('sha256').update(content).digest('hex') !== locked.sha256) fail(`source provenance is stale for ${locked.path}`) } +const nativeMatches = [] +for (const objectType of runtime.runtimeObjects?.types ?? []) for (const property of objectType.properties ?? []) if (property.typeId === 'App::PropertyLinkSubHidden') nativeMatches.push({ objectTypeId: objectType.typeId, objectAvailable: objectType.available === true, probeStatus: objectType.probeStatus, propertyName: property.name, group: property.group, statusRaw: property.status, defaultRaw: property.default }) +const identity = ({ objectTypeId, objectAvailable, probeStatus, propertyName, group, statusRaw, defaultRaw }) => ({ objectTypeId, objectAvailable, probeStatus, propertyName, group, statusRaw, defaultRaw }) +const sorted = (records) => records.map(identity).sort((left, right) => `${left.objectTypeId}.${left.propertyName}`.localeCompare(`${right.objectTypeId}.${right.propertyName}`)) +if (!isDeepStrictEqual(sorted(nativeMatches), sorted(report.records))) fail('report diverges from the locked runtime oracle') +const expectedKeys = ['App::Link.ColoredElements', 'App::LinkGroup.ColoredElements', 'App::LinkGroupPython.ColoredElements', 'App::LinkPython.ColoredElements'] +if (!isDeepStrictEqual(report.records.map(({ objectTypeId, propertyName }) => `${objectTypeId}.${propertyName}`), expectedKeys)) fail('native host/property identities changed') +for (const record of report.records) { + if (record.objectAvailable !== true || record.probeStatus !== 'available' || record.group !== ' Link' || !isDeepStrictEqual(record.statusRaw, ['LockDynamic', 26]) || !isDeepStrictEqual(record.statusNames, ['LockDynamic', 'PropHidden']) || record.defaultRaw !== null) fail(`native metadata changed for ${record.objectTypeId}.ColoredElements`) + const model = record.valueModel; const inputs = record.inputs; const dependencies = record.dependencies + if (model?.kind !== 'hidden-document-object-sub-element-reference' || model.inheritedFrom !== 'App::PropertyLinkSub' || model.elementType !== 'same-document App::DocumentObject plus ordered sub-element names' || model.orderPreserved !== true || model.duplicatesPreserved !== false || model.writable !== true) fail(`value model changed for ${record.objectTypeId}.ColoredElements`) + if (inputs?.nativeCppSetValues !== 'attached same-document DocumentObject plus sub-element string list' || inputs.emptySubListMeansWholeObject !== true || inputs.nullObjectClears !== true || inputs.detachedObjectRejected !== true || inputs.crossDocumentRejectedWithoutLinkAllowExternal !== true || inputs.invalidSubElementAcceptedAsReference !== true) fail(`input preconditions changed for ${record.objectTypeId}.ColoredElements`) + if (dependencies?.linkScope !== 'Hidden' || dependencies.createsBacklinks !== false || dependencies.includedByDefaultGetLinks !== false || dependencies.includedByGetLinksAll !== true || dependencies.defaultDagDependency !== false) fail(`hidden dependency scope changed for ${record.objectTypeId}.ColoredElements`) + if (record.applicability?.requiredObjectTypeId !== record.objectTypeId || record.applicability.role !== 'Link colored element override lookup' || record.applicability.propertyWriteRequiresShape !== false) fail(`applicability changed for ${record.objectTypeId}.ColoredElements`) +} +const sources = Object.fromEntries(await Promise.all(report.provenance.sources.map(async ({ path }) => [path, await readFile(resolve(root, path), 'utf8')]))) +const linksHeader = sources['.cache/freecad/FreeCAD/src/App/PropertyLinks.h']; const linksSource = sources['.cache/freecad/FreeCAD/src/App/PropertyLinks.cpp']; const linkHeader = sources['.cache/freecad/FreeCAD/src/App/Link.h']; const draftSource = sources['.cache/freecad/FreeCAD/src/Mod/Draft/draftobjects/draftlink.py'] +if (!linksHeader.includes('class AppExport PropertyLinkSubHidden: public PropertyLinkSub') || !linksHeader.includes('_pcScope = LinkScope::Hidden;') || !linksSource.includes('TYPESYSTEM_SOURCE(App::PropertyLinkSubHidden, App::PropertyLinkSub)') || !linksSource.includes('if (all || _pcScope != LinkScope::Hidden)') || !linksSource.includes(' objectTypeId), propertyStatus: contract.propertyStatus, dynamicStatus: contract.dynamicStatus, linkScope: contract.linkScope, classification: report.classification }, null, 2)) diff --git a/scripts/check-freecad-property-linksubhidden-success.mjs b/scripts/check-freecad-property-linksubhidden-success.mjs new file mode 100644 index 0000000..279f8b1 --- /dev/null +++ b/scripts/check-freecad-property-linksubhidden-success.mjs @@ -0,0 +1,29 @@ +import { isDeepStrictEqual } from 'node:util' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const report = JSON.parse(await readFile(resolve(root, 'config/freecad-property-linksubhidden-success.json'), 'utf8')) +const fail = (message) => { throw new Error(`FreeCAD PropertyLinkSubHidden success check failed: ${message}`) } +if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baselineId !== 'freecad-1.1.1-property-linksubhidden-success' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.propertyType !== 'App::PropertyLinkSubHidden') fail('baseline is invalid') +if (report.caseCount !== 4 || report.cases?.length !== 4 || report.bindingBoundary?.oracleDefault !== null || report.bindingBoundary.safeFirstRead !== 'assign-null-object-before-read' || report.bindingBoundary.emptySubListMeans !== 'whole-object-reference') fail('case inventory or first-read boundary changed') +const expectedHosts = new Map([['App::Link', { name: 'LinkProbe', ordinaryOutList: ['ColorSourceA'], targetInLists: { ColorSourceA: ['LinkProbe'], ColorSourceB: [] } }], ['App::LinkGroup', { name: 'LinkGroupProbe', ordinaryOutList: [], targetInLists: { ColorSourceA: [], ColorSourceB: [] } }], ['App::LinkGroupPython', { name: 'LinkGroupPythonProbe', ordinaryOutList: [], targetInLists: { ColorSourceA: [], ColorSourceB: [] } }], ['App::LinkPython', { name: 'LinkPythonProbe', ordinaryOutList: ['ColorSourceA'], targetInLists: { ColorSourceA: ['LinkPythonProbe'], ColorSourceB: [] } }]]) +const expectedValues = { ordered: { object: 'ColorSourceA', subElements: ['Face1', 'Face3'] }, duplicates: { object: 'ColorSourceA', subElements: ['Face6', 'Face1', 'Face6'] }, wholeObject: { object: 'ColorSourceA', subElements: [] }, explicitNull: null } +const expectedObjects = (ownerName, ownerTypeId) => [{ name: ownerName, typeId: ownerTypeId }, { name: 'ColorSourceA', typeId: 'Part::Feature' }, { name: 'ColorSourceB', typeId: 'Part::Feature' }] +for (const entry of report.cases) { + const host = expectedHosts.get(entry.objectTypeId) + const objectName = host?.name + if (!objectName || entry.objectName !== objectName || entry.propertyName !== 'ColoredElements' || entry.group !== ' Link' || entry.mode !== 'direct-native-property-setter') fail(`${entry.objectTypeId} identity changed`) + const baselineTargets = entry.phases?.ordered?.targetShapes + if (baselineTargets?.ColorSourceA?.isNull !== false || baselineTargets.ColorSourceA.solids !== 1 || baselineTargets.ColorSourceA.faces !== 6 || baselineTargets.ColorSourceA.edges !== 12 || baselineTargets.ColorSourceA.vertices !== 8 || typeof baselineTargets.ColorSourceA.brepSha256 !== 'string') fail(`${entry.objectTypeId} box target is invalid`) + if (baselineTargets?.ColorSourceB?.isNull !== false || baselineTargets.ColorSourceB.solids !== 1 || baselineTargets.ColorSourceB.faces !== 3 || baselineTargets.ColorSourceB.edges !== 3 || baselineTargets.ColorSourceB.vertices !== 2 || typeof baselineTargets.ColorSourceB.brepSha256 !== 'string') fail(`${entry.objectTypeId} cylinder target is invalid`) + for (const [phase, expectedValue] of Object.entries(expectedValues)) { + const snapshot = entry.phases?.[phase] + if (!isDeepStrictEqual(snapshot?.value, expectedValue) || snapshot.propertyTypeId !== 'App::PropertyLinkSubHidden' || snapshot.group !== ' Link' || !isDeepStrictEqual(snapshot.propertyStatus, ['LockDynamic', '26']) || !isDeepStrictEqual(snapshot.editorMode, ['Hidden'])) fail(`${entry.objectTypeId} ${phase} property semantics changed`) + if (!isDeepStrictEqual(snapshot.ownerState, ['Up-to-date']) || snapshot.statusString !== 'Valid' || snapshot.recomputeResult !== true || snapshot.shape?.applicable !== false) fail(`${entry.objectTypeId} ${phase} host state changed`) + if (!isDeepStrictEqual(snapshot.ownerOutList, host.ordinaryOutList) || !isDeepStrictEqual(snapshot.targetInLists, host.targetInLists)) fail(`${entry.objectTypeId} ${phase} leaked a hidden dependency or backlink`) + if (!isDeepStrictEqual(snapshot.targetShapes, baselineTargets)) fail(`${entry.objectTypeId} ${phase} changed a target Shape`) + if (!isDeepStrictEqual(snapshot.objectSet, expectedObjects(objectName, entry.objectTypeId))) fail(`${entry.objectTypeId} ${phase} changed the document object set`) + } +} +console.log(JSON.stringify({ status: 'freecad-property-linksubhidden-success-pass', cases: report.caseCount, phases: Object.keys(expectedValues), orderedValue: expectedValues.ordered, duplicateValue: expectedValues.duplicates, wholeObjectValue: expectedValues.wholeObject, hiddenDependency: { ownerOutList: [], targetInLists: { ColorSourceA: [], ColorSourceB: [] } } }, null, 2)) diff --git a/scripts/freecad-property-linklisthidden-failure.py b/scripts/freecad-property-linklisthidden-failure.py new file mode 100644 index 0000000..4bd0474 --- /dev/null +++ b/scripts/freecad-property-linklisthidden-failure.py @@ -0,0 +1,185 @@ +import hashlib +import json +import os + +import FreeCAD as App +import Part + + +FREECAD_COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" +OUTPUT_PATH = os.environ.get("FREECAD_PROPERTY_LINKLISTHIDDEN_FAILURE_OUTPUT", "") + + +def shape_hash(obj): + shape = obj.Shape + if shape.isNull(): + return None + return hashlib.sha256(shape.exportBrepToString().encode("utf-8")).hexdigest() + + +def object_set(document): + return [{"name": obj.Name, "typeId": obj.TypeId} for obj in document.Objects] + + +def snapshot(document, owner, targets): + return { + "value": [candidate.Name for candidate in owner.Exports], + "propertyTypeId": owner.getTypeIdOfProperty("Exports"), + "propertyStatus": [str(item) for item in owner.getPropertyStatus("Exports")], + "editorMode": [str(item) for item in owner.getEditorMode("Exports")], + "ownerState": [str(item) for item in owner.State], + "statusString": str(owner.getStatusString()), + "ownerShapeNull": bool(owner.Shape.isNull()), + "ownerOutList": [candidate.Name for candidate in owner.OutList], + "targetInLists": {target.Name: [candidate.Name for candidate in target.InList] for target in targets}, + "targetShapeHashes": {target.Name: shape_hash(target) for target in targets}, + "objectSet": object_set(document), + } + + +def exception_snapshot(callback): + try: + callback() + except Exception as error: + return {"type": type(error).__name__, "message": str(error)} + return None + + +def active_transaction_snapshot(): + active = App.getActiveTransaction() + return {"name": str(active[0]), "id": int(active[1])} if active else {"name": "", "id": 0} + + +def run(): + document = App.newDocument("PropertyLinkListHiddenFailure") + foreign_document = App.newDocument("PropertyLinkListHiddenForeign") + try: + owner = document.addObject("Sketcher::SketchObject", "SketchProbe") + first = document.addObject("Part::Feature", "ExportA") + first.Shape = Part.makeBox(2.0, 3.0, 4.0) + second = document.addObject("Part::Feature", "ExportB") + second.Shape = Part.makeCylinder(1.5, 5.0) + targets = [first, second] + foreign = foreign_document.addObject("Part::Feature", "ForeignExport") + foreign.Shape = Part.makeBox(1.0, 1.0, 1.0) + owner.Exports = [first, second] + document.recompute() + document.UndoMode = 1 + initial = snapshot(document, owner, targets) + failures = [] + cases = [ + ("string-scalar", "ExportA"), + ("string-item", ["ExportA"]), + ("integer-scalar", 7), + ("integer-item", [7]), + ("cross-document", [foreign]), + ] + for case_id, requested in cases: + before = snapshot(document, owner, targets) + exception = exception_snapshot(lambda value=requested: setattr(owner, "Exports", value)) + after = snapshot(document, owner, targets) + failures.append({ + "id": case_id, + "requested": repr(requested), + "exception": exception, + "before": before, + "after": after, + "valuePreserved": before["value"] == after["value"], + "documentPreserved": before == after, + }) + + editor_before = snapshot(document, owner, targets) + owner.setEditorMode("Exports", ["ReadOnly"]) + editor_mode = [str(item) for item in owner.getEditorMode("Exports")] + editor_exception = exception_snapshot(lambda: setattr(owner, "Exports", [second])) + editor_after = snapshot(document, owner, targets) + owner.setEditorMode("Exports", 0) + editor_restored = [str(item) for item in owner.getEditorMode("Exports")] + + owner.Exports = [first, second] + document.recompute() + owner.setPropertyStatus("Exports", "Immutable") + immutable_before = snapshot(document, owner, targets) + immutable_exception = exception_snapshot(lambda: setattr(owner, "Exports", [second])) + immutable_after = snapshot(document, owner, targets) + owner.setPropertyStatus("Exports", "-Immutable") + immutable_restored_status = [str(item) for item in owner.getPropertyStatus("Exports")] + + owner.Exports = [first, second] + document.recompute() + document.openTransaction("property-linklisthidden-cancel") + transaction_before = snapshot(document, owner, targets) + owner.Exports = [second, first, second] + transaction_edited = snapshot(document, owner, targets) + pending_after_edit = bool(document.HasPendingTransaction) + active_after_edit = active_transaction_snapshot() + document.abortTransaction() + transaction_after_abort_before_recompute = snapshot(document, owner, targets) + recompute_after_abort = bool(document.recompute()) + transaction_after = snapshot(document, owner, targets) + return { + "schemaVersion": 1, + "status": "pass", + "baselineId": "freecad-1.1.1-property-linklisthidden-failure", + "freecadVersion": ".".join(str(value) for value in App.Version()[:3]), + "gitCommit": FREECAD_COMMIT, + "propertyType": "App::PropertyLinkListHidden", + "object": {"name": owner.Name, "typeId": owner.TypeId}, + "property": {"name": "Exports", "typeId": owner.getTypeIdOfProperty("Exports")}, + "initial": initial, + "failures": failures, + "editorReadOnly": { + "classification": "editor-read-only-python-setter-accepted", + "before": editor_before, + "editorMode": editor_mode, + "exception": editor_exception, + "after": editor_after, + "pythonBypassesEditorReadOnly": editor_exception is None, + "restoredEditorMode": editor_restored, + }, + "immutable": { + "classification": "immutable-python-setter-accepted", + "before": immutable_before, + "exception": immutable_exception, + "after": immutable_after, + "pythonBypassesImmutable": immutable_exception is None, + "restoredStatus": immutable_restored_status, + }, + "transaction": { + "before": transaction_before, + "edited": transaction_edited, + "afterAbortBeforeRecompute": transaction_after_abort_before_recompute, + "afterAbort": transaction_after, + "recomputeAfterAbort": recompute_after_abort, + "undoMode": int(document.UndoMode), + "pendingAfterEdit": pending_after_edit, + "pendingAfterAbort": bool(document.HasPendingTransaction), + "activeAfterEdit": active_after_edit, + "activeAfterAbort": active_transaction_snapshot(), + "restored": transaction_before == transaction_after, + }, + "cancellationBoundary": { + "supported": False, + "classification": "synchronous-property-setter", + "reason": "no-native-cancel-hook", + "replacement": "abort-active-document-transaction", + }, + "documentIntegrity": { + "initialObjects": initial["objectSet"], + "finalObjects": object_set(document), + "objectsPreserved": initial["objectSet"] == object_set(document), + "foreignDocumentObjects": object_set(foreign_document), + }, + } + finally: + App.closeDocument(foreign_document.Name) + App.closeDocument(document.Name) + + +report = run() +if not OUTPUT_PATH: + raise RuntimeError("FREECAD_PROPERTY_LINKLISTHIDDEN_FAILURE_OUTPUT is required") +with open(OUTPUT_PATH, "w", encoding="utf-8") as handle: + json.dump(report, handle, indent=2, sort_keys=True) + handle.write("\n") +print("FREECAD_PROPERTY_LINKLISTHIDDEN_FAILURE_RESULT=" + json.dumps({"status": report["status"], "failureCount": len(report["failures"])}, sort_keys=True)) diff --git a/scripts/freecad-property-linklisthidden-mutation.py b/scripts/freecad-property-linklisthidden-mutation.py new file mode 100644 index 0000000..d847760 --- /dev/null +++ b/scripts/freecad-property-linklisthidden-mutation.py @@ -0,0 +1,103 @@ +import hashlib +import json +import os + +import FreeCAD as App +import Part + + +FREECAD_COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" +OUTPUT_PATH = os.environ.get("FREECAD_PROPERTY_LINKLISTHIDDEN_MUTATION_OUTPUT", "") + + +def shape_snapshot(obj): + shape = obj.Shape + if shape.isNull(): + return {"isNull": True, "solids": 0, "faces": 0, "edges": 0, "vertices": 0, "brepSha256": None} + return { + "isNull": False, + "solids": len(shape.Solids), + "faces": len(shape.Faces), + "edges": len(shape.Edges), + "vertices": len(shape.Vertexes), + "brepSha256": hashlib.sha256(shape.exportBrepToString().encode("utf-8")).hexdigest(), + } + + +def snapshot(document, owner, targets): + return { + "value": [candidate.Name for candidate in owner.Exports], + "propertyTypeId": owner.getTypeIdOfProperty("Exports"), + "group": owner.getGroupOfProperty("Exports"), + "propertyStatus": [str(item) for item in owner.getPropertyStatus("Exports")], + "editorMode": [str(item) for item in owner.getEditorMode("Exports")], + "ownerState": [str(item) for item in owner.State], + "statusString": str(owner.getStatusString()), + "ownerShape": shape_snapshot(owner), + "ownerOutList": [candidate.Name for candidate in owner.OutList], + "targetInLists": {target.Name: [candidate.Name for candidate in target.InList] for target in targets}, + "targetShapes": {target.Name: shape_snapshot(target) for target in targets}, + "objectSet": [{"name": candidate.Name, "typeId": candidate.TypeId} for candidate in document.Objects], + } + + +def run_case(object_type_id, object_name): + document = App.newDocument("PropertyLinkListHiddenMutation" + object_name) + try: + owner = document.addObject(object_type_id, object_name) + first = document.addObject("Part::Feature", "ExportA") + first.Shape = Part.makeBox(2.0, 3.0, 4.0) + second = document.addObject("Part::Feature", "ExportB") + second.Shape = Part.makeCylinder(1.5, 5.0) + targets = [first, second] + owner.Exports = [first, second] + document.recompute() + before = snapshot(document, owner, targets) + + owner.Exports = [second, first, second] + touched_after_edit = snapshot(document, owner, targets) + edit_recompute_result = bool(document.recompute()) + edited = snapshot(document, owner, targets) + + owner.Exports = [first, second] + touched_after_restore = snapshot(document, owner, targets) + restore_recompute_result = bool(document.recompute()) + restored = snapshot(document, owner, targets) + return { + "objectTypeId": object_type_id, + "objectName": object_name, + "propertyName": "Exports", + "before": before, + "touchedAfterEdit": touched_after_edit, + "edited": edited, + "touchedAfterRestore": touched_after_restore, + "restored": restored, + "editRecomputeResult": edit_recompute_result, + "restoreRecomputeResult": restore_recompute_result, + "valueRestored": before["value"] == restored["value"], + "semanticStateRestored": before == restored, + } + finally: + App.closeDocument(document.Name) + + +cases = [ + run_case("Sketcher::SketchObject", "SketchProbe"), + run_case("Sketcher::SketchObjectPython", "SketchPythonProbe"), +] +report = { + "schemaVersion": 1, + "status": "pass", + "baselineId": "freecad-1.1.1-property-linklisthidden-mutation", + "freecadVersion": ".".join(str(value) for value in App.Version()[:3]), + "gitCommit": FREECAD_COMMIT, + "propertyType": "App::PropertyLinkListHidden", + "caseCount": len(cases), + "cases": cases, +} +if not OUTPUT_PATH: + raise RuntimeError("FREECAD_PROPERTY_LINKLISTHIDDEN_MUTATION_OUTPUT is required") +with open(OUTPUT_PATH, "w", encoding="utf-8") as handle: + json.dump(report, handle, indent=2, sort_keys=True) + handle.write("\n") +print("FREECAD_PROPERTY_LINKLISTHIDDEN_MUTATION_RESULT=" + json.dumps({"status": report["status"], "caseCount": report["caseCount"]}, sort_keys=True)) diff --git a/scripts/freecad-property-linklisthidden-roundtrip.py b/scripts/freecad-property-linklisthidden-roundtrip.py new file mode 100644 index 0000000..c75d114 --- /dev/null +++ b/scripts/freecad-property-linklisthidden-roundtrip.py @@ -0,0 +1,108 @@ +import hashlib +import json +import os + +import FreeCAD as App +import Part + + +FREECAD_COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" + + +def shape_snapshot(obj): + shape = obj.Shape + if shape.isNull(): + return {"isNull": True, "valid": False, "shapeType": "Null", "solids": 0, "faces": 0, "edges": 0, "vertices": 0, "area": 0.0, "volume": 0.0, "brepSha256": None} + return { + "isNull": False, + "valid": bool(shape.isValid()), + "shapeType": shape.ShapeType, + "solids": len(shape.Solids), + "faces": len(shape.Faces), + "edges": len(shape.Edges), + "vertices": len(shape.Vertexes), + "area": round(float(shape.Area), 9), + "volume": round(float(shape.Volume), 9), + "brepSha256": hashlib.sha256(shape.exportBrepToString().encode("utf-8")).hexdigest(), + } + + +def object_snapshot(document): + owner = document.getObject("SketchProbe") + targets = [document.getObject("ExportA"), document.getObject("ExportB")] + return { + "objectSet": [{"name": item.Name, "typeId": item.TypeId} for item in document.Objects], + "object": { + "name": owner.Name, + "typeId": owner.TypeId, + "propertyName": "Exports", + "propertyTypeId": owner.getTypeIdOfProperty("Exports"), + "group": owner.getGroupOfProperty("Exports"), + "value": [item.Name for item in owner.Exports], + "propertyStatus": [str(item) for item in owner.getPropertyStatus("Exports")], + "editorMode": [str(item) for item in owner.getEditorMode("Exports")], + "state": [str(item) for item in owner.State], + "statusString": str(owner.getStatusString()), + "shape": shape_snapshot(owner), + "outList": [item.Name for item in owner.OutList], + }, + "targets": { + target.Name: { + "typeId": target.TypeId, + "inList": [item.Name for item in target.InList], + "shape": shape_snapshot(target), + } + for target in targets + }, + } + + +def create_native(path): + document = App.newDocument("PropertyLinkListHiddenRoundtrip") + try: + owner = document.addObject("Sketcher::SketchObject", "SketchProbe") + first = document.addObject("Part::Feature", "ExportA") + first.Shape = Part.makeBox(2.0, 3.0, 4.0) + second = document.addObject("Part::Feature", "ExportB") + second.Shape = Part.makeCylinder(1.5, 5.0) + owner.Exports = [first, second] + document.recompute() + document.saveAs(path) + finally: + App.closeDocument(document.Name) + reopened = App.openDocument(path) + try: + reopened.recompute() + return object_snapshot(reopened) + finally: + App.closeDocument(reopened.Name) + + +def verify_native(path, resaved_path): + document = App.openDocument(path) + try: + document.recompute() + reopened = object_snapshot(document) + document.saveAs(resaved_path) + finally: + App.closeDocument(document.Name) + resaved_document = App.openDocument(resaved_path) + try: + resaved_document.recompute() + resaved = object_snapshot(resaved_document) + finally: + App.closeDocument(resaved_document.Name) + return {"reopened": reopened, "resaved": resaved} + + +mode = os.environ.get("FREECAD_PROPERTY_LINKLISTHIDDEN_ROUNDTRIP_MODE", "") +path = os.environ.get("FREECAD_PROPERTY_LINKLISTHIDDEN_ROUNDTRIP_PATH", "") +resaved_path = os.environ.get("FREECAD_PROPERTY_LINKLISTHIDDEN_ROUNDTRIP_RESAVED_PATH", "") +if mode == "create": + result = create_native(path) +elif mode == "verify": + result = verify_native(path, resaved_path) +else: + raise RuntimeError("FREECAD_PROPERTY_LINKLISTHIDDEN_ROUNDTRIP_MODE must be create or verify") + +print("FREECAD_PROPERTY_LINKLISTHIDDEN_ROUNDTRIP_RESULT=" + json.dumps({"schemaVersion": 1, "status": "pass", "baselineId": "freecad-1.1.1-property-linklisthidden-roundtrip", "freecadVersion": ".".join(str(value) for value in App.Version()[:3]), "gitCommit": FREECAD_COMMIT, "mode": mode, "result": result}, sort_keys=True)) diff --git a/scripts/freecad-property-linklisthidden-success.py b/scripts/freecad-property-linklisthidden-success.py new file mode 100644 index 0000000..16d9cc8 --- /dev/null +++ b/scripts/freecad-property-linklisthidden-success.py @@ -0,0 +1,114 @@ +import hashlib +import json +import os + +import FreeCAD as App +import Part + + +FREECAD_COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" +OUTPUT_PATH = os.environ.get("FREECAD_PROPERTY_LINKLISTHIDDEN_SUCCESS_OUTPUT", "") + + +def shape_snapshot(obj): + if "Shape" not in obj.PropertiesList: + return {"applicable": False, "reason": "host-has-no-shape-property"} + shape = obj.Shape + if shape.isNull(): + return { + "applicable": True, + "isNull": True, + "solids": 0, + "faces": 0, + "edges": 0, + "vertices": 0, + "brepSha256": None, + } + brep = shape.exportBrepToString() + return { + "applicable": True, + "isNull": False, + "solids": len(shape.Solids), + "faces": len(shape.Faces), + "edges": len(shape.Edges), + "vertices": len(shape.Vertexes), + "brepSha256": hashlib.sha256(brep.encode("utf-8")).hexdigest(), + } + + +def snapshot(document, owner, targets): + recompute_result = bool(document.recompute()) + return { + "value": [candidate.Name for candidate in owner.Exports], + "propertyTypeId": owner.getTypeIdOfProperty("Exports"), + "group": owner.getGroupOfProperty("Exports"), + "propertyStatus": [str(item) for item in owner.getPropertyStatus("Exports")], + "editorMode": [str(item) for item in owner.getEditorMode("Exports")], + "ownerState": [str(item) for item in owner.State], + "statusString": str(owner.getStatusString()), + "recomputeResult": recompute_result, + "shape": shape_snapshot(owner), + "ownerOutList": [candidate.Name for candidate in owner.OutList], + "targetInLists": {target.Name: [candidate.Name for candidate in target.InList] for target in targets}, + "targetShapes": {target.Name: shape_snapshot(target) for target in targets}, + "objectSet": [{"name": candidate.Name, "typeId": candidate.TypeId} for candidate in document.Objects], + } + + +def run_case(object_type_id, object_name): + document = App.newDocument("PropertyLinkListHidden" + object_name) + try: + owner = document.addObject(object_type_id, object_name) + first = document.addObject("Part::Feature", "ExportA") + first.Shape = Part.makeBox(2.0, 3.0, 4.0) + second = document.addObject("Part::Feature", "ExportB") + second.Shape = Part.makeCylinder(1.5, 5.0) + targets = [first, second] + + owner.Exports = [] + phases = {"explicitEmpty": snapshot(document, owner, targets)} + assignments = { + "single": [first], + "ordered": [second, first], + "duplicates": [first, second, first], + "restored": [], + } + for phase, value in assignments.items(): + owner.Exports = value + phases[phase] = snapshot(document, owner, targets) + return { + "objectTypeId": object_type_id, + "objectName": object_name, + "propertyName": "Exports", + "group": "Sketch", + "mode": "direct-native-property-setter", + "phases": phases, + } + finally: + App.closeDocument(document.Name) + + +cases = [ + run_case("Sketcher::SketchObject", "SketchProbe"), + run_case("Sketcher::SketchObjectPython", "SketchPythonProbe"), +] +report = { + "schemaVersion": 1, + "status": "pass", + "baselineId": "freecad-1.1.1-property-linklisthidden-success", + "freecadVersion": ".".join(str(value) for value in App.Version()[:3]), + "gitCommit": FREECAD_COMMIT, + "propertyType": "App::PropertyLinkListHidden", + "caseCount": len(cases), + "bindingBoundary": { + "oracleDefault": [], + "safeFirstRead": "assign-empty-list-before-read", + }, + "cases": cases, +} +if not OUTPUT_PATH: + raise RuntimeError("FREECAD_PROPERTY_LINKLISTHIDDEN_SUCCESS_OUTPUT is required") +with open(OUTPUT_PATH, "w", encoding="utf-8") as handle: + json.dump(report, handle, indent=2, sort_keys=True) + handle.write("\n") +print("FREECAD_PROPERTY_LINKLISTHIDDEN_SUCCESS_RESULT=" + json.dumps({"status": report["status"], "caseCount": report["caseCount"]}, sort_keys=True)) diff --git a/scripts/freecad-property-linksubhidden-failure.py b/scripts/freecad-property-linksubhidden-failure.py new file mode 100644 index 0000000..9d33455 --- /dev/null +++ b/scripts/freecad-property-linksubhidden-failure.py @@ -0,0 +1,219 @@ +import hashlib +import json +import os + +import FreeCAD as App +import Part + + +FREECAD_COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" +OUTPUT_PATH = os.environ.get("FREECAD_PROPERTY_LINKSUBHIDDEN_FAILURE_OUTPUT", "") + + +def value_of(owner): + value = owner.ColoredElements + if value is None: + return None + return {"object": value[0].Name if value[0] else None, "subElements": list(value[1])} + + +def shape_hash(obj): + shape = obj.Shape + if shape.isNull(): + return None + return hashlib.sha256(shape.exportBrepToString().encode("utf-8")).hexdigest() + + +def object_set(document): + return [{"name": obj.Name, "typeId": obj.TypeId} for obj in document.Objects] + + +def snapshot(document, owner, targets): + return { + "value": value_of(owner), + "propertyTypeId": owner.getTypeIdOfProperty("ColoredElements"), + "propertyStatus": [str(item) for item in owner.getPropertyStatus("ColoredElements")], + "editorMode": [str(item) for item in owner.getEditorMode("ColoredElements")], + "ownerState": [str(item) for item in owner.State], + "statusString": str(owner.getStatusString()), + "ownerOutList": [candidate.Name for candidate in owner.OutList], + "targetInLists": {target.Name: [candidate.Name for candidate in target.InList] for target in targets}, + "targetShapeHashes": {target.Name: shape_hash(target) for target in targets}, + "objectSet": object_set(document), + } + + +def exception_snapshot(callback): + try: + callback() + except Exception as error: + return {"type": type(error).__name__, "message": str(error)} + return None + + +def active_transaction_snapshot(): + active = App.getActiveTransaction() + return {"name": str(active[0]), "id": int(active[1])} if active else {"name": "", "id": 0} + + +def run_case(): + document = App.newDocument("PropertyLinkSubHiddenFailure") + external = App.newDocument("PropertyLinkSubHiddenExternal") + try: + owner = document.addObject("App::LinkGroup", "LinkGroupProbe") + first = document.addObject("Part::Feature", "ColorSourceA") + first.Shape = Part.makeBox(2.0, 3.0, 4.0) + second = document.addObject("Part::Feature", "ColorSourceB") + second.Shape = Part.makeCylinder(1.5, 5.0) + other = external.addObject("Part::Feature", "ExternalSource") + other.Shape = Part.makeBox(1.0, 1.0, 1.0) + targets = [first, second] + + owner.ColoredElements = (first, ["Face1", "Face3"]) + document.recompute() + document.UndoMode = 1 + initial = snapshot(document, owner, targets) + + failures = [] + for name, candidate in [("scalar", "ColorSourceA"), ("wrongSubList", (first, "Face1")), ("unknownTuple", (first, ["MissingFace"]))]: + before = snapshot(document, owner, targets) + exception = exception_snapshot(lambda value=candidate: setattr(owner, "ColoredElements", value)) + after = snapshot(document, owner, targets) + failures.append({ + "name": name, + "requested": repr(candidate), + "accepted": exception is None, + "error": exception, + "before": before, + "after": after, + "valuePreserved": before["value"] == after["value"], + "documentPreserved": before == after, + }) + owner.ColoredElements = (first, ["Face1", "Face3"]) + document.recompute() + + owner.ColoredElements = (first, ["Face1", "Face3"]) + document.recompute() + before = snapshot(document, owner, targets) + exception = exception_snapshot(lambda: setattr(owner, "ColoredElements", (other, ["Face1"]))) + after = snapshot(document, owner, targets) + cross_document = { + "accepted": exception is None, + "error": exception, + "before": before, + "after": after, + "valuePreserved": before["value"] == after["value"], + "documentPreserved": before == after, + } + + owner.ColoredElements = (first, ["Face1", "Face3"]) + document.recompute() + owner.setPropertyStatus("ColoredElements", "ReadOnly") + read_only_before = snapshot(document, owner, targets) + read_only_exception = exception_snapshot(lambda: setattr(owner, "ColoredElements", (second, ["Face2"]))) + read_only_after = snapshot(document, owner, targets) + read_only = { + "accepted": read_only_exception is None, + "error": read_only_exception, + "before": read_only_before, + "after": read_only_after, + "status": [str(item) for item in owner.getPropertyStatus("ColoredElements")], + "pythonBypassesEditorReadOnly": read_only_exception is None, + } + owner.setPropertyStatus("ColoredElements", "-ReadOnly") + + owner.ColoredElements = (first, ["Face1", "Face3"]) + document.recompute() + owner.setPropertyStatus("ColoredElements", "Immutable") + immutable_before = snapshot(document, owner, targets) + immutable_exception = exception_snapshot(lambda: setattr(owner, "ColoredElements", (first, ["Face4"]))) + immutable_after = snapshot(document, owner, targets) + immutable = { + "accepted": immutable_exception is None, + "error": immutable_exception, + "before": immutable_before, + "after": immutable_after, + "status": [str(item) for item in owner.getPropertyStatus("ColoredElements")], + "pythonBypassesImmutable": immutable_exception is None, + } + owner.setPropertyStatus("ColoredElements", "-Immutable") + + owner.ColoredElements = (first, ["Face1", "Face3"]) + document.recompute() + document.openTransaction("property-linksubhidden-cancel") + transaction_before = snapshot(document, owner, targets) + owner.ColoredElements = (second, ["Face5"]) + transaction_edited = snapshot(document, owner, targets) + pending_after_edit = bool(document.HasPendingTransaction) + active_after_edit = active_transaction_snapshot() + document.abortTransaction() + transaction_after_abort_before_recompute = snapshot(document, owner, targets) + recompute_after_abort = bool(document.recompute()) + transaction_after = snapshot(document, owner, targets) + + return { + "objectTypeId": "App::LinkGroup", + "objectName": "LinkGroupProbe", + "propertyName": "ColoredElements", + "propertyTypeId": owner.getTypeIdOfProperty("ColoredElements"), + "baseline": initial["value"], + "initial": initial, + "failures": failures, + "crossDocument": cross_document, + "readOnly": read_only, + "immutable": immutable, + "transaction": { + "before": transaction_before, + "edited": transaction_edited, + "afterAbortBeforeRecompute": transaction_after_abort_before_recompute, + "afterAbort": transaction_after, + "recomputeAfterAbort": recompute_after_abort, + "undoMode": int(document.UndoMode), + "pendingAfterEdit": pending_after_edit, + "pendingAfterAbort": bool(document.HasPendingTransaction), + "activeAfterEdit": active_after_edit, + "activeAfterAbort": active_transaction_snapshot(), + "restored": transaction_before == transaction_after, + }, + "documentIntegrity": { + "initialObjects": initial["objectSet"], + "finalObjects": object_set(document), + "objectsPreserved": initial["objectSet"] == object_set(document), + "foreignDocumentObjects": object_set(external), + }, + } + finally: + App.closeDocument(external.Name) + App.closeDocument(document.Name) + + +case = run_case() +report = { + "schemaVersion": 1, + "status": "pass", + "baselineId": "freecad-1.1.1-property-linksubhidden-failure", + "freecadVersion": ".".join(str(value) for value in App.Version()[:3]), + "gitCommit": FREECAD_COMMIT, + "propertyType": "App::PropertyLinkSubHidden", + "caseCount": 1, + "case": case, + "disabled": { + "classification": "editor-read-only-python-mutable-until-immutable", + "readOnlySetter": "accepted", + "immutableSetter": "rejected", + "readOnly": case["readOnly"], + "immutable": case["immutable"], + }, + "cancellationBoundary": { + "supported": False, + "classification": "synchronous-property-setter", + "reason": "no-native-cancel-hook", + "replacement": "abort-active-document-transaction", + }, +} +if not OUTPUT_PATH: + raise RuntimeError("FREECAD_PROPERTY_LINKSUBHIDDEN_FAILURE_OUTPUT is required") +with open(OUTPUT_PATH, "w", encoding="utf-8") as handle: + json.dump(report, handle, indent=2, sort_keys=True) + handle.write("\n") +print("FREECAD_PROPERTY_LINKSUBHIDDEN_FAILURE_RESULT=" + json.dumps({"status": report["status"], "caseCount": report["caseCount"]}, sort_keys=True)) diff --git a/scripts/freecad-property-linksubhidden-success.py b/scripts/freecad-property-linksubhidden-success.py new file mode 100644 index 0000000..e480c7a --- /dev/null +++ b/scripts/freecad-property-linksubhidden-success.py @@ -0,0 +1,85 @@ +import hashlib +import json +import os + +import FreeCAD as App +import Part + +FREECAD_COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" +OUTPUT_PATH = os.environ.get("FREECAD_PROPERTY_LINKSUBHIDDEN_SUCCESS_OUTPUT", "") + + +def shape_snapshot(obj): + if "Shape" not in obj.PropertiesList: + return {"applicable": False, "reason": "host-has-no-shape-property"} + shape = obj.Shape + if shape.isNull(): + return {"applicable": True, "isNull": True, "solids": 0, "faces": 0, "edges": 0, "vertices": 0, "brepSha256": None} + return {"applicable": True, "isNull": False, "solids": len(shape.Solids), "faces": len(shape.Faces), "edges": len(shape.Edges), "vertices": len(shape.Vertexes), "brepSha256": hashlib.sha256(shape.exportBrepToString().encode("utf-8")).hexdigest()} + + +def colored_value(owner): + value = owner.ColoredElements + if value is None: + return None + target, subs = value + return {"object": target.Name if target is not None else None, "subElements": list(subs)} + + +def snapshot(document, owner, targets): + recompute_result = bool(document.recompute()) + return { + "value": colored_value(owner), + "propertyTypeId": owner.getTypeIdOfProperty("ColoredElements"), + "group": owner.getGroupOfProperty("ColoredElements"), + "propertyStatus": [str(item) for item in owner.getPropertyStatus("ColoredElements")], + "editorMode": [str(item) for item in owner.getEditorMode("ColoredElements")], + "ownerState": [str(item) for item in owner.State], + "statusString": str(owner.getStatusString()), + "recomputeResult": recompute_result, + "shape": shape_snapshot(owner), + "ownerOutList": [candidate.Name for candidate in owner.OutList], + "targetInLists": {target.Name: [candidate.Name for candidate in target.InList] for target in targets}, + "targetShapes": {target.Name: shape_snapshot(target) for target in targets}, + "objectSet": [{"name": candidate.Name, "typeId": candidate.TypeId} for candidate in document.Objects], + } + + +def run_case(object_type_id, object_name): + document = App.newDocument("PropertyLinkSubHidden" + object_name) + try: + owner = document.addObject(object_type_id, object_name) + first = document.addObject("Part::Feature", "ColorSourceA") + first.Shape = Part.makeBox(2.0, 3.0, 4.0) + second = document.addObject("Part::Feature", "ColorSourceB") + second.Shape = Part.makeCylinder(1.5, 5.0) + targets = [first, second] + if "LinkedObject" in owner.PropertiesList: + owner.LinkedObject = first + phases = {} + owner.ColoredElements = (first, ["Face1", "Face3"]) + phases["ordered"] = snapshot(document, owner, targets) + owner.ColoredElements = (first, ["Face6", "Face1", "Face6"]) + phases["duplicates"] = snapshot(document, owner, targets) + owner.ColoredElements = (first, []) + phases["wholeObject"] = snapshot(document, owner, targets) + owner.ColoredElements = None + phases["explicitNull"] = snapshot(document, owner, targets) + return {"objectTypeId": object_type_id, "objectName": object_name, "propertyName": "ColoredElements", "group": " Link", "mode": "direct-native-property-setter", "phases": phases} + finally: + App.closeDocument(document.Name) + + +cases = [ + run_case("App::Link", "LinkProbe"), + run_case("App::LinkGroup", "LinkGroupProbe"), + run_case("App::LinkGroupPython", "LinkGroupPythonProbe"), + run_case("App::LinkPython", "LinkPythonProbe"), +] +report = {"schemaVersion": 1, "status": "pass", "baselineId": "freecad-1.1.1-property-linksubhidden-success", "freecadVersion": ".".join(str(value) for value in App.Version()[:3]), "gitCommit": FREECAD_COMMIT, "propertyType": "App::PropertyLinkSubHidden", "caseCount": len(cases), "bindingBoundary": {"oracleDefault": None, "safeFirstRead": "assign-null-object-before-read", "emptySubListMeans": "whole-object-reference"}, "cases": cases} +if not OUTPUT_PATH: + raise RuntimeError("FREECAD_PROPERTY_LINKSUBHIDDEN_SUCCESS_OUTPUT is required") +with open(OUTPUT_PATH, "w", encoding="utf-8") as handle: + json.dump(report, handle, indent=2, sort_keys=True) + handle.write("\n") +print("FREECAD_PROPERTY_LINKSUBHIDDEN_SUCCESS_RESULT=" + json.dumps({"status": report["status"], "caseCount": report["caseCount"]}, sort_keys=True)) diff --git a/scripts/generate-freecad-property-acceleration-promotion.mjs b/scripts/generate-freecad-property-acceleration-promotion.mjs index d435b86..f93625f 100644 --- a/scripts/generate-freecad-property-acceleration-promotion.mjs +++ b/scripts/generate-freecad-property-acceleration-promotion.mjs @@ -24,7 +24,7 @@ const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::Prop if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 1) fail('global property semantics did not promote App::PropertyAcceleration') const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] const editable = semantics.supportSummary?.['native-editable-codec'] -if (opaque?.typeCount !== 46 || opaque.recordCount !== 457 || editable?.typeCount !== 34 || editable.recordCount !== 4370) fail('global property support summary is not synchronized') +if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') if (roundTrip.classification?.zeroUnknownDrift !== true || chrome.persistence?.loadedType !== 'App::PropertyAcceleration' || chrome.persistence.loadedValue !== 500 || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') const report = { diff --git a/scripts/generate-freecad-property-area-promotion.mjs b/scripts/generate-freecad-property-area-promotion.mjs index e38d656..21891af 100644 --- a/scripts/generate-freecad-property-area-promotion.mjs +++ b/scripts/generate-freecad-property-area-promotion.mjs @@ -24,7 +24,7 @@ const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::Prop if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 1 || propertyType.statusNames?.join(',') !== 'PropOutput,PropReadOnly') fail('global property semantics did not promote App::PropertyArea with its native status') const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] const editable = semantics.supportSummary?.['native-editable-codec'] -if (opaque?.typeCount !== 46 || opaque.recordCount !== 457 || editable?.typeCount !== 34 || editable.recordCount !== 4370) fail('global property support summary is not synchronized') +if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') if (roundTrip.classification?.zeroUnknownDrift !== true || chrome.ui?.areaReadOnly !== true || chrome.persistence?.loadedAreaType !== 'App::PropertyArea' || chrome.persistence.loadedElementsType !== 'App::PropertyLinkSubList' || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') const report = { diff --git a/scripts/generate-freecad-property-boollist-promotion.mjs b/scripts/generate-freecad-property-boollist-promotion.mjs index a9732c4..20323c0 100644 --- a/scripts/generate-freecad-property-boollist-promotion.mjs +++ b/scripts/generate-freecad-property-boollist-promotion.mjs @@ -24,7 +24,7 @@ const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::Prop if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 7 || propertyType.objectTypeCount !== 7 || propertyType.statusNames?.join(',') !== 'Hidden,Immutable,LockDynamic') fail('global property semantics did not promote App::PropertyBoolList with its native status') const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] const editable = semantics.supportSummary?.['native-editable-codec'] -if (opaque?.typeCount !== 46 || opaque.recordCount !== 457 || editable?.typeCount !== 34 || editable.recordCount !== 4370) fail('global property support summary is not synchronized') +if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') if (roundTrip.classification?.zeroUnknownDrift !== true || chrome.persistence?.loadedType !== 'App::PropertyBoolList' || chrome.persistence.fcstdElement !== 'BoolList' || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') const report = { diff --git a/scripts/generate-freecad-property-color-promotion.mjs b/scripts/generate-freecad-property-color-promotion.mjs index c80fe0e..7caa85b 100644 --- a/scripts/generate-freecad-property-color-promotion.mjs +++ b/scripts/generate-freecad-property-color-promotion.mjs @@ -24,7 +24,7 @@ const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::Prop if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 8 || propertyType.objectTypeCount !== 8 || propertyType.statusNames?.length !== 0) fail('global property semantics did not promote App::PropertyColor') const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] const editable = semantics.supportSummary?.['native-editable-codec'] -if (opaque?.typeCount !== 46 || opaque.recordCount !== 457 || editable?.typeCount !== 34 || editable.recordCount !== 4370) fail('global property support summary is not synchronized') +if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') if (roundTrip.classification?.zeroUnknownDrift !== true || chrome.persistence?.loadedType !== 'App::PropertyColor' || chrome.persistence.fcstdElement !== 'PropertyColor' || chrome.persistence.fcstdValue !== '862362060' || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') const report = { diff --git a/scripts/generate-freecad-property-colorlist-promotion.mjs b/scripts/generate-freecad-property-colorlist-promotion.mjs index fddac0d..549ff76 100644 --- a/scripts/generate-freecad-property-colorlist-promotion.mjs +++ b/scripts/generate-freecad-property-colorlist-promotion.mjs @@ -24,7 +24,7 @@ const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::Prop if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 2 || propertyType.objectTypeCount !== 2 || propertyType.statusNames?.length !== 0) fail('global property semantics did not promote App::PropertyColorList') const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] const editable = semantics.supportSummary?.['native-editable-codec'] -if (opaque?.typeCount !== 46 || opaque.recordCount !== 457 || editable?.typeCount !== 34 || editable.recordCount !== 4370) fail('global property support summary is not synchronized') +if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') if (roundTrip.classification?.zeroUnknownDrift !== true || chrome.persistence?.loadedType !== 'App::PropertyColorList' || chrome.persistence.fcstdElement !== 'ColorList' || chrome.persistence.fcstdResourcePath !== 'ColourList' || chrome.persistence.fcstdResourceBytes !== 12 || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') const report = { diff --git a/scripts/generate-freecad-property-direction-promotion.mjs b/scripts/generate-freecad-property-direction-promotion.mjs index badd62a..0f5b735 100644 --- a/scripts/generate-freecad-property-direction-promotion.mjs +++ b/scripts/generate-freecad-property-direction-promotion.mjs @@ -24,7 +24,7 @@ const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::Prop if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 2 || propertyType.objectTypeCount !== 2 || propertyType.statusNames?.length !== 0) fail('global property semantics did not promote App::PropertyDirection') const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] const editable = semantics.supportSummary?.['native-editable-codec'] -if (opaque?.typeCount !== 46 || opaque.recordCount !== 457 || editable?.typeCount !== 34 || editable.recordCount !== 4370) fail('global property support summary is not synchronized') +if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') if (roundTrip.classification?.zeroUnknownDrift !== true || chrome.persistence?.loadedType !== 'App::PropertyDirection' || chrome.persistence.fcstdElement !== 'PropertyVector' || chrome.persistence.fcstdValue !== JSON.stringify({ x: 0.25, y: -0.5, z: 1.5 }) || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') const report = { diff --git a/scripts/generate-freecad-property-file-promotion.mjs b/scripts/generate-freecad-property-file-promotion.mjs index 20d5b99..0b6257b 100644 --- a/scripts/generate-freecad-property-file-promotion.mjs +++ b/scripts/generate-freecad-property-file-promotion.mjs @@ -24,7 +24,7 @@ const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::Prop if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 16 || propertyType.objectTypeCount !== 12 || propertyType.statusNames?.length !== 0) fail('global property semantics did not promote App::PropertyFile') const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] const editable = semantics.supportSummary?.['native-editable-codec'] -if (opaque?.typeCount !== 46 || opaque.recordCount !== 457 || editable?.typeCount !== 34 || editable.recordCount !== 4370) fail('global property support summary is not synchronized') +if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') if (roundTrip.classification?.zeroUnknownDrift !== true || roundTrip.classification.externalResourceBoundary !== 'project-relative-path; asset bytes remain external to FCStd' || chrome.persistence?.loadedType !== 'App::PropertyFile' || chrome.persistence.fcstdElement !== 'String' || chrome.persistence.fcstdValue !== 'project-files/chrome-updated.obj' || chrome.resource?.released !== true || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') const report = { diff --git a/scripts/generate-freecad-property-fileincluded-promotion.mjs b/scripts/generate-freecad-property-fileincluded-promotion.mjs index eacf6df..117963a 100644 --- a/scripts/generate-freecad-property-fileincluded-promotion.mjs +++ b/scripts/generate-freecad-property-fileincluded-promotion.mjs @@ -24,7 +24,7 @@ const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::Prop if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 20 || propertyType.objectTypeCount !== 15 || propertyType.statusNames?.join(',') !== 'PropOutput,ReadOnly') fail('global property semantics did not promote App::PropertyFileIncluded') const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] const editable = semantics.supportSummary?.['native-editable-codec'] -if (opaque?.typeCount !== 46 || opaque.recordCount !== 457 || editable?.typeCount !== 34 || editable.recordCount !== 4370) fail('global property support summary is not synchronized') +if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') if (roundTrip.classification?.zeroUnknownDrift !== true || roundTrip.classification.archiveName !== 'payload.bin' || roundTrip.classification.resavedResourceMatches !== true || chrome.persistence?.loadedType !== 'App::PropertyFileIncluded' || chrome.persistence.fcstdElement !== 'FileIncluded' || chrome.persistence.fcstdResourceMatches !== true || chrome.resource?.documentRoundTrip !== true || chrome.resource.independentReleased !== true || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') const report = { diff --git a/scripts/generate-freecad-property-font-promotion.mjs b/scripts/generate-freecad-property-font-promotion.mjs index b3e07c0..38ef06e 100644 --- a/scripts/generate-freecad-property-font-promotion.mjs +++ b/scripts/generate-freecad-property-font-promotion.mjs @@ -24,7 +24,7 @@ const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::Prop if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 2 || propertyType.objectTypeCount !== 2 || propertyType.statusNames?.length !== 0) fail('global property semantics did not promote App::PropertyFont') const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] const editable = semantics.supportSummary?.['native-editable-codec'] -if (opaque?.typeCount !== 46 || opaque.recordCount !== 457 || editable?.typeCount !== 34 || editable.recordCount !== 4370) fail('global property support summary is not synchronized') +if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') if (roundTrip.classification?.zeroUnknownDrift !== true || roundTrip.classification.fontResourceBoundary !== 'font family name persists in FCStd; font files and availability remain presentation-only' || chrome.persistence?.loadedType !== 'App::PropertyFont' || chrome.persistence.fcstdElement !== 'String' || chrome.persistence.fcstdValue !== 'Noto Sans CJK SC' || chrome.resource?.released !== true || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') const report = { diff --git a/scripts/generate-freecad-property-force-promotion.mjs b/scripts/generate-freecad-property-force-promotion.mjs index 01ed3d0..35df541 100644 --- a/scripts/generate-freecad-property-force-promotion.mjs +++ b/scripts/generate-freecad-property-force-promotion.mjs @@ -24,7 +24,7 @@ const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::Prop if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 4 || propertyType.objectTypeCount !== 2 || propertyType.statusNames?.join(',') !== 'PropOutput') fail('global property semantics did not promote App::PropertyForce') const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] const editable = semantics.supportSummary?.['native-editable-codec'] -if (opaque?.typeCount !== 46 || opaque.recordCount !== 457 || editable?.typeCount !== 34 || editable.recordCount !== 4370) fail('global property support summary is not synchronized') +if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') if (roundTrip.classification?.zeroUnknownDrift !== true || roundTrip.classification.outputPropertiesPreserved !== true || chrome.persistence?.loadedType !== 'App::PropertyForce' || chrome.persistence.loadedValue !== 500000 || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') const report = { diff --git a/scripts/generate-freecad-property-heatflux-promotion.mjs b/scripts/generate-freecad-property-heatflux-promotion.mjs index 335822f..89d9876 100644 --- a/scripts/generate-freecad-property-heatflux-promotion.mjs +++ b/scripts/generate-freecad-property-heatflux-promotion.mjs @@ -24,7 +24,7 @@ const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::Prop if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 1 || propertyType.objectTypeCount !== 1 || propertyType.statusNames?.length !== 0) fail('global property semantics did not promote App::PropertyHeatFlux') const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] const editable = semantics.supportSummary?.['native-editable-codec'] -if (opaque?.typeCount !== 46 || opaque.recordCount !== 457 || editable?.typeCount !== 34 || editable.recordCount !== 4370) fail('global property support summary is not synchronized') +if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') if (roundTrip.classification?.zeroUnknownDrift !== true || roundTrip.classification.nativeValueMatches !== true || chrome.persistence?.loadedType !== 'App::PropertyHeatFlux' || chrome.persistence.loadedValue !== 500 || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') const report = { diff --git a/scripts/generate-freecad-property-integerset-promotion.mjs b/scripts/generate-freecad-property-integerset-promotion.mjs index b9cff9e..38ee398 100644 --- a/scripts/generate-freecad-property-integerset-promotion.mjs +++ b/scripts/generate-freecad-property-integerset-promotion.mjs @@ -17,7 +17,7 @@ const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::Prop if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 2 || propertyType.objectTypeCount !== 2 || propertyType.statusNames?.length !== 0) fail('global property semantics did not promote App::PropertyIntegerSet') const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] const editable = semantics.supportSummary?.['native-editable-codec'] -if (opaque?.typeCount !== 46 || opaque.recordCount !== 457 || editable?.typeCount !== 34 || editable.recordCount !== 4370) fail('global property support summary is not synchronized') +if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') if (roundTrip.classification?.zeroUnknownDrift !== true || chrome.persistence?.loadedType !== 'App::PropertyIntegerSet' || chrome.persistence.fcstdElement !== 'IntegerSet' || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') const report = { schemaVersion: 1, status: 'pass', taskId: 'PROP-app-propertyintegerset-I', baseline: semantics.baseline, propertyType: 'App::PropertyIntegerSet', recordCount: 2, diff --git a/scripts/generate-freecad-property-linklisthidden-inventory.mjs b/scripts/generate-freecad-property-linklisthidden-inventory.mjs new file mode 100644 index 0000000..3e03033 --- /dev/null +++ b/scripts/generate-freecad-property-linklisthidden-inventory.mjs @@ -0,0 +1,96 @@ +import { createHash } from 'node:crypto' +import { readFile, writeFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const runtimePath = '.cache/freecad/reference-desktop.json' +const sourcePaths = [ + '.cache/freecad/FreeCAD/src/App/PropertyLinks.h', + '.cache/freecad/FreeCAD/src/App/PropertyLinks.cpp', + '.cache/freecad/FreeCAD/src/Mod/Sketcher/App/SketchObject.h', + '.cache/freecad/FreeCAD/src/Mod/Sketcher/App/SketchObject.cpp', +] +const outputPath = resolve(root, 'config/freecad-property-linklisthidden-inventory.json') +const [runtimeContent, ...sourceContents] = await Promise.all([ + readFile(resolve(root, runtimePath)), + ...sourcePaths.map((path) => readFile(resolve(root, path))), +]) +const runtime = JSON.parse(runtimeContent) +const fail = (message) => { throw new Error(`FreeCAD PropertyLinkListHidden inventory generation failed: ${message}`) } +if (runtime.schemaVersion !== 1 || runtime.freecadVersion !== '1.1.1' || runtime.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('desktop oracle is not the locked FreeCAD baseline') + +const records = [] +for (const objectType of runtime.runtimeObjects?.types ?? []) { + for (const property of objectType.properties ?? []) { + if (property.typeId !== 'App::PropertyLinkListHidden') continue + records.push({ + objectTypeId: objectType.typeId, + objectAvailable: objectType.available === true, + probeStatus: objectType.probeStatus, + propertyName: property.name, + group: property.group, + statusRaw: property.status, + statusNames: property.status.map((status) => status === 26 ? 'PropHidden' : `UnknownBit${status}`), + defaultRaw: property.default, + valueModel: { + kind: 'hidden-document-object-list', + inheritedFrom: 'App::PropertyLinkList', + elementType: 'same-document App::DocumentObject reference', + orderPreserved: true, + duplicatesPreserved: true, + writable: !property.status.some((status) => status === 'ReadOnly' || status === 'Immutable' || status === 24), + }, + inputs: { + nativeCppSetValues: 'attached same-document DocumentObject list', + emptyListClears: true, + singleNullListClears: true, + detachedObjectRejected: true, + crossDocumentRejectedWithoutLinkAllowExternal: true, + }, + dependencies: { + linkScope: 'Hidden', + createsBacklinks: false, + includedByDefaultGetLinks: false, + includedByGetLinksAll: true, + defaultDagDependency: false, + }, + applicability: { + requiredObjectTypeId: objectType.typeId, + role: 'Sketch exported child geometry lookup by object name', + propertyWriteRequiresShape: false, + source: 'Document.supportedTypes runtime inventory and SketchObject::getSubObject', + }, + }) + } +} +records.sort((left, right) => `${left.objectTypeId}.${left.propertyName}`.localeCompare(`${right.objectTypeId}.${right.propertyName}`)) +if (records.length !== 2 || new Set(records.map(({ objectTypeId }) => objectTypeId)).size !== 2) fail(`expected two native App::PropertyLinkListHidden records, found ${records.length}`) + +const report = { + schemaVersion: 1, + status: 'pass', + baseline: { freecadVersion: runtime.freecadVersion, commit: runtime.gitCommit }, + propertyType: 'App::PropertyLinkListHidden', + recordCount: records.length, + objectTypeCount: new Set(records.map(({ objectTypeId }) => objectTypeId)).size, + writableRecordCount: records.filter(({ valueModel }) => valueModel.writable).length, + records, + typeContract: { + inheritedFrom: 'App::PropertyLinkList', + linkScope: 'Hidden', + propertyStatus: 'PropHidden', + propertyStatusPurpose: 'hide the Exports property from the ordinary editor surface', + linkScopePurpose: 'exclude Exports from ordinary backlink and dependency traversal', + xmlElement: 'LinkList', + xmlChildElement: 'Link', + legacyRestoreType: 'App::PropertyLinkList', + }, + provenance: { + runtime: { path: runtimePath, bytes: runtimeContent.length, sha256: createHash('sha256').update(runtimeContent).digest('hex') }, + sources: sourcePaths.map((path, index) => ({ path, bytes: sourceContents[index].length, sha256: createHash('sha256').update(sourceContents[index]).digest('hex') })), + }, + classification: 'opaque-fcstd-proxy', + nextPhases: ['B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'], +} +await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`) +console.log(JSON.stringify({ status: 'freecad-property-linklisthidden-inventory-generated', output: 'config/freecad-property-linklisthidden-inventory.json', propertyType: report.propertyType, recordCount: report.recordCount, writableRecordCount: report.writableRecordCount }, null, 2)) diff --git a/scripts/generate-freecad-property-linklisthidden-promotion.mjs b/scripts/generate-freecad-property-linklisthidden-promotion.mjs new file mode 100644 index 0000000..7077bad --- /dev/null +++ b/scripts/generate-freecad-property-linklisthidden-promotion.mjs @@ -0,0 +1,66 @@ +import { readFile, writeFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse) +const outputPath = resolve(root, 'config/freecad-property-linklisthidden-promotion.json') +const [inventory, success, failure, mutation, roundTrip, chrome, semantics, progress] = await Promise.all([ + load('config/freecad-property-linklisthidden-inventory.json'), + load('config/freecad-property-linklisthidden-success.json'), + load('config/freecad-property-linklisthidden-failure.json'), + load('config/freecad-property-linklisthidden-mutation.json'), + load('config/freecad-property-linklisthidden-roundtrip.json'), + load('config/chrome-property-linklisthidden-verification.json'), + load('config/freecad-native-property-semantics.json'), + load('config/freecad-follow-up-task-progress.json'), +]) +const fail = (message) => { throw new Error(`FreeCAD PropertyLinkListHidden promotion generation failed: ${message}`) } +const requiredCompletedPhases = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] +const completed = new Map(progress.completedTasks?.map((entry) => [entry.id, entry]) ?? []) +for (const phase of requiredCompletedPhases) if (!completed.has(`PROP-app-propertylinklisthidden-${phase}`)) fail(`phase ${phase} is not completed in the evidence ledger`) +if (inventory.status !== 'pass' || inventory.propertyType !== 'App::PropertyLinkListHidden' || inventory.recordCount !== 2 || inventory.objectTypeCount !== 2 || inventory.writableRecordCount !== 2) fail('inventory evidence is invalid') +for (const [name, artifact] of Object.entries({ success, failure, mutation, roundTrip, chrome })) if (artifact.status !== 'pass') fail(`${name} evidence is not passing`) +const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::PropertyLinkListHidden') +if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 2 || propertyType.objectTypeCount !== 2 || propertyType.statusNames?.join(',') !== 'PropHidden') fail('global property semantics did not promote App::PropertyLinkListHidden') +const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] +const editable = semantics.supportSummary?.['native-editable-codec'] +if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') +if (roundTrip.classification?.zeroUnknownDrift !== true || roundTrip.classification.hiddenLinksPreserved !== true || roundTrip.classification.nativeDependencyMetadataNormalized !== true || chrome.persistence?.loadedType !== 'App::PropertyLinkListHidden' || chrome.persistence.fcstdElement !== 'LinkList' || chrome.ui?.hiddenControlCountAfter !== 0 || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') +const report = { + schemaVersion: 1, + status: 'pass', + taskId: 'PROP-app-propertylinklisthidden-I', + baseline: semantics.baseline, + propertyType: 'App::PropertyLinkListHidden', + recordCount: 2, + phaseEvidence: Object.fromEntries(requiredCompletedPhases.map((phase) => [phase, completed.get(`PROP-app-propertylinklisthidden-${phase}`).evidence])), + promotion: { + from: inventory.classification, + to: propertyType.support, + facadeValueModel: 'ordered-duplicate-preserving-same-document-object-name-array', + writableRecords: 2, + objectTypeCount: 2, + propertyStatus: 'PropHidden', + linkScope: 'Hidden', + runtimeDependencyEdges: 0, + fcstdElement: chrome.persistence.fcstdElement, + nativeRoundTripValue: roundTrip.classification.resavedValue, + browserRoundTripValue: chrome.persistence.loadedValue, + hiddenUiFiltered: chrome.ui.hiddenControlCountAfter === 0 && chrome.ui.hiddenTextPresentAfter === false, + hiddenLinksPreserved: roundTrip.classification.hiddenLinksPreserved, + nativeDependencyMetadataNormalized: roundTrip.classification.nativeDependencyMetadataNormalized, + zeroUnknownDrift: roundTrip.classification.zeroUnknownDrift, + }, + exactBlockerSync: { + nativeEditableTypes: editable.typeCount, + nativeEditableRecords: editable.recordCount, + opaqueTypes: opaque.typeCount, + opaqueRecords: opaque.recordCount, + exactPromotionReady: semantics.exactPromotionReady, + exactBlocker: semantics.exactBlocker, + }, + systemExact: false, + generatedAt: new Date().toISOString(), +} +await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`) +console.log(JSON.stringify({ status: 'freecad-property-linklisthidden-promotion-generated', output: 'config/freecad-property-linklisthidden-promotion.json', promotion: report.promotion, exactBlockerSync: report.exactBlockerSync }, null, 2)) diff --git a/scripts/generate-freecad-property-linksubhidden-inventory.mjs b/scripts/generate-freecad-property-linksubhidden-inventory.mjs new file mode 100644 index 0000000..c5468e4 --- /dev/null +++ b/scripts/generate-freecad-property-linksubhidden-inventory.mjs @@ -0,0 +1,47 @@ +import { createHash } from 'node:crypto' +import { readFile, writeFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const runtimePath = '.cache/freecad/reference-desktop.json' +const sourcePaths = [ + '.cache/freecad/FreeCAD/src/App/PropertyLinks.h', + '.cache/freecad/FreeCAD/src/App/PropertyLinks.cpp', + '.cache/freecad/FreeCAD/src/App/Link.h', + '.cache/freecad/FreeCAD/src/Mod/Draft/draftobjects/draftlink.py', +] +const outputPath = resolve(root, 'config/freecad-property-linksubhidden-inventory.json') +const [runtimeContent, ...sourceContents] = await Promise.all([readFile(resolve(root, runtimePath)), ...sourcePaths.map((path) => readFile(resolve(root, path)))]) +const runtime = JSON.parse(runtimeContent) +const fail = (message) => { throw new Error(`FreeCAD PropertyLinkSubHidden inventory generation failed: ${message}`) } +if (runtime.schemaVersion !== 1 || runtime.freecadVersion !== '1.1.1' || runtime.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('desktop oracle is not the locked FreeCAD baseline') + +const records = [] +for (const objectType of runtime.runtimeObjects?.types ?? []) for (const property of objectType.properties ?? []) { + if (property.typeId !== 'App::PropertyLinkSubHidden') continue + records.push({ + objectTypeId: objectType.typeId, + objectAvailable: objectType.available === true, + probeStatus: objectType.probeStatus, + propertyName: property.name, + group: property.group, + statusRaw: property.status, + statusNames: property.status.map((status) => status === 26 ? 'PropHidden' : status), + defaultRaw: property.default, + valueModel: { kind: 'hidden-document-object-sub-element-reference', inheritedFrom: 'App::PropertyLinkSub', elementType: 'same-document App::DocumentObject plus ordered sub-element names', orderPreserved: true, duplicatesPreserved: false, writable: true }, + inputs: { nativeCppSetValues: 'attached same-document DocumentObject plus sub-element string list', emptySubListMeansWholeObject: true, nullObjectClears: true, detachedObjectRejected: true, crossDocumentRejectedWithoutLinkAllowExternal: true, invalidSubElementAcceptedAsReference: true }, + dependencies: { linkScope: 'Hidden', createsBacklinks: false, includedByDefaultGetLinks: false, includedByGetLinksAll: true, defaultDagDependency: false }, + applicability: { requiredObjectTypeId: objectType.typeId, role: 'Link colored element override lookup', propertyWriteRequiresShape: false, source: 'Link extension and Draft colored element implementation' }, + }) +} +records.sort((left, right) => `${left.objectTypeId}.${left.propertyName}`.localeCompare(`${right.objectTypeId}.${right.propertyName}`)) +if (records.length !== 4 || new Set(records.map(({ objectTypeId }) => objectTypeId)).size !== 4) fail(`expected four native App::PropertyLinkSubHidden records, found ${records.length}`) + +const report = { + schemaVersion: 1, status: 'pass', baseline: { freecadVersion: runtime.freecadVersion, commit: runtime.gitCommit }, propertyType: 'App::PropertyLinkSubHidden', recordCount: records.length, objectTypeCount: new Set(records.map(({ objectTypeId }) => objectTypeId)).size, writableRecordCount: records.filter(({ valueModel }) => valueModel.writable).length, records, + typeContract: { inheritedFrom: 'App::PropertyLinkSub', linkScope: 'Hidden', propertyStatus: 'PropHidden', dynamicStatus: 'LockDynamic', propertyStatusPurpose: 'hide the ColoredElements property from the ordinary editor surface', linkScopePurpose: 'exclude ColoredElements from ordinary backlink and dependency traversal', xmlElement: 'LinkSub', xmlChildElement: 'Sub', legacyRestoreType: 'App::PropertyLinkSub', emptySubListMeans: 'whole-object-reference' }, + provenance: { runtime: { path: runtimePath, bytes: runtimeContent.length, sha256: createHash('sha256').update(runtimeContent).digest('hex') }, sources: sourcePaths.map((path, index) => ({ path, bytes: sourceContents[index].length, sha256: createHash('sha256').update(sourceContents[index]).digest('hex') })) }, + classification: 'opaque-fcstd-proxy', nextPhases: ['B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'], +} +await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`) +console.log(JSON.stringify({ status: 'freecad-property-linksubhidden-inventory-generated', output: 'config/freecad-property-linksubhidden-inventory.json', propertyType: report.propertyType, recordCount: report.recordCount, writableRecordCount: report.writableRecordCount }, null, 2)) diff --git a/scripts/run-chrome-property-linklisthidden.mjs b/scripts/run-chrome-property-linklisthidden.mjs new file mode 100644 index 0000000..2975660 --- /dev/null +++ b/scripts/run-chrome-property-linklisthidden.mjs @@ -0,0 +1,51 @@ +import { createServer } from 'node:http' +import { existsSync } from 'node:fs' +import { writeFile } from 'node:fs/promises' +import { resolve } from 'node:path' +import { spawn } from 'node:child_process' +import { createChromeProfile, removeChromeProfile } from './chrome-profile.mjs' + +const root = resolve(new URL('..', import.meta.url).pathname) +const reportPath = resolve(root, 'config/chrome-property-linklisthidden-verification.json') +const waitForPort = (child) => new Promise((resolvePort, reject) => { + let output = '' + const timer = setTimeout(() => reject(new Error(`Vite did not announce a port: ${output.slice(-2000)}`)), 30_000) + const onData = (chunk) => { output += String(chunk); const match = output.match(/http:\/\/127\.0\.0\.1:(\d+)/); if (match) { clearTimeout(timer); resolvePort(Number(match[1])) } } + child.stdout.on('data', onData); child.stderr.on('data', onData) +}) + +const vite = spawn('./nodew', ['node_modules/vite/bin/vite.js', '--host', '127.0.0.1'], { cwd: root, stdio: ['ignore', 'pipe', 'pipe'] }) +let vitePort +try { vitePort = await waitForPort(vite) } catch (error) { vite.kill('SIGTERM'); throw error } +let resolveReport +const reportPromise = new Promise((resolveValue) => { resolveReport = resolveValue }) +const server = createServer(async (request, response) => { + response.setHeader('Cross-Origin-Opener-Policy', 'same-origin') + response.setHeader('Cross-Origin-Embedder-Policy', 'require-corp') + response.setHeader('Cross-Origin-Resource-Policy', 'same-origin') + if (request.method === 'POST' && request.url === '/__chrome-property-linklisthidden-report') { + let body = '' + request.setEncoding('utf8'); request.on('data', (chunk) => { body += String(chunk) }) + request.on('end', async () => { const report = JSON.parse(body); await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`); resolveReport(report); response.writeHead(204); response.end() }) + return + } + try { + const upstream = await fetch(`http://127.0.0.1:${vitePort}${request.url}`, { headers: { ...request.headers } }) + response.writeHead(upstream.status, Object.fromEntries(upstream.headers.entries())); response.end(Buffer.from(await upstream.arrayBuffer())) + } catch (error) { response.writeHead(502); response.end(String(error)) } +}) +await new Promise((resolveServer) => server.listen(0, '127.0.0.1', resolveServer)) +const port = server.address().port +const executable = process.env.CHROME_BIN || '/home/mes123456/.local/bin/google-chrome' +if (!existsSync(executable)) throw new Error(`Chrome executable is missing: ${executable}`) +const chromeProfile = await createChromeProfile('property-linklisthidden') +const chrome = spawn(executable, ['--headless=new', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage', '--noerrdialogs', '--no-first-run', `--user-data-dir=${chromeProfile}`, `http://127.0.0.1:${port}/chrome-property-linklisthidden-harness.html`], { cwd: root, stdio: ['ignore', 'ignore', 'pipe'] }) +let chromeStderr = '' +chrome.stderr.on('data', (chunk) => { chromeStderr += String(chunk) }) +let timeoutId +const timeoutReport = new Promise((resolveTimeout) => { timeoutId = setTimeout(() => resolveTimeout({ status: 'timeout', browserId: 'chrome' }), 120_000) }) +const report = await Promise.race([reportPromise, timeoutReport]) +clearTimeout(timeoutId); chrome.kill('SIGTERM'); vite.kill('SIGTERM'); server.closeAllConnections?.(); server.close() +await removeChromeProfile(chromeProfile) +console.log(JSON.stringify(report, null, 2)) +if (report.status !== 'pass') { if (chromeStderr) console.error(chromeStderr.slice(-3000)); process.exit(1) } diff --git a/scripts/run-freecad-native-property-semantics.mjs b/scripts/run-freecad-native-property-semantics.mjs index bab5415..9dbeb29 100644 --- a/scripts/run-freecad-native-property-semantics.mjs +++ b/scripts/run-freecad-native-property-semantics.mjs @@ -18,7 +18,7 @@ const editableTypes = new Set([ 'App::PropertyAcceleration', 'App::PropertyAngle', 'App::PropertyArea', 'App::PropertyBool', 'App::PropertyBoolList', 'App::PropertyColor', 'App::PropertyColorList', 'App::PropertyDistance', 'App::PropertyEnumeration', 'App::PropertyForce', 'App::PropertyHeatFlux', 'App::PropertyFloat', 'App::PropertyFloatConstraint', 'App::PropertyFloatList', 'App::PropertyInteger', 'App::PropertyIntegerConstraint', 'App::PropertyIntegerSet', 'App::PropertyIntegerList', 'App::PropertyLength', 'App::PropertyLink', 'App::PropertyLinkHidden', 'App::PropertyLinkList', - 'App::PropertyLinkSub', 'App::PropertyLinkSubList', 'App::PropertyPlacement', 'App::PropertyString', + 'App::PropertyLinkSub', 'App::PropertyLinkSubList', 'App::PropertyLinkListHidden', 'App::PropertyPlacement', 'App::PropertyString', 'App::PropertyPrecision', 'App::PropertyQuantityConstraint', 'App::PropertyStringList', 'App::PropertyVector', 'App::PropertyDirection', 'App::PropertyFile', 'App::PropertyFileIncluded', 'App::PropertyFont', ]) const specializedTypes = new Set([ diff --git a/scripts/run-freecad-property-linklisthidden-failure.mjs b/scripts/run-freecad-property-linklisthidden-failure.mjs new file mode 100644 index 0000000..78f3b93 --- /dev/null +++ b/scripts/run-freecad-property-linklisthidden-failure.mjs @@ -0,0 +1,27 @@ +import { existsSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import { spawnSync } from 'node:child_process' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const executable = process.env.FREECAD_CMD || resolve(root, '.cache/freecad/install-desktop/bin/FreeCADCmd') +const sysroot = resolve(root, '.cache/freecad/sysroot') +const scriptPath = resolve(root, 'scripts/freecad-property-linklisthidden-failure.py') +const outputPath = resolve(root, 'config/freecad-property-linklisthidden-failure.json') +if (!existsSync(executable)) throw new Error(`FreeCAD PropertyLinkListHidden executable is missing: ${executable}`) +const execution = spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), scriptPath], { + cwd: root, + encoding: 'utf8', + timeout: 120_000, + maxBuffer: 32 * 1024 * 1024, + env: { + ...process.env, + FREECAD_PROPERTY_LINKLISTHIDDEN_FAILURE_OUTPUT: outputPath, + PYTHONPATH: `${resolve(sysroot, 'usr/lib/python3/dist-packages')}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`, + LD_LIBRARY_PATH: `${resolve(sysroot, 'usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}`, + }, +}) +const output = `${execution.stdout || ''}\n${execution.stderr || ''}` +if (execution.error || execution.status !== 0 || !output.includes('FREECAD_PROPERTY_LINKLISTHIDDEN_FAILURE_RESULT=')) throw new Error(`FreeCAD PropertyLinkListHidden failure probe failed with status ${execution.status}: ${execution.error?.message || output.trim()}`) +const report = JSON.parse(await readFile(outputPath, 'utf8')) +console.log(JSON.stringify({ status: report.status, output: 'config/freecad-property-linklisthidden-failure.json', failureCount: report.failures?.length, failures: report.failures?.map(({ id, exception }) => ({ id, exception })) }, null, 2)) diff --git a/scripts/run-freecad-property-linklisthidden-mutation.mjs b/scripts/run-freecad-property-linklisthidden-mutation.mjs new file mode 100644 index 0000000..60b0520 --- /dev/null +++ b/scripts/run-freecad-property-linklisthidden-mutation.mjs @@ -0,0 +1,27 @@ +import { existsSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import { spawnSync } from 'node:child_process' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const executable = process.env.FREECAD_CMD || resolve(root, '.cache/freecad/install-desktop/bin/FreeCADCmd') +const sysroot = resolve(root, '.cache/freecad/sysroot') +const scriptPath = resolve(root, 'scripts/freecad-property-linklisthidden-mutation.py') +const outputPath = resolve(root, 'config/freecad-property-linklisthidden-mutation.json') +if (!existsSync(executable)) throw new Error(`FreeCAD PropertyLinkListHidden executable is missing: ${executable}`) +const execution = spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), scriptPath], { + cwd: root, + encoding: 'utf8', + timeout: 120_000, + maxBuffer: 32 * 1024 * 1024, + env: { + ...process.env, + FREECAD_PROPERTY_LINKLISTHIDDEN_MUTATION_OUTPUT: outputPath, + PYTHONPATH: `${resolve(sysroot, 'usr/lib/python3/dist-packages')}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`, + LD_LIBRARY_PATH: `${resolve(sysroot, 'usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}`, + }, +}) +const output = `${execution.stdout || ''}\n${execution.stderr || ''}` +if (execution.error || execution.status !== 0 || !output.includes('FREECAD_PROPERTY_LINKLISTHIDDEN_MUTATION_RESULT=')) throw new Error(`FreeCAD PropertyLinkListHidden mutation probe failed with status ${execution.status}: ${execution.error?.message || output.trim()}`) +const report = JSON.parse(await readFile(outputPath, 'utf8')) +console.log(JSON.stringify({ status: report.status, output: 'config/freecad-property-linklisthidden-mutation.json', caseCount: report.caseCount, restored: report.cases?.map(({ objectTypeId, semanticStateRestored }) => ({ objectTypeId, semanticStateRestored })) }, null, 2)) diff --git a/scripts/run-freecad-property-linklisthidden-roundtrip.ts b/scripts/run-freecad-property-linklisthidden-roundtrip.ts new file mode 100644 index 0000000..2b844c7 --- /dev/null +++ b/scripts/run-freecad-property-linklisthidden-roundtrip.ts @@ -0,0 +1,108 @@ +import { createHash } from 'node:crypto' +import { existsSync } from 'node:fs' +import { mkdir, readFile, writeFile } from 'node:fs/promises' +import { spawnSync } from 'node:child_process' +import { resolve } from 'node:path' +import { isDeepStrictEqual } from 'node:util' +import { unzipSync } from 'fflate' +import { createWebCadFacade } from '../src/facade/mockFacade' +import { decodeFcstdPropertyValue } from '../src/facade/fcstd' + +const root = resolve(new URL('..', import.meta.url).pathname) +const executable = process.env.FREECAD_CMD || resolve(root, '.cache/freecad/install-desktop/bin/FreeCADCmd') +const sysroot = resolve(root, '.cache/freecad/sysroot') +const scriptPath = resolve(root, 'scripts/freecad-property-linklisthidden-roundtrip.py') +const outputDirectory = resolve(root, '.cache/freecad/property-linklisthidden-roundtrip') +const nativePath = resolve(outputDirectory, 'native-initial.FCStd') +const webPath = resolve(outputDirectory, 'web-edited.FCStd') +const resavedPath = resolve(outputDirectory, 'native-resaved.FCStd') +const reportPath = resolve(root, 'config/freecad-property-linklisthidden-roundtrip.json') +if (!existsSync(executable)) throw new Error(`FreeCAD PropertyLinkListHidden roundtrip executable is missing: ${executable}`) +await mkdir(outputDirectory, { recursive: true }) + +const runNative = (mode: 'create' | 'verify', path: string, resaved = '') => { + const execution = spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), scriptPath], { + cwd: root, + encoding: 'utf8', + timeout: 120_000, + maxBuffer: 32 * 1024 * 1024, + env: { + ...process.env, + FREECAD_PROPERTY_LINKLISTHIDDEN_ROUNDTRIP_MODE: mode, + FREECAD_PROPERTY_LINKLISTHIDDEN_ROUNDTRIP_PATH: path, + FREECAD_PROPERTY_LINKLISTHIDDEN_ROUNDTRIP_RESAVED_PATH: resaved, + PYTHONPATH: `${resolve(sysroot, 'usr/lib/python3/dist-packages')}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`, + LD_LIBRARY_PATH: `${resolve(sysroot, 'usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}`, + }, + }) + const output = `${execution.stdout || ''}\n${execution.stderr || ''}` + const marker = 'FREECAD_PROPERTY_LINKLISTHIDDEN_ROUNDTRIP_RESULT=' + const line = output.split(/\r?\n/).find((candidate) => candidate.includes(marker)) + if (execution.error || execution.status !== 0 || !line) throw new Error(`FreeCAD PropertyLinkListHidden ${mode} probe failed with status ${execution.status}: ${execution.error?.message || output.trim()}`) + return JSON.parse(line.slice(line.indexOf(marker) + marker.length)) +} + +const initial = ['ExportA', 'ExportB'] +const target = ['ExportB', 'ExportA', 'ExportB'] +const native = runNative('create', nativePath) +const nativeBytes = new Uint8Array(await readFile(nativePath)) +const facade = createWebCadFacade({ runtimeMode: 'mock' }) +const inspectedBefore = facade.project.fcstd.inspect(nativeBytes) +const property = (inspection: typeof inspectedBefore) => inspection.objects.find((object) => object.name === 'SketchProbe')?.properties.find((candidate) => candidate.name === 'Exports') +const editedBytes = facade.project.fcstd.rewriteLinkListHidden(nativeBytes, { objectName: 'SketchProbe', propertyName: 'Exports', value: target, expectedValue: initial }) +const inspectedAfter = facade.project.fcstd.inspect(editedBytes) +await facade.project.dispose() +await writeFile(webPath, editedBytes) + +const initialFiles = unzipSync(nativeBytes) +const editedFiles = unzipSync(editedBytes) +const opaquePaths = Object.keys(initialFiles).filter((path) => path.toLowerCase() !== 'document.xml') +const opaqueEntriesPreserved = opaquePaths.every((path) => Buffer.from(initialFiles[path]).equals(Buffer.from(editedFiles[path] ?? new Uint8Array()))) +const withoutEditedProperty = (inspection: typeof inspectedBefore) => inspection.objects.map((object) => ({ ...object, properties: object.name === 'SketchProbe' ? object.properties.filter((candidate) => candidate.name !== 'Exports') : object.properties })) +const semanticObjectsPreserved = isDeepStrictEqual(withoutEditedProperty(inspectedBefore), withoutEditedProperty(inspectedAfter)) +const editedDocumentXml = new TextDecoder().decode(editedFiles['Document.xml']) +const initialDocumentXml = new TextDecoder().decode(initialFiles['Document.xml']) +const dependencyTargets = (xml: string, objectName: string): string[] => { + const escapedName = objectName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + const match = xml.match(new RegExp(`]*Name="${escapedName}")[^>]*>([\\s\\S]*?)<\\/ObjectDeps>`)) + return match ? [...match[1].matchAll(//g)].map((entry) => entry[1]) : [] +} +const initialDependencyTargets = dependencyTargets(initialDocumentXml, 'SketchProbe') +const webDependencyTargets = dependencyTargets(editedDocumentXml, 'SketchProbe') +const targetMarkedTouched = /]*name="SketchProbe")(?=[^>]*Touched="1")[^>]*\/>/.test(editedDocumentXml) +const hiddenDependencyMetadataPreserved = isDeepStrictEqual(initialDependencyTargets, initial) && isDeepStrictEqual(webDependencyTargets, initial) +const decodedAfter = property(inspectedAfter) ? decodeFcstdPropertyValue(property(inspectedAfter)!) : undefined +const webOutputMatches = decodedAfter?.decoded === true && isDeepStrictEqual(decodedAfter.value, target) + +const nativeAfter = runNative('verify', webPath, resavedPath) +const resavedBytes = new Uint8Array(await readFile(resavedPath)) +const resavedFiles = unzipSync(resavedBytes) +const nativeResavedDependencyTargets = dependencyTargets(new TextDecoder().decode(resavedFiles['Document.xml']), 'SketchProbe') +const snapshots = [native.result, nativeAfter.result.reopened, nativeAfter.result.resaved] +const nativeOutputMatches = isDeepStrictEqual(native.result.object.value, initial) && [nativeAfter.result.reopened, nativeAfter.result.resaved].every((snapshot: any) => isDeepStrictEqual(snapshot.object.value, target)) +const objectSetPreserved = snapshots.every((snapshot: any) => isDeepStrictEqual(snapshot.objectSet, native.result.objectSet)) +const hiddenLinksPreserved = snapshots.every((snapshot: any) => isDeepStrictEqual(snapshot.object.outList, []) && isDeepStrictEqual(snapshot.targets.ExportA.inList, []) && isDeepStrictEqual(snapshot.targets.ExportB.inList, [])) +const shapeStructure = (shape: any) => { const { brepSha256: _brepSha256, ...structure } = shape; return structure } +const targetShapeStructurePreserved = snapshots.every((snapshot: any) => isDeepStrictEqual(shapeStructure(snapshot.targets.ExportA.shape), shapeStructure(native.result.targets.ExportA.shape)) && isDeepStrictEqual(shapeStructure(snapshot.targets.ExportB.shape), shapeStructure(native.result.targets.ExportB.shape))) +const ownerShapePreserved = snapshots.every((snapshot: any) => isDeepStrictEqual(snapshot.object.shape, native.result.object.shape)) +const nativeDependencyMetadataNormalized = isDeepStrictEqual(nativeResavedDependencyTargets, target) +const zeroUnknownDrift = opaqueEntriesPreserved && semanticObjectsPreserved && targetMarkedTouched && hiddenDependencyMetadataPreserved && nativeDependencyMetadataNormalized && webOutputMatches && nativeOutputMatches && objectSetPreserved && hiddenLinksPreserved && targetShapeStructurePreserved && ownerShapePreserved +const report = { + schemaVersion: 1, + status: zeroUnknownDrift ? 'pass' : 'fail', + baselineId: 'freecad-1.1.1-property-linklisthidden-roundtrip', + freecadVersion: native.freecadVersion, + gitCommit: native.gitCommit, + propertyType: 'App::PropertyLinkListHidden', + archives: { + nativeInitial: { path: '.cache/freecad/property-linklisthidden-roundtrip/native-initial.FCStd', bytes: nativeBytes.byteLength, sha256: createHash('sha256').update(nativeBytes).digest('hex') }, + webEdited: { path: '.cache/freecad/property-linklisthidden-roundtrip/web-edited.FCStd', bytes: editedBytes.byteLength, sha256: createHash('sha256').update(editedBytes).digest('hex') }, + nativeResaved: { path: '.cache/freecad/property-linklisthidden-roundtrip/native-resaved.FCStd', bytes: resavedBytes.byteLength, sha256: createHash('sha256').update(resavedBytes).digest('hex') }, + }, + nativeInitial: native.result, + web: { before: property(inspectedBefore), after: property(inspectedAfter), targetMarkedTouched, initialDependencyTargets, webDependencyTargets, hiddenDependencyMetadataPreserved, webOutputMatches, objectSetPreserved, semanticObjectsPreserved, opaquePathsPreserved: opaquePaths.length, opaqueEntriesPreserved }, + nativeAfter: nativeAfter.result, + classification: { requestedValue: target, webValue: decodedAfter?.value, reopenedValue: nativeAfter.result.reopened.object.value, resavedValue: nativeAfter.result.resaved.object.value, nativeOutputMatches, hiddenLinksPreserved, nativeResavedDependencyTargets, nativeDependencyMetadataNormalized, targetShapeStructurePreserved, ownerShapePreserved, nativeTargetBrepHashes: snapshots.map((snapshot: any) => ({ ExportA: snapshot.targets.ExportA.shape.brepSha256, ExportB: snapshot.targets.ExportB.shape.brepSha256 })), unknownSemanticDrift: !zeroUnknownDrift, zeroUnknownDrift }, +} +await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`) +console.log(JSON.stringify({ status: report.status, output: 'config/freecad-property-linklisthidden-roundtrip.json', values: report.classification, opaqueEntriesPreserved, semanticObjectsPreserved }, null, 2)) diff --git a/scripts/run-freecad-property-linklisthidden-success.mjs b/scripts/run-freecad-property-linklisthidden-success.mjs new file mode 100644 index 0000000..5f4f76e --- /dev/null +++ b/scripts/run-freecad-property-linklisthidden-success.mjs @@ -0,0 +1,27 @@ +import { existsSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import { spawnSync } from 'node:child_process' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const executable = process.env.FREECAD_CMD || resolve(root, '.cache/freecad/install-desktop/bin/FreeCADCmd') +const sysroot = resolve(root, '.cache/freecad/sysroot') +const scriptPath = resolve(root, 'scripts/freecad-property-linklisthidden-success.py') +const outputPath = resolve(root, 'config/freecad-property-linklisthidden-success.json') +if (!existsSync(executable)) throw new Error(`FreeCAD PropertyLinkListHidden executable is missing: ${executable}`) +const execution = spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), scriptPath], { + cwd: root, + encoding: 'utf8', + timeout: 120_000, + maxBuffer: 32 * 1024 * 1024, + env: { + ...process.env, + FREECAD_PROPERTY_LINKLISTHIDDEN_SUCCESS_OUTPUT: outputPath, + PYTHONPATH: `${resolve(sysroot, 'usr/lib/python3/dist-packages')}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`, + LD_LIBRARY_PATH: `${resolve(sysroot, 'usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}`, + }, +}) +const output = `${execution.stdout || ''}\n${execution.stderr || ''}` +if (execution.error || execution.status !== 0 || !output.includes('FREECAD_PROPERTY_LINKLISTHIDDEN_SUCCESS_RESULT=')) throw new Error(`FreeCAD PropertyLinkListHidden success probe failed with status ${execution.status}: ${execution.error?.message || output.trim()}`) +const report = JSON.parse(await readFile(outputPath, 'utf8')) +console.log(JSON.stringify({ status: report.status, output: 'config/freecad-property-linklisthidden-success.json', caseCount: report.caseCount, phases: Object.keys(report.cases?.[0]?.phases ?? {}) }, null, 2)) diff --git a/scripts/run-freecad-property-linksubhidden-failure.mjs b/scripts/run-freecad-property-linksubhidden-failure.mjs new file mode 100644 index 0000000..c04fc60 --- /dev/null +++ b/scripts/run-freecad-property-linksubhidden-failure.mjs @@ -0,0 +1,15 @@ +import { existsSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import { spawnSync } from 'node:child_process' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const executable = process.env.FREECAD_CMD || resolve(root, '.cache/freecad/install-desktop/bin/FreeCADCmd') +const sysroot = resolve(root, '.cache/freecad/sysroot') +const outputPath = resolve(root, 'config/freecad-property-linksubhidden-failure.json') +if (!existsSync(executable)) throw new Error(`FreeCAD PropertyLinkSubHidden executable is missing: ${executable}`) +const execution = spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), resolve(root, 'scripts/freecad-property-linksubhidden-failure.py')], { cwd: root, encoding: 'utf8', timeout: 120_000, maxBuffer: 32 * 1024 * 1024, env: { ...process.env, FREECAD_PROPERTY_LINKSUBHIDDEN_FAILURE_OUTPUT: outputPath, PYTHONPATH: `${resolve(sysroot, 'usr/lib/python3/dist-packages')}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`, LD_LIBRARY_PATH: `${resolve(sysroot, 'usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}` } }) +const output = `${execution.stdout || ''}\n${execution.stderr || ''}` +if (execution.error || execution.status !== 0 || !output.includes('FREECAD_PROPERTY_LINKSUBHIDDEN_FAILURE_RESULT=')) throw new Error(`FreeCAD PropertyLinkSubHidden failure probe failed with status ${execution.status}: ${execution.error?.message || output.trim()}`) +const report = JSON.parse(await readFile(outputPath, 'utf8')) +console.log(JSON.stringify({ status: report.status, output: 'config/freecad-property-linksubhidden-failure.json', caseCount: report.caseCount, failureNames: report.case.failures.map(({ name }) => name) }, null, 2)) diff --git a/scripts/run-freecad-property-linksubhidden-success.mjs b/scripts/run-freecad-property-linksubhidden-success.mjs new file mode 100644 index 0000000..683a7de --- /dev/null +++ b/scripts/run-freecad-property-linksubhidden-success.mjs @@ -0,0 +1,16 @@ +import { existsSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import { spawnSync } from 'node:child_process' +import { resolve } from 'node:path' + +const root = resolve(new URL('..', import.meta.url).pathname) +const executable = process.env.FREECAD_CMD || resolve(root, '.cache/freecad/install-desktop/bin/FreeCADCmd') +const sysroot = resolve(root, '.cache/freecad/sysroot') +const scriptPath = resolve(root, 'scripts/freecad-property-linksubhidden-success.py') +const outputPath = resolve(root, 'config/freecad-property-linksubhidden-success.json') +if (!existsSync(executable)) throw new Error(`FreeCAD PropertyLinkSubHidden executable is missing: ${executable}`) +const execution = spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), scriptPath], { cwd: root, encoding: 'utf8', timeout: 120_000, maxBuffer: 32 * 1024 * 1024, env: { ...process.env, FREECAD_PROPERTY_LINKSUBHIDDEN_SUCCESS_OUTPUT: outputPath, PYTHONPATH: `${resolve(sysroot, 'usr/lib/python3/dist-packages')}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`, LD_LIBRARY_PATH: `${resolve(sysroot, 'usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}` } }) +const output = `${execution.stdout || ''}\n${execution.stderr || ''}` +if (execution.error || execution.status !== 0 || !output.includes('FREECAD_PROPERTY_LINKSUBHIDDEN_SUCCESS_RESULT=')) throw new Error(`FreeCAD PropertyLinkSubHidden success probe failed with status ${execution.status}: ${execution.error?.message || output.trim()}`) +const report = JSON.parse(await readFile(outputPath, 'utf8')) +console.log(JSON.stringify({ status: report.status, output: 'config/freecad-property-linksubhidden-success.json', caseCount: report.caseCount, phases: Object.keys(report.cases?.[0]?.phases ?? {}) }, null, 2)) diff --git a/src/App.tsx b/src/App.tsx index b4c1e08..cc4c08c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -966,7 +966,7 @@ function TaskPanel({ workbench, facade, showNotice }: { workbench: Workbench; fa } -function PropertyPanel({ facade, objectId, scope, showNotice }: { facade: BitBybitWebCadFacade; objectId: string; scope: 'data' | 'view'; showNotice: (message: string) => void }) { +export function PropertyPanel({ facade, objectId, scope, showNotice }: { facade: BitBybitWebCadFacade; objectId: string; scope: 'data' | 'view'; showNotice: (message: string) => void }) { const object = facade.app.document.getObject(objectId) if (!object) return
No object selected
const properties = object.properties.filter((property) => property.scope === scope && !property.hidden && !isFreecadPropertyHidden(property.nativeStatus)) diff --git a/src/chromePropertyLinkListHiddenHarness.tsx b/src/chromePropertyLinkListHiddenHarness.tsx new file mode 100644 index 0000000..5309a6c --- /dev/null +++ b/src/chromePropertyLinkListHiddenHarness.tsx @@ -0,0 +1,173 @@ +import { useEffect, useState } from 'react' +import { createRoot } from 'react-dom/client' +import { PropertyPanel } from './App' +import { decodeFcstdPropertyValue } from './facade/fcstd' +import { createProductionFacade } from './facade/productionFacade' +import { encodeFreecadPropertyStatus, isFreecadPropertyHidden } from './facade/propertyStatus' +import type { BitBybitWebCadFacade, DocumentSnapshot } from './facade/types' +import './styles.css' + +type WorkerTrace = { constructed: Array<{ name: string; url: string }>; requestTypes: string[]; terminated: string[] } +type PropertyLinkListHiddenReport = { + schemaVersion: 1 + status: 'running' | 'pass' | 'failed' + browserId: 'chrome' + userAgent: string + crossOriginIsolated: boolean + worker?: WorkerTrace + ui?: { visibleLabelInput: boolean; hiddenControlCountBefore: number; hiddenControlCountAfter: number; hiddenTextPresentBefore: boolean; hiddenTextPresentAfter: boolean; hiddenStatus: boolean; beforeValue: string[]; afterValue: string[]; documentVersion: number; dirty: boolean; objectState: string | null; dependencyCount: number; errors: string[] } + persistence?: { mode: string; sqliteWasm: boolean; opfs: boolean; savedMode: string; loadedValue: string[] | null; loadedType: string | null; loadedDependencyCount: number | null; checkpointVersion: number | null; recoveryIntegrity: string; fcstdElement: string | null; fcstdValue: string[] | null; fcstdNativeStatus: number | null } + resource?: { byteLength: number; roundTrip: boolean; released: boolean; markerRemoved: boolean } + release?: { shapeCount: number; kernelReferenceCount: number; workerTerminated: boolean } + error?: string +} + +const hiddenStatus = encodeFreecadPropertyStatus(['PropHidden']) +const fixture = (): DocumentSnapshot => ({ + id: 'chrome-property-linklisthidden', label: 'Chrome PropertyLinkListHidden', version: 1, dirty: false, readOnly: false, units: 'mm', + tree: [ + { id: 'SketchProbe', label: 'Sketch probe', type: 'sketch', state: 'valid' }, + { id: 'ExportA', label: 'Export A', type: 'feature', state: 'valid' }, + { id: 'ExportB', label: 'Export B', type: 'feature', state: 'valid' }, + ], + objects: [ + { id: 'SketchProbe', typeId: 'Sketcher::SketchObject', properties: [ + { name: 'Label', label: 'Label', group: 'Base', scope: 'data', type: 'App::PropertyString', value: 'Sketch probe', recompute: false }, + { name: 'Exports', label: 'Exports', group: 'Sketch', scope: 'data', type: 'App::PropertyLinkListHidden', value: ['ExportA', 'ExportB'], nativeStatus: hiddenStatus, recompute: true }, + ] }, + { id: 'ExportA', typeId: 'Part::Feature', properties: [] }, + { id: 'ExportB', typeId: 'Part::Feature', properties: [] }, + ], + dependencies: [], + recompute: { generation: 0, status: 'idle', objectStates: { SketchProbe: 'up-to-date', ExportA: 'up-to-date', ExportB: 'up-to-date' }, dirtyObjects: [], order: [], errors: [] }, +}) + +const hiddenProperty = (facade: BitBybitWebCadFacade) => facade.app.document.getObject('SketchProbe')?.properties.find(({ name }) => name === 'Exports') +const valueOf = (facade: BitBybitWebCadFacade) => [...(hiddenProperty(facade)?.value as string[])] + +function HarnessPanel({ facade, errors }: { facade: BitBybitWebCadFacade; errors: string[] }) { + const [, setRevision] = useState(0) + useEffect(() => facade.subscribe((event) => { if (event.type === 'state.changed') setRevision((revision) => revision + 1) }), [facade]) + return
errors.push(message)} />
+} + +const waitFor = async (predicate: () => boolean, message: string, timeoutMs = 30_000) => { + const deadline = performance.now() + timeoutMs + while (performance.now() < deadline) { if (predicate()) return; await new Promise((resolve) => setTimeout(resolve, 25)) } + throw new Error(`Timed out waiting for ${message}.`) +} + +const trace: WorkerTrace = { constructed: [], requestTypes: [], terminated: [] } +const BrowserWorker = globalThis.Worker +class TrackedWorker extends BrowserWorker { + private readonly traceName: string + constructor(scriptURL: string | URL, options?: WorkerOptions) { super(scriptURL, options); this.traceName = options?.name ?? 'unnamed'; trace.constructed.push({ name: this.traceName, url: String(scriptURL) }) } + override postMessage(message: unknown, transferOrOptions?: StructuredSerializeOptions | Transferable[]) { + if (message && typeof message === 'object' && 'type' in message && typeof message.type === 'string') trace.requestTypes.push(message.type) + if (Array.isArray(transferOrOptions)) super.postMessage(message, transferOrOptions) + else if (transferOrOptions) super.postMessage(message, transferOrOptions) + else super.postMessage(message) + } + override terminate() { trace.terminated.push(this.traceName); super.terminate() } +} +Object.defineProperty(globalThis, 'Worker', { configurable: true, writable: true, value: TrackedWorker }) + +const initial = ['ExportA', 'ExportB'] +const target = ['ExportB', 'ExportA', 'ExportB'] +const report: PropertyLinkListHiddenReport = { schemaVersion: 1, status: 'running', browserId: 'chrome', userAgent: navigator.userAgent, crossOriginIsolated: globalThis.crossOriginIsolated === true } +const errors: string[] = [] +const facade = createProductionFacade({ initialDocument: fixture(), initialSelectedObjectIds: ['SketchProbe'] }) +const root = createRoot(document.getElementById('root')!) +root.render() + +try { + await waitFor(() => document.querySelector('input[aria-label="Label"]') !== null, 'the production PropertyPanel') + await waitFor(() => facade.project.capabilities().mode === 'sqlite-opfs', 'the SQLite OPFS Worker') + const hiddenControlCountBefore = document.querySelectorAll('[aria-label="Exports"]').length + const hiddenTextPresentBefore = document.querySelector('.properties-scroll')?.textContent?.includes('Exports') === true + const beforeValue = valueOf(facade) + facade.app.document.setProperty({ objectId: 'SketchProbe', propertyName: 'Exports', value: target, expectedDocumentVersion: 1 }) + await waitFor(() => facade.app.document.getActive().version === 2, 'the hidden property commit') + await new Promise((resolve) => requestAnimationFrame(() => resolve(undefined))) + const edited = facade.app.document.getActive() + report.ui = { + visibleLabelInput: document.querySelector('input[aria-label="Label"]')?.value === 'Sketch probe', + hiddenControlCountBefore, + hiddenControlCountAfter: document.querySelectorAll('[aria-label="Exports"]').length, + hiddenTextPresentBefore, + hiddenTextPresentAfter: document.querySelector('.properties-scroll')?.textContent?.includes('Exports') === true, + hiddenStatus: isFreecadPropertyHidden(hiddenProperty(facade)?.nativeStatus), + beforeValue, + afterValue: valueOf(facade), + documentVersion: edited.version, + dirty: edited.dirty, + objectState: edited.recompute?.objectStates.SketchProbe ?? null, + dependencyCount: facade.app.document.getDependencies().length, + errors, + } + + const saved = await facade.project.save() + const loaded = await facade.project.load(edited.id) + const checkpoint = await facade.project.loadCheckpoint(edited.id, edited.version) + const recovery = await facade.project.recovery(edited.id) + const loadedProperty = loaded?.objects.find(({ id }) => id === 'SketchProbe')?.properties.find(({ name }) => name === 'Exports') + const archive = facade.project.fcstd.serializeMetadata(edited) + const archiveProperty = facade.project.fcstd.inspect(archive).objects.find(({ name }) => name === 'SketchProbe')?.properties.find(({ name }) => name === 'Exports') + const decoded = archiveProperty ? decodeFcstdPropertyValue(archiveProperty) : undefined + const capabilities = facade.project.capabilities() + report.persistence = { + mode: capabilities.mode, + sqliteWasm: capabilities.sqliteWasm, + opfs: capabilities.opfs, + savedMode: saved.mode, + loadedValue: Array.isArray(loadedProperty?.value) && loadedProperty.value.every((entry) => typeof entry === 'string') ? [...loadedProperty.value] : null, + loadedType: loadedProperty?.type ?? null, + loadedDependencyCount: loaded?.dependencies?.length ?? null, + checkpointVersion: checkpoint?.version ?? null, + recoveryIntegrity: recovery.integrity, + fcstdElement: archiveProperty?.element ?? null, + fcstdValue: decoded?.decoded && Array.isArray(decoded.value) && decoded.value.every((entry) => typeof entry === 'string') ? [...decoded.value] : null, + fcstdNativeStatus: archiveProperty?.nativeStatus ?? null, + } + + const payload = new TextEncoder().encode('PropertyLinkListHidden Chrome resource bytes') + const stored = await facade.project.resource.put(payload, 'application/octet-stream') + const restored = await facade.project.resource.get(stored.hash) + await facade.project.resource.release(stored.hash) + const released = await facade.project.resource.get(stored.hash) === null + const directory = await navigator.storage.getDirectory() + const markerName = 'property-linklisthidden-chrome-marker.json' + const marker = await directory.getFileHandle(markerName, { create: true }) + const writable = await marker.createWritable() + await writable.write(JSON.stringify({ suite: 'PROP-LINKLISTHIDDEN-H', value: target })) + await writable.close(); await directory.removeEntry(markerName) + let markerRemoved = false + try { await directory.getFileHandle(markerName) } catch (error) { markerRemoved = error instanceof DOMException && error.name === 'NotFoundError' } + report.resource = { byteLength: stored.byteLength, roundTrip: restored !== null && new TextDecoder().decode(restored) === new TextDecoder().decode(payload), released, markerRemoved } +} catch (error) { + report.error = error instanceof Error ? error.stack || error.message : String(error) +} finally { + root.unmount(); facade.geometry.dispose() + await facade.project.dispose().catch((error: unknown) => { report.error = `${report.error ? `${report.error}\n` : ''}${error instanceof Error ? error.stack || error.message : String(error)}` }) + Object.defineProperty(globalThis, 'Worker', { configurable: true, writable: true, value: BrowserWorker }) + const geometry = facade.geometry.capabilities() + report.worker = trace + report.release = { shapeCount: geometry.shapeCount, kernelReferenceCount: geometry.kernelReferenceCount, workerTerminated: trace.terminated.includes('bitbybit-persistence') } +} + +const same = (left: string[] | null | undefined, right: string[]) => Array.isArray(left) && left.length === right.length && left.every((entry, index) => entry === right[index]) +const requiredWorkerRequests = ['initialize', 'save-document', 'load-document', 'load-checkpoint', 'recovery-report', 'put-resource', 'get-resource', 'release-resource', 'dispose'] +report.status = !report.error && report.crossOriginIsolated + && report.worker?.constructed.some(({ name }) => name === 'bitbybit-persistence') === true + && requiredWorkerRequests.every((type) => report.worker?.requestTypes.includes(type)) + && report.ui?.visibleLabelInput === true && report.ui.hiddenControlCountBefore === 0 && report.ui.hiddenControlCountAfter === 0 && !report.ui.hiddenTextPresentBefore && !report.ui.hiddenTextPresentAfter && report.ui.hiddenStatus && same(report.ui.beforeValue, initial) && same(report.ui.afterValue, target) && report.ui.documentVersion === 2 && report.ui.dirty && report.ui.objectState === 'touched' && report.ui.dependencyCount === 0 && report.ui.errors.length === 0 + && report.persistence?.mode === 'sqlite-opfs' && report.persistence.sqliteWasm && report.persistence.opfs && report.persistence.savedMode === 'sqlite-opfs' && same(report.persistence.loadedValue, target) && report.persistence.loadedType === 'App::PropertyLinkListHidden' && report.persistence.loadedDependencyCount === 0 && report.persistence.checkpointVersion === 2 && report.persistence.recoveryIntegrity === 'ok' && report.persistence.fcstdElement === 'LinkList' && same(report.persistence.fcstdValue, target) && report.persistence.fcstdNativeStatus === hiddenStatus + && report.resource !== undefined && report.resource.byteLength > 0 && report.resource.roundTrip && report.resource.released && report.resource.markerRemoved + && report.release?.shapeCount === 0 && report.release.kernelReferenceCount === 0 && report.release.workerTerminated ? 'pass' : 'failed' + +document.documentElement.dataset.status = report.status +document.body.textContent = JSON.stringify(report, null, 2) +await fetch('/__chrome-property-linklisthidden-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) }) +if (report.status !== 'pass') throw new Error(report.error || 'Chrome PropertyLinkListHidden harness failed.') + +export {} diff --git a/src/facade/fcstd.ts b/src/facade/fcstd.ts index a9e2667..4dc46a7 100644 --- a/src/facade/fcstd.ts +++ b/src/facade/fcstd.ts @@ -59,6 +59,7 @@ export type FcstdNumericPropertyEdit = { objectName: string; propertyName: strin export type FcstdMeasureAreaEdit = { objectName: string; elements: LinkSubListValue; area: number; expectedElements?: LinkSubListValue; expectedArea?: number } export type FcstdBoolListEdit = { objectName: string; propertyName: string; value: boolean[]; expectedValue?: boolean[] } export type FcstdIntegerSetEdit = { objectName: string; propertyName: string; value: number[]; expectedValue?: number[] } +export type FcstdLinkListHiddenEdit = { objectName: string; propertyName: 'Exports'; value: string[]; expectedValue?: string[] } export type FcstdColorEdit = { objectName: string; propertyName: string; value: ColorValue; expectedValue?: ColorValue } export type FcstdColorListEdit = { objectName: string; propertyName: string; value: ColorListValue; expectedValue?: ColorListValue } export type FcstdDirectionEdit = { objectName: string; propertyName: string; value: VectorValue; expectedValue?: VectorValue } @@ -290,6 +291,7 @@ const recognizedTypeIds = new Set([ 'PartDesign::ShapeBinder', 'PartDesign::SubShapeBinder', 'Sketcher::SketchObject', + 'Sketcher::SketchObjectPython', 'Sketcher::SketchObjectSF', 'Mesh::Import', 'App::DocumentObjectFileIncluded', @@ -437,6 +439,8 @@ const guiPropertyXml = (property: ObjectPropertySnapshot) => { return '' } const nativeDataPropertyNames = new Map>([ + ['Sketcher::SketchObject', new Set(['Exports'])], + ['Sketcher::SketchObjectPython', new Set(['Exports'])], ['Path::Feature', new Set(['Path'])], ['Robot::TrajectoryDressUpObject', new Set(['Acceleration'])], ['Measure::MeasureArea', new Set(['Area', 'Elements'])], @@ -1135,7 +1139,7 @@ const propertyXml = (property: ObjectPropertySnapshot, native: boolean, objectId if (property.value !== null && typeof property.value !== 'string') throw new TypeError(`FCStd Link property ${property.name} requires an object name.`) return `` } - if (property.type === 'App::PropertyLinkList') { + if (property.type === 'App::PropertyLinkList' || property.type === 'App::PropertyLinkListHidden') { if (!Array.isArray(property.value) || property.value.some((entry) => typeof entry !== 'string')) throw new TypeError(`FCStd LinkList property ${property.name} requires an object-name list.`) const links = (property.value as string[]).map((entry) => ``).join('') return `${links}` @@ -1249,6 +1253,7 @@ const propertyXml = (property: ObjectPropertySnapshot, native: boolean, objectId 'App::PropertyLink': 'Link', 'App::PropertyLinkSub': 'LinkSub', 'App::PropertyLinkList': 'LinkList', + 'App::PropertyLinkListHidden': 'LinkList', 'App::PropertyVector': 'PropertyVector', 'App::PropertyDirection': 'PropertyVector', 'App::PropertyPlacement': 'PropertyPlacement', @@ -2214,7 +2219,7 @@ export const decodeFcstdPropertyValue = (property: FcstdPropertySummary): FcstdD decoded: true, } if (property.typeId === 'App::PropertyLink' || property.typeId === 'App::PropertyLinkHidden') return { value: property.value || null, decoded: true } - if (property.typeId === 'App::PropertyLinkList' && property.links) return { value: [...property.links], decoded: true } + if ((property.typeId === 'App::PropertyLinkList' || property.typeId === 'App::PropertyLinkListHidden') && property.links) return { value: [...property.links], decoded: true } if (property.typeId === 'App::PropertyLinkSubList' && property.linkSubs) return { value: { schemaVersion: 1, entries: property.linkSubs.map((entry) => ({ ...entry })) }, decoded: true } if (property.typeId === 'Part::PropertyPartShape' && property.shapeResource) return { value: { ...property.shapeResource, format: 'brep' }, decoded: true } if (property.typeId === 'Path::PropertyPath') { @@ -2226,7 +2231,7 @@ export const decodeFcstdPropertyValue = (property: FcstdPropertySummary): FcstdD } } if (property.typeId === 'App::PropertyDirection' && property.element !== 'PropertyVector') return { value: property.value, decoded: false, error: `Property ${property.name} is not a native PropertyVector payload.` } - if (property.typeId === 'App::PropertyLinkSub' || property.typeId === 'App::PropertyLinkSubList' || property.typeId === 'App::PropertyLinkList' || property.typeId === 'App::PropertyBoolList' || property.typeId === 'App::PropertyFloatList' || property.typeId === 'App::PropertyIntegerList' || property.typeId === 'App::PropertyIntegerSet' || property.typeId === 'App::PropertyStringList' || property.typeId === 'App::PropertyVector' || property.typeId === 'App::PropertyDirection' || property.typeId === 'App::PropertyPlacement' || property.typeId === 'App::PropertyMultiTransform' || property.typeId === 'Part::PropertyGeometryList' || property.typeId === 'Sketcher::PropertyConstraintList') { + if (property.typeId === 'App::PropertyLinkSub' || property.typeId === 'App::PropertyLinkSubList' || property.typeId === 'App::PropertyLinkList' || property.typeId === 'App::PropertyLinkListHidden' || property.typeId === 'App::PropertyBoolList' || property.typeId === 'App::PropertyFloatList' || property.typeId === 'App::PropertyIntegerList' || property.typeId === 'App::PropertyIntegerSet' || property.typeId === 'App::PropertyStringList' || property.typeId === 'App::PropertyVector' || property.typeId === 'App::PropertyDirection' || property.typeId === 'App::PropertyPlacement' || property.typeId === 'App::PropertyMultiTransform' || property.typeId === 'Part::PropertyGeometryList' || property.typeId === 'Sketcher::PropertyConstraintList') { try { return { value: JSON.parse(property.value), decoded: true } } catch (error) { return { value: property.value, decoded: false, error: `Property ${property.name} contains invalid JSON: ${error instanceof Error ? error.message : String(error)}` } } } return { value: property.value, decoded: true } @@ -2410,6 +2415,57 @@ export const rewriteFcstdIntegerSetProperty = (bytes: Uint8Array, edit: FcstdInt return rewritten } +/** Rewrites one locked Sketcher Exports list without creating ordinary dependency metadata. */ +export const rewriteFcstdLinkListHiddenProperty = (bytes: Uint8Array, edit: FcstdLinkListHiddenEdit): Uint8Array => { + if (!edit.objectName.trim() || edit.propertyName !== 'Exports') throw new TypeError('FCStd PropertyLinkListHidden rewrite requires a Sketch object and its Exports property.') + const validateValue = (value: string[], context: string) => { + if (!Array.isArray(value) || !value.every((entry) => typeof entry === 'string' && entry.length > 0)) throw new TypeError(`FCStd PropertyLinkListHidden ${context} requires a non-empty object-name list.`) + } + const equal = (left: string[], right: string[]) => left.length === right.length && left.every((entry, index) => entry === right[index]) + validateValue(edit.value, 'value') + if (edit.expectedValue !== undefined) validateValue(edit.expectedValue, 'expectedValue') + + const inspection = inspectFcstdArchive(bytes) + const object = inspection.objects.find((candidate) => candidate.name === edit.objectName) + if (!object) throw new Error(`FCStd PropertyLinkListHidden object does not exist: ${edit.objectName}`) + if (object.typeId !== 'Sketcher::SketchObject' && object.typeId !== 'Sketcher::SketchObjectPython') throw new TypeError(`FCStd PropertyLinkListHidden host ${edit.objectName} must be a Sketcher object.`) + const declaredNames = new Set(inspection.objects.map((candidate) => candidate.name)) + for (const target of edit.value) if (!declaredNames.has(target)) throw new RangeError(`FCStd PropertyLinkListHidden target does not exist: ${target}`) + const summary = object.properties.find((candidate) => candidate.name === edit.propertyName) + if (!summary) throw new Error(`FCStd PropertyLinkListHidden property does not exist: ${edit.objectName}.${edit.propertyName}`) + if (summary.typeId !== 'App::PropertyLinkListHidden' || summary.element !== 'LinkList') throw new TypeError(`FCStd ${edit.objectName}.${edit.propertyName} must be App::PropertyLinkListHidden with a LinkList payload.`) + const decoded = decodeFcstdPropertyValue(summary) + if (!decoded.decoded || !Array.isArray(decoded.value) || !decoded.value.every((entry) => typeof entry === 'string')) throw new TypeError(`FCStd PropertyLinkListHidden ${edit.objectName}.${edit.propertyName} is not decodable.`) + if (edit.expectedValue !== undefined && !equal(decoded.value as string[], edit.expectedValue)) throw new RangeError(`FCStd PropertyLinkListHidden ${edit.objectName}.${edit.propertyName} does not match expectedValue.`) + + const entries = inspectZipDirectory(bytes, DEFAULT_FCSTD_LIMITS) + const files = unzipSync(bytes) + const documentPath = entries.find((entry) => entry.path.toLowerCase() === 'document.xml')?.path + if (!documentPath || !files[documentPath]) throw new Error('FCStd archive does not contain Document.xml.') + const parsed = parseXmlRoot(files[documentPath], documentPath, 'Document', DEFAULT_FCSTD_LIMITS) + const declarations = asArray((((parsed.root.Objects as Record | undefined)?.Object) as Record | Record[] | undefined)).filter((candidate) => attribute(candidate, 'name') === edit.objectName) + if (declarations.length !== 1) throw new Error(`FCStd PropertyLinkListHidden object declaration is not unique: ${edit.objectName}`) + declarations[0]['@_Touched'] = '1' + const objectData = asArray((((parsed.root.ObjectData as Record | undefined)?.Object) as Record | Record[] | undefined)).filter((candidate) => attribute(candidate, 'name') === edit.objectName) + if (objectData.length !== 1) throw new Error(`FCStd PropertyLinkListHidden object data is not unique: ${edit.objectName}`) + const properties = asArray(((((objectData[0].Properties as Record | undefined)?.Property)) as Record | Record[] | undefined)) + const candidates = properties.filter((candidate) => attribute(candidate, 'name') === edit.propertyName && attribute(candidate, 'type') === 'App::PropertyLinkListHidden') + if (candidates.length !== 1 || !candidates[0].LinkList || typeof candidates[0].LinkList !== 'object' || Array.isArray(candidates[0].LinkList)) throw new TypeError(`FCStd PropertyLinkListHidden ${edit.objectName}.${edit.propertyName} has no unique LinkList payload.`) + const payload = candidates[0].LinkList as Record + payload['@_count'] = String(edit.value.length) + if (edit.value.length === 0) delete payload.Link + else payload.Link = edit.value.map((value) => ({ '@_value': value })) + const documentXml = new XMLBuilder({ ignoreAttributes: false, attributeNamePrefix: '@_', format: false, processEntities: false, suppressBooleanAttributes: false, suppressEmptyNode: true }).build({ Document: parsed.root }) + files[documentPath] = new TextEncoder().encode(documentXml) + const output: Record = {} + for (const entry of entries) output[entry.path] = new Uint8Array(files[entry.path]) + const rewritten = new Uint8Array(zipSync(output, { level: 6 })) + const rewrittenSummary = inspectFcstdArchive(rewritten).objects.find((candidate) => candidate.name === edit.objectName)?.properties.find((candidate) => candidate.name === edit.propertyName) + const rewrittenValue = rewrittenSummary ? decodeFcstdPropertyValue(rewrittenSummary) : undefined + if (!rewrittenValue?.decoded || !Array.isArray(rewrittenValue.value) || !equal(rewrittenValue.value as string[], edit.value)) throw new Error(`FCStd PropertyLinkListHidden ${edit.objectName}.${edit.propertyName} did not rewrite exactly.`) + return rewritten +} + /** Rewrites one native App::PropertyColor packed RGBA value while preserving every other archive entry. */ export const rewriteFcstdColorProperty = (bytes: Uint8Array, edit: FcstdColorEdit): Uint8Array => { if (!edit.objectName.trim() || !edit.propertyName.trim()) throw new TypeError('FCStd PropertyColor rewrite requires non-empty object and property names.') diff --git a/src/facade/index.ts b/src/facade/index.ts index 1efed49..515c43c 100644 --- a/src/facade/index.ts +++ b/src/facade/index.ts @@ -105,8 +105,8 @@ export type { AttachmentFrame, AttachmentMapMode, AttachmentSupport } from './at export { redirectBodyTips, resolveBodyTip } from './bodyRules' export type { BodyFeatureState } from './bodyRules' export type { RecomputeExecutionError, RecomputeExecutionOptions, RecomputeExecutionResult, RecomputeExecutionStatus, RecomputeGeometryRuntime, RecomputeNodeContext, RecomputeNodeExecutor, RecomputeNodeResult, RecomputeProgress } from './recomputeEngine' -export { DEFAULT_FCSTD_LIMITS, decodeFcstdPathProperty, decodeFcstdPropertyValue, encodeFcstdPathResource, extractFcstdShapeResources, inspectFcstdArchive, rewriteFcstdBoolListProperty, rewriteFcstdColorListProperty, rewriteFcstdColorProperty, rewriteFcstdDirectionProperty, rewriteFcstdFileProperty, rewriteFcstdIncludedFileProperty, rewriteFcstdMeasureArea, rewriteFcstdMetadataArchive, rewriteFcstdNumericProperty, rewriteFcstdPathProperty, serializeElementMap2Resource, serializeFcstdMetadataArchive, serializeStringHasherTableResource, storeFcstdShapeResources } from './fcstd' -export type { FcstdArchiveLimits, FcstdBoolListEdit, FcstdColorEdit, FcstdColorListEdit, FcstdCompatibilityReport, FcstdDecodedPropertyValue, FcstdDirectionEdit, FcstdElementMapResource, FcstdEntryMetadata, FcstdEntryRole, FcstdFileEdit, FcstdGuiInspection, FcstdIncludedFileEdit, FcstdIncludedFileResourceEdit, FcstdInspection, FcstdMeasureAreaEdit, FcstdNumericPropertyEdit, FcstdObjectSummary, FcstdObjectSupport, FcstdPathEdit, FcstdPropertySummary, FcstdShapeResource, FcstdShapeResourcePayload, FcstdStoredShapeResource, FcstdStringHasherResource, FcstdWriteOptions } from './fcstd' +export { DEFAULT_FCSTD_LIMITS, decodeFcstdPathProperty, decodeFcstdPropertyValue, encodeFcstdPathResource, extractFcstdShapeResources, inspectFcstdArchive, rewriteFcstdBoolListProperty, rewriteFcstdColorListProperty, rewriteFcstdColorProperty, rewriteFcstdDirectionProperty, rewriteFcstdFileProperty, rewriteFcstdIncludedFileProperty, rewriteFcstdLinkListHiddenProperty, rewriteFcstdMeasureArea, rewriteFcstdMetadataArchive, rewriteFcstdNumericProperty, rewriteFcstdPathProperty, serializeElementMap2Resource, serializeFcstdMetadataArchive, serializeStringHasherTableResource, storeFcstdShapeResources } from './fcstd' +export type { FcstdArchiveLimits, FcstdBoolListEdit, FcstdColorEdit, FcstdColorListEdit, FcstdCompatibilityReport, FcstdDecodedPropertyValue, FcstdDirectionEdit, FcstdElementMapResource, FcstdEntryMetadata, FcstdEntryRole, FcstdFileEdit, FcstdGuiInspection, FcstdIncludedFileEdit, FcstdIncludedFileResourceEdit, FcstdInspection, FcstdLinkListHiddenEdit, FcstdMeasureAreaEdit, FcstdNumericPropertyEdit, FcstdObjectSummary, FcstdObjectSupport, FcstdPathEdit, FcstdPropertySummary, FcstdShapeResource, FcstdShapeResourcePayload, FcstdStoredShapeResource, FcstdStringHasherResource, FcstdWriteOptions } from './fcstd' export { cloneStringHasherTable, migrateStringHasherSchema, parseStringHasherTable, StringHasherFlag, validateElementMap2StringHasherEvidence, validateStringHasherTable, writeStringHasherTable } from './stringHasher' export type { AnyStringHasherTable, StringHasherEntry, StringHasherTable, StringHasherTableV1, StringHasherValidationIssue, StringHasherValidationReport, ElementMap2StringHasherEvidenceIssue } from './stringHasher' export { appendElementMap2NameEntry, canonicalizeElementMap2, cloneElementMap2, createElementMap2MultiStageNameMapping, createElementMap2NameEntry, createElementMap2NameToken, elementMap2NameTokenToReference, elementMap2SemanticDigest, formatElementMap2NameToken, migrateElementMap2Schema, parseElementMap2, parseElementMap2MultiStageNameMapping, validateElementMap2, validateElementMap2MultiStageNameMapping, writeElementMap2, writeElementMap2MultiStageNameMapping } from './elementMap2' diff --git a/src/facade/mockFacade.ts b/src/facade/mockFacade.ts index e990e10..69e674b 100644 --- a/src/facade/mockFacade.ts +++ b/src/facade/mockFacade.ts @@ -45,7 +45,7 @@ import { DependencyGraph, createRecomputeSnapshot, type DependencyEdge } from '. import { convertQuantity, evaluateQuantityExpression, getUnit, quantityDimensionForUnit, quantityFromNumber, quantityFromUnit, type Quantity } from './units' import { carbonCopySketchGeometry, cloneSketch, cloneSketchConstraint, cloneSketchGeometry, createSketch, projectSketchGeometry, solveSketch, type SketchConstraint, type SketchExternalGeometry, type SketchGeometry, type SketchSnapshot } from './sketcher' import { createFacadeGeometryRecomputeExecutor, RecomputeCoordinator, sketchProfile, type RecomputeExecutionOptions } from './recomputeEngine' -import { decodeFcstdPathProperty, extractFcstdIncludedFileResources, extractFcstdShapeResources, inspectFcstdArchive, instantiateFcstdShapeResource, rewriteFcstdBoolListProperty, rewriteFcstdColorListProperty, rewriteFcstdColorProperty, rewriteFcstdDirectionProperty, rewriteFcstdFileProperty, rewriteFcstdFontProperty, rewriteFcstdIncludedFileProperty, rewriteFcstdIntegerSetProperty, rewriteFcstdMeasureArea, rewriteFcstdMetadataArchive, rewriteFcstdNumericProperty, rewriteFcstdPathProperty, serializeFcstdMetadataArchive, storeFcstdIncludedFileResources, storeFcstdShapeResources } from './fcstd' +import { decodeFcstdPathProperty, extractFcstdIncludedFileResources, extractFcstdShapeResources, inspectFcstdArchive, instantiateFcstdShapeResource, rewriteFcstdBoolListProperty, rewriteFcstdColorListProperty, rewriteFcstdColorProperty, rewriteFcstdDirectionProperty, rewriteFcstdFileProperty, rewriteFcstdFontProperty, rewriteFcstdIncludedFileProperty, rewriteFcstdIntegerSetProperty, rewriteFcstdLinkListHiddenProperty, rewriteFcstdMeasureArea, rewriteFcstdMetadataArchive, rewriteFcstdNumericProperty, rewriteFcstdPathProperty, serializeFcstdMetadataArchive, storeFcstdIncludedFileResources, storeFcstdShapeResources } from './fcstd' import type { FcstdWriteOptions } from './fcstd' import { buildDiagnosticTree, buildRecomputeDiagnostics, cloneDiagnostic, replaceRecomputeDiagnostics } from './diagnostics' import { cloneObjectTopologySnapshot, migrateDocumentTopologyReferences, parseTopoRef, resolveDocumentTopologyReference } from './topologyReferences' @@ -612,9 +612,9 @@ const validatePropertyValue = (document: DocumentSnapshot, property: ObjectPrope if (property.name === 'AttachmentOffset') validateAttachmentOffset(value) } if (property.type === 'App::PropertyMultiTransform') validateMultiTransformValue(value) - if (property.type === 'App::PropertyLinkList' || property.type === 'App::PropertyStringList') { + if (property.type === 'App::PropertyLinkList' || property.type === 'App::PropertyLinkListHidden' || property.type === 'App::PropertyStringList') { if (!Array.isArray(value) || !value.every((entry) => typeof entry === 'string')) throw new TypeError(`${property.label} requires a string list.`) - if (property.type === 'App::PropertyLinkList') { + if (property.type === 'App::PropertyLinkList' || property.type === 'App::PropertyLinkListHidden') { const knownIds = new Set(document.tree.flatMap((item) => [item.id, ...(item.children ?? [])])) if (value.some((entry) => !knownIds.has(entry))) throw new RangeError(`${property.label} contains a target that does not exist in this document.`) } @@ -1745,6 +1745,7 @@ export function createWebCadFacade(options: WebCadFacadeOptions = {}): BitBybitW rewriteMeasureArea: (bytes, edit) => rewriteFcstdMeasureArea(bytes, edit), rewriteBoolList: (bytes, edit) => rewriteFcstdBoolListProperty(bytes, edit), rewriteIntegerSet: (bytes, edit) => rewriteFcstdIntegerSetProperty(bytes, edit), + rewriteLinkListHidden: (bytes, edit) => rewriteFcstdLinkListHiddenProperty(bytes, edit), rewriteColor: (bytes, edit) => rewriteFcstdColorProperty(bytes, edit), rewriteColorList: (bytes, edit) => rewriteFcstdColorListProperty(bytes, edit), rewriteDirection: (bytes, edit) => rewriteFcstdDirectionProperty(bytes, edit), diff --git a/src/facade/types.ts b/src/facade/types.ts index 7f4bbac..716a351 100644 --- a/src/facade/types.ts +++ b/src/facade/types.ts @@ -3,7 +3,7 @@ import type { DependencyEdge, RecomputeSnapshot, RecomputePlan } from './depende import type { Quantity, QuantityDimension } from './units' import type { SketchConstraint, SketchExternalGeometry, SketchGeometry, SketchSnapshot, SketchSolveResult } from './sketcher' import type { RecomputeExecutionOptions, RecomputeExecutionResult } from './recomputeEngine' -import type { FcstdArchiveLimits, FcstdBoolListEdit, FcstdColorEdit, FcstdColorListEdit, FcstdDirectionEdit, FcstdFileEdit, FcstdFontEdit, FcstdIncludedFileEdit, FcstdIncludedFileResourcePayload, FcstdInspection, FcstdInstantiatedShape, FcstdIntegerSetEdit, FcstdMeasureAreaEdit, FcstdNumericPropertyEdit, FcstdPathEdit, FcstdShapeResourcePayload, FcstdStoredIncludedFileResource, FcstdStoredShapeResource, FcstdWriteOptions } from './fcstd' +import type { FcstdArchiveLimits, FcstdBoolListEdit, FcstdColorEdit, FcstdColorListEdit, FcstdDirectionEdit, FcstdFileEdit, FcstdFontEdit, FcstdIncludedFileEdit, FcstdIncludedFileResourcePayload, FcstdInspection, FcstdInstantiatedShape, FcstdIntegerSetEdit, FcstdLinkListHiddenEdit, FcstdMeasureAreaEdit, FcstdNumericPropertyEdit, FcstdPathEdit, FcstdShapeResourcePayload, FcstdStoredIncludedFileResource, FcstdStoredShapeResource, FcstdWriteOptions } from './fcstd' import type { NativeOcctHistoryCapabilities, NativeOcctHistoryProvider } from './nativeHistoryProtocol' import type { FacadeRuntimeProfile } from './runtimeProfile' import type { CamApi } from './cam' @@ -108,7 +108,7 @@ export type NativeEditablePropertyType = | 'App::PropertyString' | 'App::PropertyLength' | 'App::PropertyDistance' | 'App::PropertyAngle' | 'App::PropertyAcceleration' | 'App::PropertyArea' | 'App::PropertyForce' | 'App::PropertyHeatFlux' | 'App::PropertyQuantityConstraint' | 'App::PropertyBool' | 'App::PropertyBoolList' | 'App::PropertyEnumeration' | 'App::PropertyLink' | 'App::PropertyLinkHidden' | 'App::PropertyLinkSub' | 'App::PropertyLinkSubList' - | 'App::PropertyLinkList' | 'App::PropertyStringList' | 'App::PropertyFloatList' + | 'App::PropertyLinkList' | 'App::PropertyLinkListHidden' | 'App::PropertyStringList' | 'App::PropertyFloatList' | 'App::PropertyIntegerList' | 'App::PropertyIntegerSet' | 'App::PropertyVector' | 'App::PropertyDirection' | 'App::PropertyFile' | 'App::PropertyFileIncluded' | 'App::PropertyFont' | 'App::PropertyPlacement' | 'App::PropertyMultiTransform' | 'Path::PropertyPath' | 'Part::PropertyPartShape' | 'App::PropertyColor' | 'App::PropertyColorList' | 'App::PropertyPercent' | 'App::PropertyFloat' @@ -1030,6 +1030,7 @@ export interface BitBybitWebCadFacade { rewriteMeasureArea(bytes: Uint8Array, edit: FcstdMeasureAreaEdit): Uint8Array rewriteBoolList(bytes: Uint8Array, edit: FcstdBoolListEdit): Uint8Array rewriteIntegerSet(bytes: Uint8Array, edit: FcstdIntegerSetEdit): Uint8Array + rewriteLinkListHidden(bytes: Uint8Array, edit: FcstdLinkListHiddenEdit): Uint8Array rewriteColor(bytes: Uint8Array, edit: FcstdColorEdit): Uint8Array rewriteColorList(bytes: Uint8Array, edit: FcstdColorListEdit): Uint8Array rewriteDirection(bytes: Uint8Array, edit: FcstdDirectionEdit): Uint8Array diff --git a/tests/propertyLinkListHiddenFacade.test.ts b/tests/propertyLinkListHiddenFacade.test.ts new file mode 100644 index 0000000..36ae32f --- /dev/null +++ b/tests/propertyLinkListHiddenFacade.test.ts @@ -0,0 +1,71 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { unzipSync } from 'fflate' +import { createWebCadFacade } from '../src/facade/mockFacade' +import { decodeFcstdPropertyValue } from '../src/facade/fcstd' +import { encodeFreecadPropertyStatus, isFreecadPropertyHidden } from '../src/facade/propertyStatus' +import type { DocumentSnapshot, FacadeEvent } from '../src/facade/types' + +const hiddenStatus = encodeFreecadPropertyStatus(['PropHidden']) + +const fixture = (): DocumentSnapshot => ({ + id: 'property-linklisthidden-facade', label: 'PropertyLinkListHidden Facade', version: 1, dirty: false, readOnly: false, units: 'mm', + tree: [ + { id: 'SketchProbe', label: 'Sketch probe', type: 'sketch', state: 'valid' }, + { id: 'SketchPythonProbe', label: 'Sketch Python probe', type: 'sketch', state: 'valid' }, + { id: 'ExportA', label: 'Export A', type: 'feature', state: 'valid' }, + { id: 'ExportB', label: 'Export B', type: 'feature', state: 'valid' }, + ], + objects: [ + { id: 'SketchProbe', typeId: 'Sketcher::SketchObject', properties: [{ name: 'Exports', label: 'Exports', group: 'Sketch', scope: 'data', type: 'App::PropertyLinkListHidden', value: ['ExportA', 'ExportB'], nativeStatus: hiddenStatus, recompute: true }] }, + { id: 'SketchPythonProbe', typeId: 'Sketcher::SketchObjectPython', properties: [{ name: 'Exports', label: 'Exports', group: 'Sketch', scope: 'data', type: 'App::PropertyLinkListHidden', value: ['ExportA'], nativeStatus: hiddenStatus, recompute: true }] }, + { id: 'ExportA', typeId: 'Part::Feature', properties: [] }, + { id: 'ExportB', typeId: 'Part::Feature', properties: [] }, + ], + dependencies: [], + recompute: { generation: 0, status: 'idle', objectStates: { SketchProbe: 'up-to-date', SketchPythonProbe: 'up-to-date', ExportA: 'up-to-date', ExportB: 'up-to-date' }, dirtyObjects: [], order: [], errors: [] }, +}) + +const valueOf = (facade: ReturnType) => facade.app.document.getObject('SketchProbe')?.properties.find((property) => property.name === 'Exports')?.value + +test('App::PropertyLinkListHidden preserves ordered links without adding dependency edges', () => { + const facade = createWebCadFacade({ initialDocument: fixture(), initialSelectedObjectIds: ['SketchProbe'], runtimeMode: 'mock' }) + const events: FacadeEvent[] = [] + facade.subscribe((event) => { if (event.type === 'property.before-change' || event.type === 'property.changed' || event.type === 'transaction.committed') events.push(event) }) + + facade.app.document.setProperty({ objectId: 'SketchProbe', propertyName: 'Exports', value: ['ExportB', 'ExportA', 'ExportB'] }) + assert.deepEqual(valueOf(facade), ['ExportB', 'ExportA', 'ExportB']) + assert.equal(facade.app.document.getActive().version, 2) + assert.equal(facade.app.document.getActive().recompute?.objectStates.SketchProbe, 'touched') + assert.deepEqual(facade.app.document.getDependencies(), []) + assert.deepEqual(events.map((event) => event.type), ['property.before-change', 'property.changed', 'transaction.committed']) + assert.equal(isFreecadPropertyHidden(hiddenStatus), true) + + const beforeFailure = facade.app.document.getActive() + assert.throws(() => facade.app.document.setProperty({ objectId: 'SketchProbe', propertyName: 'Exports', value: ['MissingExport'] }), /target that does not exist/) + assert.throws(() => facade.app.document.setProperty({ objectId: 'SketchProbe', propertyName: 'Exports', value: [1] as unknown as string[] }), /string list/) + assert.deepEqual(facade.app.document.getActive(), beforeFailure) + + const archive = facade.project.fcstd.serializeMetadata(facade.app.document.getActive()) + const xml = new TextDecoder().decode(unzipSync(archive)['Document.xml']) + assert.match(xml, //) + assert.match(xml, //) + assert.match(xml, new RegExp(`<\/LinkList><\/Property>`)) + assert.doesNotMatch(xml, /]+(?:group|doc|attr|ro|hide)=/) + const object = facade.project.fcstd.inspect(archive).objects.find(({ name }) => name === 'SketchProbe') + assert.equal(object?.support, 'recognized') + const summary = object?.properties.find(({ name }) => name === 'Exports') + assert.deepEqual({ typeId: summary?.typeId, element: summary?.element, nativeStatus: summary?.nativeStatus, statusNames: summary?.statusNames }, { typeId: 'App::PropertyLinkListHidden', element: 'LinkList', nativeStatus: hiddenStatus, statusNames: ['PropHidden'] }) + assert.deepEqual(decodeFcstdPropertyValue(summary!), { value: ['ExportB', 'ExportA', 'ExportB'], decoded: true }) + const pythonObject = facade.project.fcstd.inspect(archive).objects.find(({ name }) => name === 'SketchPythonProbe') + const pythonSummary = pythonObject?.properties.find(({ name }) => name === 'Exports') + assert.equal(pythonObject?.support, 'recognized') + assert.deepEqual({ typeId: pythonSummary?.typeId, element: pythonSummary?.element, nativeStatus: pythonSummary?.nativeStatus }, { typeId: 'App::PropertyLinkListHidden', element: 'LinkList', nativeStatus: hiddenStatus }) + assert.deepEqual(decodeFcstdPropertyValue(pythonSummary!), { value: ['ExportA'], decoded: true }) + + const rewritten = facade.project.fcstd.rewriteLinkListHidden(archive, { objectName: 'SketchProbe', propertyName: 'Exports', value: ['ExportA', 'ExportB', 'ExportA'], expectedValue: ['ExportB', 'ExportA', 'ExportB'] }) + const rewrittenSummary = facade.project.fcstd.inspect(rewritten).objects.find(({ name }) => name === 'SketchProbe')?.properties.find(({ name }) => name === 'Exports') + assert.deepEqual(decodeFcstdPropertyValue(rewrittenSummary!), { value: ['ExportA', 'ExportB', 'ExportA'], decoded: true }) + assert.throws(() => facade.project.fcstd.rewriteLinkListHidden(archive, { objectName: 'SketchProbe', propertyName: 'Exports', value: ['MissingExport'], expectedValue: ['ExportB', 'ExportA', 'ExportB'] }), /target does not exist/) + assert.throws(() => facade.project.fcstd.rewriteLinkListHidden(archive, { objectName: 'SketchProbe', propertyName: 'Exports', value: ['ExportA'], expectedValue: ['ExportA'] }), /does not match expectedValue/) +}) diff --git a/tests/propertyLinkListHiddenTransaction.test.ts b/tests/propertyLinkListHiddenTransaction.test.ts new file mode 100644 index 0000000..bb78123 --- /dev/null +++ b/tests/propertyLinkListHiddenTransaction.test.ts @@ -0,0 +1,79 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { createWebCadFacade } from '../src/facade/mockFacade' +import { encodeFreecadPropertyStatus } from '../src/facade/propertyStatus' +import type { DocumentSnapshot, FacadeEvent } from '../src/facade/types' + +const fixture = (): DocumentSnapshot => ({ + id: 'property-linklisthidden-transaction', label: 'PropertyLinkListHidden Transaction', version: 1, dirty: false, readOnly: false, units: 'mm', + tree: [ + { id: 'SketchProbe', label: 'Sketch probe', type: 'sketch', state: 'valid' }, + { id: 'ExportA', label: 'Export A', type: 'feature', state: 'valid' }, + { id: 'ExportB', label: 'Export B', type: 'feature', state: 'valid' }, + ], + objects: [ + { id: 'SketchProbe', typeId: 'Sketcher::SketchObject', properties: [{ name: 'Exports', label: 'Exports', group: 'Sketch', scope: 'data', type: 'App::PropertyLinkListHidden', value: ['ExportA', 'ExportB'], nativeStatus: encodeFreecadPropertyStatus(['PropHidden']), recompute: true }] }, + { id: 'ExportA', typeId: 'Part::Feature', properties: [] }, + { id: 'ExportB', typeId: 'Part::Feature', properties: [] }, + ], + dependencies: [], + recompute: { generation: 0, status: 'idle', objectStates: { SketchProbe: 'up-to-date', ExportA: 'up-to-date', ExportB: 'up-to-date' }, dirtyObjects: [], order: [], errors: [] }, +}) + +const valueOf = (facade: ReturnType) => facade.app.document.getObject('SketchProbe')?.properties.find((property) => property.name === 'Exports')?.value as string[] + +test('App::PropertyLinkListHidden transaction closes undo, redo, stale, cancel, failure, hidden dependencies and resources', async () => { + const facade = createWebCadFacade({ initialDocument: fixture(), runtimeMode: 'mock' }) + const events: FacadeEvent[] = [] + facade.subscribe((event) => { if (event.type === 'property.before-change' || event.type === 'property.changed' || event.type === 'transaction.committed') events.push(event) }) + const resourcesBefore = facade.geometry.capabilities() + const requested = ['ExportB', 'ExportA', 'ExportB'] + facade.app.document.setProperty({ objectId: 'SketchProbe', propertyName: 'Exports', value: requested, expectedDocumentVersion: 1 }) + requested[0] = 'ExportA' + assert.deepEqual(valueOf(facade), ['ExportB', 'ExportA', 'ExportB']) + const exposed = valueOf(facade) + exposed[0] = 'ExportA' + assert.deepEqual(valueOf(facade), ['ExportB', 'ExportA', 'ExportB']) + assert.equal(facade.app.document.getActive().version, 2) + assert.equal(facade.app.document.getActive().dirty, true) + assert.equal(facade.app.document.getActive().recompute?.objectStates.SketchProbe, 'touched') + assert.deepEqual(facade.app.document.getDependencies(), []) + assert.deepEqual(events.map(({ type }) => type), ['property.before-change', 'property.changed', 'transaction.committed']) + assert.equal(new Set(events.map((event) => 'transactionId' in event ? event.transactionId : '')).size, 1) + const committed = events[2] + assert.equal(committed.type, 'transaction.committed') + if (committed.type === 'transaction.committed') assert.deepEqual({ operation: committed.operation, beforeVersion: committed.beforeVersion, afterVersion: committed.afterVersion }, { operation: 'property.set', beforeVersion: 1, afterVersion: 2 }) + + facade.history.undo() + assert.deepEqual(valueOf(facade), ['ExportA', 'ExportB']) + assert.equal(facade.app.document.getActive().version, 1) + assert.deepEqual(facade.app.document.getDependencies(), []) + assert.equal(facade.history.canUndo(), false) + assert.equal(facade.history.canRedo(), true) + facade.history.redo() + assert.deepEqual(valueOf(facade), ['ExportB', 'ExportA', 'ExportB']) + assert.equal(facade.app.document.getActive().version, 2) + assert.deepEqual(facade.app.document.getDependencies(), []) + assert.equal(facade.history.canUndo(), true) + assert.equal(facade.history.canRedo(), false) + + const stable = JSON.stringify(facade.app.document.getActive()) + const eventCount = events.length + assert.throws(() => facade.app.document.setProperty({ objectId: 'SketchProbe', propertyName: 'Exports', value: ['ExportA'], expectedDocumentVersion: 1 }), /Stale document version: expected 1, current 2/) + const cancelled = new AbortController() + cancelled.abort() + assert.throws(() => facade.app.document.setProperty({ objectId: 'SketchProbe', propertyName: 'Exports', value: ['ExportA'], expectedDocumentVersion: 2, signal: cancelled.signal }), (error: unknown) => error instanceof DOMException && error.name === 'AbortError') + assert.throws(() => facade.app.document.setProperty({ objectId: 'SketchProbe', propertyName: 'Exports', value: ['MissingExport'] }), /target that does not exist/) + assert.throws(() => facade.app.document.setProperty({ objectId: 'SketchProbe', propertyName: 'Exports', value: [1] as unknown as string[] }), /string list/) + assert.equal(JSON.stringify(facade.app.document.getActive()), stable) + assert.equal(events.length, eventCount) + assert.equal(facade.history.canUndo(), true) + assert.equal(facade.history.canRedo(), false) + + const resourcesAfter = facade.geometry.capabilities() + assert.deepEqual( + { shapeCount: resourcesAfter.shapeCount, kernelReferenceCount: resourcesAfter.kernelReferenceCount, releasedShapeCount: resourcesAfter.releasedShapeCount }, + { shapeCount: resourcesBefore.shapeCount, kernelReferenceCount: resourcesBefore.kernelReferenceCount, releasedShapeCount: resourcesBefore.releasedShapeCount }, + ) + await facade.project.dispose() +}) diff --git a/后续工作.txt b/后续工作.txt index 58de4c3..38bec5f 100644 --- a/后续工作.txt +++ b/后续工作.txt @@ -5337,3 +5337,132 @@ git diff --check - SQLite OPFS save/load、version 2 checkpoint 和 recovery integrity `ok` 均恢复完整 `IncludedFileValue` 与 `App::PropertyFileIncluded` TypeId;浏览器内 FCStd 检查确认 `FileIncluded file="chrome-resource.bin"`、16 bytes 内嵌 entry 与目标字节逐项一致。 - 文档资源按 SHA-256 回读为同一 16 bytes;独立 48 bytes 资源的 put/get/release 后读取为 null,OPFS marker 已创建并删除。Worker 请求覆盖 initialize/save/load/checkpoint/recovery/resource/dispose,最终 persistence Worker 已 terminate。 - 最终 `shapeCount=0`、`kernelReferenceCount=0`;`test/check:chrome-property-fileincluded`、E/F focused test、G checker、生产 `build`、JSON 校验与 `git diff --check` 均通过,H 阶段机器证据已加入连续账本。 + +## 23. 2026-08-17 接续执行记录 + +### `PROP-app-propertyintegerset-A..H` 接续审计 + +#### 执行过程与完成证据 + +- 接续时发现已提交的连续进度账本包含 `PROP-app-propertyintegerset-A..H`,但活动队列仍停在 A;`check:freecad-active-work-queue` 按预期以 stale 失败,没有把过期队列误当成当前真值。 +- 逐项复核 A-H 已有证据:原生清册锁定 2 条可写宿主;成功探针锁定去重、排序、空集合、负数、标量和最大安全整数;失败探针锁定类型拒绝、Immutable/ReadOnly 与事务回滚;变异探针锁定两个 FEM 宿主的编辑/恢复。 +- Facade 与事务测试确认 Web 值模型为排序、唯一的安全整数数组,原生 FCStd payload 为 `IntegerSet`;stale、cancel、非法值和资源指标均零污染。真实 FreeCAD 往返保持 `[-1,8]` 且零未知漂移,Chrome SQLite OPFS/Worker 回放恢复相同值并释放资源和 Worker。 +- A、B、C、D、E、F、G、H 及 promotion 九个 focused gate 全部通过;重建机器队列后恢复为单一活动任务 `PROP-app-propertyintegerset-I`,未改写既有原生或浏览器证据。 + +### `PROP-app-propertyintegerset-I` + +#### 执行过程与完成证据 + +- promotion checker 逐项核对 A-H 连续账本、2 条原生宿主、排序唯一安全整数数组、`IntegerSet` FCStd payload、真实 FreeCAD 往返和 Chrome OPFS/Worker 释放证据。 +- `App::PropertyIntegerSet` 已从 `opaque-fcstd-proxy` 提升为 `native-editable-codec`;全局快照保持 `34 / 4370 native-editable`、`46 / 457 opaque`,exact blocker 仍为剩余 46 个 opaque Property 类型,系统 exact 保持 false。 +- 原生绑定对从未赋值的空集合读取会终止进程;Facade 使用“先赋空数组再读取”的安全等价边界,没有把该原生缺陷隐藏成普通成功路径。 +- `check:freecad-property-integerset-promotion` 通过,I 阶段证据已加入连续账本;下一步重建活动队列并直接接领新的唯一 `nextTask`。 + +### `PROP-app-propertylinklisthidden-A` + +#### 执行过程与完成证据 + +- 从锁定 FreeCAD 1.1.1 桌面 oracle 确认仅有 `Sketcher::SketchObject.Exports` 与 `Sketcher::SketchObjectPython.Exports` 两条记录;两者均可实例化、默认空列表、分组为 Sketch,原生状态位 26 解码为 `PropHidden`。 +- 将属性状态 `PropHidden` 与类型的 `LinkScope::Hidden` 分开锁定:前者隐藏普通属性编辑入口,后者使链接不建立 backlink、默认 `getLinks()` 和 DAG 依赖,但 `getLinks(all=true)` 仍可枚举。 +- 源码合同确认该类型继承 `App::PropertyLinkList`,只接受已挂接的同文档对象列表,空列表或单 null 列表清空;未开启外部链接标志时拒绝跨文档对象。列表顺序与重复项保留,持久化仍使用 `LinkList/Link`。 +- Sketch 的 Exports 用于按对象名解析导出子对象,并兼容从旧 `App::PropertyLinkList` 归档恢复;属性写入本身不要求 Shape。 +- 新增 `generate/check:freecad-property-linklisthidden-inventory`,同时锁定 runtime 与四份 FreeCAD 源码的字节数和 SHA-256;生成器、focused checker、脚本语法、JSON 与 package 清单均通过。A 阶段仍保持 `opaque-fcstd-proxy`,真实 setter、回链/依赖、错误边界和宿主行为留给 B/C。 + +### `PROP-app-propertylinklisthidden-B` + +#### 执行过程与完成证据 + +- 对 `Sketcher::SketchObject.Exports` 与 `Sketcher::SketchObjectPython.Exports` 运行真实 FreeCAD 1.1.1 setter;先显式赋空列表再读取,覆盖单对象、有序双对象、重复对象和恢复空列表。 +- 两个宿主都原样保留 `[ExportB, ExportA]` 顺序及 `[ExportA, ExportB, ExportA]` 重复项;属性状态稳定为 `26 / PropHidden`,editor mode 为 Hidden。 +- 所有阶段的宿主 `OutList` 与两个目标的 `InList` 均为空,锁定 `LinkScope::Hidden` 不泄漏普通 DAG/backlink;对象集始终为一个 Sketch 与两个 Part Feature。 +- 两个空 Sketch 始终是 Valid、Up-to-date 的 Null Shape;目标 box/cylinder 分别保持 1 solid 与稳定 BREP hash,没有用伪 Shape 替代链接成功证据。 +- `probe/check:freecad-property-linklisthidden-success`、A checker、脚本语法、JSON 和 `git diff --check` 均通过;B 阶段证据已加入连续账本。 + +### `PROP-app-propertylinklisthidden-C` + +#### 执行过程与完成证据 + +- 字符串/整数的标量和列表元素分别以精确 TypeError 原子拒绝;跨文档对象以 `PropertyLinkList does not support external object` 的 ValueError 拒绝。五个 case 都保持 `[ExportA, ExportB]`、对象集、Shape hash、OutList/InList 和宿主状态不变。 +- 首次原生探针发现该类型不能套用普通属性的只读假设:叠加 editor ReadOnly 后 Python setter 仍写成 `[ExportB]`;叠加 property Immutable 后也仍成功写入。两项均作为 `python-setter-accepted` 原生边界保留,没有伪造拒绝结果。 +- 开启 `UndoMode=1` 后,活动事务把列表改成 `[ExportB, ExportA, ExportB]`;abort 恢复原值但宿主暂为 Touched,随后一次原生 recompute 返回 Up-to-date/Valid,隐藏依赖、目标 Shape 与对象集全程不变。 +- setter 没有 native cancel hook,取消合同固定为 abort 活动 Document transaction;pending/active transaction 最终归零,外部文档对象也未污染。 +- `probe/check:freecad-property-linklisthidden-failure`、B checker、脚本语法、JSON 和 `git diff --check` 均通过;C 阶段证据已加入连续账本。 + +### `PROP-app-propertylinklisthidden-D` + +#### 执行过程与完成证据 + +- 对 `Sketcher::SketchObject` 和 `Sketcher::SketchObjectPython` 分别执行 `[ExportA, ExportB] -> [ExportB, ExportA, ExportB] -> [ExportA, ExportB]`,真实覆盖顺序变化与重复项。 +- 两个宿主在每次 setter 后都由 Up-to-date/Valid 进入 Touched,原生 recompute 后返回 Up-to-date/Valid;最终 value 与完整 semantic snapshot 精确恢复。 +- 五个阶段中的属性 TypeId、Sketch 分组、`26 / PropHidden`、Hidden editor mode、Null Sketch Shape、box/cylinder 拓扑与 BREP hash、对象集均保持稳定。 +- owner OutList 与 target InList 全程为空,证明参数变异和恢复不会临时泄漏普通 DAG/backlink。 +- `probe/check:freecad-property-linklisthidden-mutation`、C checker、脚本语法、JSON 与 `git diff --check` 均通过;D 阶段证据已加入连续账本。 + +### `PROP-app-propertylinklisthidden-E` + +#### 执行过程与完成证据 + +- 将 `App::PropertyLinkListHidden` 加入 typed Facade,复用有序字符串链接数组但保留独立 TypeId;setter 校验所有目标都属于当前文档,顺序和重复项不归一化,非法类型和未知目标在事件/版本变化前拒绝。 +- 依赖收集器仅为普通 `PropertyLinkList` 生成边,Hidden 类型明确保持零依赖;成功写入仍按原生行为将宿主标脏并进入统一事务事件路径。 +- FCStd writer 为 `Sketcher::SketchObject.Exports` 与 `Sketcher::SketchObjectPython.Exports` 写出静态原生 `PropertyLinkListHidden + LinkList/Link`,保留 `PropHidden` mask,不携带动态 `group/doc/attr/ro/hide` 元数据;decoder 恢复完整有序重复列表。 +- 两个 Sketch TypeId 均进入 recognized/static host 清册;生产 PropertyPanel 已按 `isFreecadPropertyHidden(nativeStatus)` 过滤该属性,不暴露普通 UI 编辑入口,程序化修改只能走 Facade。 +- focused test、完整 `test:facade` 199 项、A-D checker、生产 `build`、JSON 与 `git diff --check` 均通过;构建仅保留既有 OCCT 模块 externalized 与大 chunk 警告。E 阶段证据已加入连续账本。 + +### `PROP-app-propertylinklisthidden-F` + +#### 执行过程与完成证据 + +- 通过公开 Facade 将 Exports 从 `[ExportA, ExportB]` 改为 `[ExportB, ExportA, ExportB]`;一次提交只产生同一 transaction ID 的 `before-change -> changed -> committed`,版本 `1 -> 2`、dirty 与宿主 touched 状态正确。 +- setter、文档快照和 history 均深拷贝字符串数组;调用方和读取方后续变异不会污染存储值。undo 精确恢复原列表/版本 1,redo 恢复重复有序列表/版本 2。 +- stale 版本、预取消、未知目标和非字符串元素均在提交前 fail-closed;文档 JSON、事务事件数与 undo/redo 可用性零污染。 +- 成功、undo/redo 和全部失败路径中的 dependency edges 始终为空,未把 Hidden 链接提升为普通 DAG;Shape 数、内核引用与释放计数完全不变,并显式 dispose 项目持久化边界。 +- `test:freecad-property-linklisthidden-transaction`、E focused test、JSON 与 `git diff --check` 均通过;F 阶段证据已加入连续账本。 + +### `PROP-app-propertylinklisthidden-G` + +#### 执行过程与完成证据 + +- 新增受限 `rewriteFcstdLinkListHiddenProperty` 并经公开 Facade `project.fcstd.rewriteLinkListHidden` 暴露;只允许 `Sketcher::SketchObject/SketchObjectPython.Exports`,写前校验对象、属性 TypeId、所有同归档目标和可选旧值,写后重新 inspect 精确闭环。 +- 真实 FreeCAD 1.1.1 链路创建 `Exports=[ExportA,ExportB]`,Web 直改为 `[ExportB,ExportA,ExportB]`;FreeCAD reopen/recompute/saveAs/reopen 均精确保留新顺序与重复项,宿主仍为 Hidden、Valid、Up-to-date 的 Null Shape。 +- 原生基线确认 Hidden link 的运行时 `OutList/InList` 始终为空,但 FCStd 加载顺序表仍含目标名:Web 编辑原样保留初始 `[ExportA,ExportB]`,原生再保存后规范化为 `[ExportB,ExportA,ExportB]`。探针分别锁定两层语义,未把 FCStd 加载依赖误判为运行时 DAG。 +- 两个目标 box/cylinder 的对象集、拓扑、面积、体积及三阶段 BREP SHA-256 全部稳定;除 `Document.xml` 外 4 个 opaque 条目逐字节不变,排除目标属性后的语义对象不变。 +- `probe/check:freecad-property-linklisthidden-roundtrip`、focused Facade test、TypeScript noEmit 与生产 `build` 均通过,`zeroUnknownDrift=true`;G 阶段证据已加入连续账本。 + +### `PROP-app-propertylinklisthidden-H` + +#### 执行过程与完成证据 + +- 新增独立真实 Chrome `PropertyLinkListHidden` harness,直接挂载生产 `PropertyPanel` 与 `createProductionFacade`;为此只把既有面板函数导出,没有复制或绕开生产过滤逻辑。 +- 面板用可见 `Label` 控件证明已正常渲染,而 `Exports` 在程序化修改前后均为 0 个控件、0 次可见文字;`PropHidden` 状态有效,没有把该属性误暴露为普通编辑器。 +- 通过同一生产 Facade 将隐藏列表从 `[ExportA,ExportB]` 改为 `[ExportB,ExportA,ExportB]`;版本 `1 -> 2`、dirty/touched 正确,运行时依赖始终为 0,通知错误为空。 +- SQLite OPFS save/load、version 2 checkpoint 和 recovery integrity `ok` 均恢复完整有序重复列表及 `App::PropertyLinkListHidden` TypeId;浏览器内 FCStd 检查确认 `LinkList` payload、`67108864 / PropHidden` 与目标列表完全一致。 +- Worker 请求覆盖 initialize/save/load/checkpoint/recovery/resource/dispose;44 bytes 独立资源 put/get/release 后读取为 null,OPFS marker 已创建并删除,最终 persistence Worker terminate、`shapeCount=0`、`kernelReferenceCount=0`。 +- `test/check:chrome-property-linklisthidden`、E/F focused test、生产 `build`、TypeScript noEmit、JSON 与脚本语法均通过;H 阶段证据已加入连续账本。 + +### `PROP-app-propertylinklisthidden-I` + +#### 执行过程与完成证据 + +- 将 `App::PropertyLinkListHidden` 纳入全局 native-editable codec 分区;原生语义清册现为 35 种/4372 条 native-editable、5 种/683 条 specialized、45 种/455 条 opaque,15 类 status 仍全部有类型化 Facade 行为。 +- promotion 报告逐项锁定 A-H 连续账本、2 条可写记录、2 个 Sketch 宿主、`PropHidden` 属性状态、`LinkScope::Hidden` 运行时零依赖、`LinkList` FCStd 元素、有序重复值和 Web 隐藏面板过滤。 +- G 阶段 FreeCAD-Web-FreeCAD 往返 `zeroUnknownDrift=true`,H 阶段 Chrome Worker/OPFS/FCStd/资源释放证据通过;旧有 acceleration、area、boollist、color、colorlist、direction、file、fileincluded、font、force、heatflux、integerset promotion 报告同步到同一 blocker 计数。 +- exact parity blocker 已从 `46 types/457 records` 更新为 `45 types/455 records`;`systemExact=false` 仍明确表示剩余 opaque 类型未全部完成,未将局部提升误报为全局 exact。 +- `probe/check:freecad-native-property-semantics`、全部 13 个 promotion checker(含本 I)、focused Facade/transaction、Chrome checker、生产 `build`、JSON 与 `git diff --check` 均通过;I 阶段证据已加入连续账本,机器队列随后进入下一个能力。 + +### `PROP-app-propertylinksubhidden-A` + +#### 执行过程与完成证据 + +- 从锁定 FreeCAD 1.1.1 runtime oracle 确认 4 条记录:`App::Link.ColoredElements`、`App::LinkGroup.ColoredElements`、`App::LinkPython.ColoredElements`、`App::LinkGroupPython.ColoredElements`。 +- 四条记录均为可用宿主,分组为 ` Link`,默认 `null`,状态为 `LockDynamic + PropHidden`;继承 `App::PropertyLinkSub`,接受同文档对象与有序子元素名称,空子列表/空对象清空,跨文档和 detached 对象拒绝。 +- 源码合同锁定专用 `PropertyLinkSubHidden` 的 `LinkScope::Hidden`:普通 `getLinks`、backlink 和 DAG 排除,`getLinks(all=true)` 仍可枚举;FCStd 使用 `LinkSub/Sub`,Draft Link 扩展动态添加并隐藏 `ColoredElements`。 +- 新增 `generate/check:freecad-property-linksubhidden-inventory`,runtime 与 `PropertyLinks.h/.cpp`、`Link.h`、Draft `draftlink.py` 字节数和 SHA-256 provenance 均锁定,4 条记录与类型合同 checker 通过。A 阶段证据已加入连续账本。 + +### `PROP-app-propertylinksubhidden-B` + +#### 执行过程与完成证据 + +- 真实 FreeCAD 1.1.1 对 `App::Link`、`App::LinkGroup`、`App::LinkGroupPython`、`App::LinkPython` 四个宿主执行 `ColoredElements=(ColorSourceA,[Face1,Face3])`、重复子元素、空子列表和 `None` 清空。 +- 四宿主均精确保留有序子元素与重复项;空子列表原生语义是“整对象引用”,不是清空,直接赋 `None` 才清空。属性 TypeId、`LockDynamic + PropHidden`、Hidden editor mode 和对象集稳定。 +- LinkGroup 两类无普通边;Link/LinkPython 的 `LinkedObject=ColorSourceA` 仅产生其自身既有 OutList/InList,隐藏 ColoredElements 未额外增加依赖/backlink;两个目标 box/cylinder 拓扑和 BREP hash 全阶段稳定。 +- 新增 `probe/check:freecad-property-linksubhidden-success`,四宿主、ordered/duplicates/wholeObject/explicitNull 四阶段全部通过;B 阶段证据已加入连续账本。