feat: classify recovered naming drift
Some checks failed
real-verification / chrome (push) Has been cancelled
real-verification / freecad-oracle (push) Has been cancelled
real-verification / wasm (push) Has been cancelled

This commit is contained in:
2026-08-15 01:01:54 -04:00
parent 5bbd7b9d4f
commit ea3706bd3d
21 changed files with 18736 additions and 15778 deletions

View File

@@ -4,7 +4,7 @@
"freecadVersion": "1.1.1",
"commit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d"
},
"updatedAt": "2026-08-14",
"updatedAt": "2026-08-15",
"generatedBy": "scripts/generate-freecad-active-work-queue.mjs",
"policy": {
"maxInProgress": 1,
@@ -12,12 +12,12 @@
"transitionUnit": "one ordered operation pair",
"driftUnit": "one native case"
},
"nextTask": "ORA-GUI-WF-002",
"nextTask": "TSN-DRIFT-002",
"summary": {
"tasks": 487,
"completed": 112,
"completed": 119,
"inProgress": 1,
"pending": 374
"pending": 367
},
"milestones": [
{
@@ -1375,7 +1375,7 @@
{
"id": "ORA-GUI-WORKFLOWS",
"exactTask": "EX-ORA-01",
"status": "in_progress",
"status": "completed",
"tasks": [
{
"id": "ORA-GUI-WF-000",
@@ -1409,46 +1409,67 @@
{
"id": "ORA-GUI-WF-002",
"title": "Capture the native disabled workflow",
"status": "in_progress",
"status": "completed",
"dependencies": [
"ORA-GUI-WF-001"
],
"evidence": [
"config/freecad-gui-workflow-oracle.json",
"scripts/check-freecad-gui-workflow-oracle.mjs"
],
"exit": "one native workflow fixture and its focused assertion pass"
},
{
"id": "ORA-GUI-WF-003",
"title": "Capture the native failure workflow",
"status": "pending",
"status": "completed",
"dependencies": [
"ORA-GUI-WF-002"
],
"evidence": [
"config/freecad-gui-workflow-oracle.json",
"scripts/check-freecad-gui-workflow-oracle.mjs"
],
"exit": "one native workflow fixture and its focused assertion pass"
},
{
"id": "ORA-GUI-WF-004",
"title": "Capture the native cancel workflow",
"status": "pending",
"status": "completed",
"dependencies": [
"ORA-GUI-WF-003"
],
"evidence": [
"config/freecad-gui-workflow-oracle.json",
"scripts/check-freecad-gui-workflow-oracle.mjs"
],
"exit": "one native workflow fixture and its focused assertion pass"
},
{
"id": "ORA-GUI-WF-005",
"title": "Capture recovery after failure or cancel",
"status": "pending",
"status": "completed",
"dependencies": [
"ORA-GUI-WF-004"
],
"evidence": [
"config/freecad-gui-workflow-oracle.json",
"scripts/check-freecad-gui-workflow-oracle.mjs"
],
"exit": "one native workflow fixture and its focused assertion pass"
},
{
"id": "ORA-GUI-WF-006",
"title": "Update the exact blocker and focused checks",
"status": "pending",
"status": "completed",
"dependencies": [
"ORA-GUI-WF-005"
],
"evidence": [
"config/freecad-web-exact-parity-plan.json",
"scripts/check-freecad-gui-workflow-plan.mjs",
"scripts/generate-freecad-active-work-queue.mjs"
],
"exit": "one native workflow fixture and its focused assertion pass"
}
]
@@ -1456,12 +1477,12 @@
{
"id": "TSN-RECOVERY-DRIFT",
"exactTask": "EX-TSN-04",
"status": "pending",
"status": "in_progress",
"tasks": [
{
"id": "TSN-DRIFT-000",
"title": "Classify recovered naming evolution for partdesign-plain",
"status": "pending",
"status": "completed",
"dependencies": [
"ORA-GUI-WF-006"
],
@@ -1471,12 +1492,17 @@
0,
9
],
"classification": "allowed_evolution",
"evidence": [
"config/freecad-recovered-naming-classification.json",
"native-history-stabilizes-after-edit-restore"
],
"exit": "the case is classified as stable semantics, allowed evolution, or an implementation defect"
},
{
"id": "TSN-DRIFT-001",
"title": "Classify recovered naming evolution for partdesign-midplane",
"status": "pending",
"status": "completed",
"dependencies": [
"TSN-DRIFT-000"
],
@@ -1486,12 +1512,17 @@
0,
9
],
"classification": "allowed_evolution",
"evidence": [
"config/freecad-recovered-naming-classification.json",
"native-history-stabilizes-after-edit-restore"
],
"exit": "the case is classified as stable semantics, allowed evolution, or an implementation defect"
},
{
"id": "TSN-DRIFT-002",
"title": "Classify recovered naming evolution for partdesign-reverse",
"status": "pending",
"status": "in_progress",
"dependencies": [
"TSN-DRIFT-001"
],

File diff suppressed because it is too large Load Diff

View File

@@ -716,8 +716,8 @@
"name": "Common07",
"ordinal": 2,
"relationCount": 8,
"relationDigest": "295286347d8935ff0fc96fe706a211f55c042edd96c5f714a6ef31d2b9eac359",
"semanticNameDigest": "bad23fbf57f26098673a1e702fad4011f7fa332e96c2d415d32063b8b934d3ed",
"relationDigest": "7794560879873d1dd1468a41ff9a0fed2e5912ac6ebf493d0de6725aede5d014",
"semanticNameDigest": "9d9b0bb54f9c3cc9d1bacbcb9d4e48a9014844b888085edb0b4cbc0dd9a28a22",
"typeId": "Part::Common"
}
],
@@ -751,8 +751,8 @@
"name": "Common07",
"ordinal": 2,
"relationCount": 8,
"relationDigest": "295286347d8935ff0fc96fe706a211f55c042edd96c5f714a6ef31d2b9eac359",
"semanticNameDigest": "bad23fbf57f26098673a1e702fad4011f7fa332e96c2d415d32063b8b934d3ed",
"relationDigest": "7794560879873d1dd1468a41ff9a0fed2e5912ac6ebf493d0de6725aede5d014",
"semanticNameDigest": "9d9b0bb54f9c3cc9d1bacbcb9d4e48a9014844b888085edb0b4cbc0dd9a28a22",
"typeId": "Part::Common"
}
],
@@ -786,8 +786,8 @@
"name": "Common07",
"ordinal": 2,
"relationCount": 8,
"relationDigest": "295286347d8935ff0fc96fe706a211f55c042edd96c5f714a6ef31d2b9eac359",
"semanticNameDigest": "bad23fbf57f26098673a1e702fad4011f7fa332e96c2d415d32063b8b934d3ed",
"relationDigest": "7794560879873d1dd1468a41ff9a0fed2e5912ac6ebf493d0de6725aede5d014",
"semanticNameDigest": "9d9b0bb54f9c3cc9d1bacbcb9d4e48a9014844b888085edb0b4cbc0dd9a28a22",
"typeId": "Part::Common"
}
]
@@ -829,8 +829,8 @@
"name": "Common08",
"ordinal": 2,
"relationCount": 20,
"relationDigest": "dfdf72915217455c1b31cc7c5ba1e7ada26c2311bbb5a566cc64b61e217f65b9",
"semanticNameDigest": "9d429381d7597415c59081bdaf14a9d2de1f5aada3f2c323f81811d9fea22c08",
"relationDigest": "d6ab1196f3f4caec17c35f901fefec1f62e9d1034e2118bbbb4be8377e90dc86",
"semanticNameDigest": "14d3f53fbcd4f2a1b595eba4ac8c49dc5e0ac3515f799d2acdcf595dd11549dd",
"typeId": "Part::Common"
}
],
@@ -864,8 +864,8 @@
"name": "Common08",
"ordinal": 2,
"relationCount": 20,
"relationDigest": "dfdf72915217455c1b31cc7c5ba1e7ada26c2311bbb5a566cc64b61e217f65b9",
"semanticNameDigest": "9d429381d7597415c59081bdaf14a9d2de1f5aada3f2c323f81811d9fea22c08",
"relationDigest": "d6ab1196f3f4caec17c35f901fefec1f62e9d1034e2118bbbb4be8377e90dc86",
"semanticNameDigest": "14d3f53fbcd4f2a1b595eba4ac8c49dc5e0ac3515f799d2acdcf595dd11549dd",
"typeId": "Part::Common"
}
],
@@ -899,8 +899,8 @@
"name": "Common08",
"ordinal": 2,
"relationCount": 20,
"relationDigest": "dfdf72915217455c1b31cc7c5ba1e7ada26c2311bbb5a566cc64b61e217f65b9",
"semanticNameDigest": "9d429381d7597415c59081bdaf14a9d2de1f5aada3f2c323f81811d9fea22c08",
"relationDigest": "d6ab1196f3f4caec17c35f901fefec1f62e9d1034e2118bbbb4be8377e90dc86",
"semanticNameDigest": "14d3f53fbcd4f2a1b595eba4ac8c49dc5e0ac3515f799d2acdcf595dd11549dd",
"typeId": "Part::Common"
}
]
@@ -2713,8 +2713,8 @@
"name": "Body15",
"ordinal": 0,
"relationCount": 52,
"relationDigest": "835e0cf6f0102faf415757a4b21e47cedf21f35329898ff417f8d7bedc6d9798",
"semanticNameDigest": "9707309dde4d9bda56609872fd2e8d88ea9e7a69313fe8cca3ce33871e97b619",
"relationDigest": "48ca1f5bc04d2b40dee361740cea63e5d7f40bd447abd97af63a6cff9c33ae44",
"semanticNameDigest": "2c5e4fc67634c300b98cdc62c51d57a6248428ef0b9b0973b3a37ffed3336418",
"typeId": "PartDesign::Body"
},
{
@@ -2830,8 +2830,8 @@
"name": "Pocket15",
"ordinal": 11,
"relationCount": 52,
"relationDigest": "adffd9c88c511bb64a85b993d5fb13f30668df6f0c6c1877ba4f1b0826287782",
"semanticNameDigest": "6f05ebd5436a54724d02365700524cc3f13e8979408d51f646e07f870f4ecd19",
"relationDigest": "37cc4db7c553219ced9d96419849ebfab4bad1bb49e33f7406da753a3eb88f95",
"semanticNameDigest": "90c517098693f8d01c88f618db28d8063ab41de7d34c7bebd9fbdaa56aeb24e8",
"typeId": "PartDesign::Pocket"
}
],
@@ -2848,8 +2848,8 @@
"name": "Body15",
"ordinal": 0,
"relationCount": 52,
"relationDigest": "835e0cf6f0102faf415757a4b21e47cedf21f35329898ff417f8d7bedc6d9798",
"semanticNameDigest": "9707309dde4d9bda56609872fd2e8d88ea9e7a69313fe8cca3ce33871e97b619",
"relationDigest": "48ca1f5bc04d2b40dee361740cea63e5d7f40bd447abd97af63a6cff9c33ae44",
"semanticNameDigest": "2c5e4fc67634c300b98cdc62c51d57a6248428ef0b9b0973b3a37ffed3336418",
"typeId": "PartDesign::Body"
},
{
@@ -2965,8 +2965,8 @@
"name": "Pocket15",
"ordinal": 11,
"relationCount": 52,
"relationDigest": "adffd9c88c511bb64a85b993d5fb13f30668df6f0c6c1877ba4f1b0826287782",
"semanticNameDigest": "6f05ebd5436a54724d02365700524cc3f13e8979408d51f646e07f870f4ecd19",
"relationDigest": "37cc4db7c553219ced9d96419849ebfab4bad1bb49e33f7406da753a3eb88f95",
"semanticNameDigest": "90c517098693f8d01c88f618db28d8063ab41de7d34c7bebd9fbdaa56aeb24e8",
"typeId": "PartDesign::Pocket"
}
],
@@ -2983,8 +2983,8 @@
"name": "Body15",
"ordinal": 0,
"relationCount": 52,
"relationDigest": "835e0cf6f0102faf415757a4b21e47cedf21f35329898ff417f8d7bedc6d9798",
"semanticNameDigest": "9707309dde4d9bda56609872fd2e8d88ea9e7a69313fe8cca3ce33871e97b619",
"relationDigest": "48ca1f5bc04d2b40dee361740cea63e5d7f40bd447abd97af63a6cff9c33ae44",
"semanticNameDigest": "2c5e4fc67634c300b98cdc62c51d57a6248428ef0b9b0973b3a37ffed3336418",
"typeId": "PartDesign::Body"
},
{
@@ -3100,8 +3100,8 @@
"name": "Pocket15",
"ordinal": 11,
"relationCount": 52,
"relationDigest": "adffd9c88c511bb64a85b993d5fb13f30668df6f0c6c1877ba4f1b0826287782",
"semanticNameDigest": "6f05ebd5436a54724d02365700524cc3f13e8979408d51f646e07f870f4ecd19",
"relationDigest": "37cc4db7c553219ced9d96419849ebfab4bad1bb49e33f7406da753a3eb88f95",
"semanticNameDigest": "90c517098693f8d01c88f618db28d8063ab41de7d34c7bebd9fbdaa56aeb24e8",
"typeId": "PartDesign::Pocket"
}
]
@@ -3126,8 +3126,8 @@
"name": "Body16",
"ordinal": 0,
"relationCount": 50,
"relationDigest": "2d719df5714859cd44047b2e1df1e8fea9b106aaff263981b48cd31c7f55f998",
"semanticNameDigest": "1a26a90f5303344243a006e0b8c5b6685dc0a713e0ede6046b6529267bcc61b4",
"relationDigest": "789f08ee93078a4993256f4f84aaeebeda55b1e4b85ba320bc3bf1660f61424f",
"semanticNameDigest": "a43643c71e07a6f3cf199cf37eda3d1048f7cdbb62c4b069ad64a42d98380d38",
"typeId": "PartDesign::Body"
},
{
@@ -3243,8 +3243,8 @@
"name": "Pocket16",
"ordinal": 11,
"relationCount": 50,
"relationDigest": "df296e78d31bcb9e05107e9c90c4b5d3ab6b9e5118e65b26d827f4aa0b0c42d5",
"semanticNameDigest": "4f4e7febf92fb183e5aebba6f92b295a71c8333c96030907347a64365afe13b4",
"relationDigest": "5342570780854a405f82bfef7a11730681d8f6b71dfd318e482096b1be058678",
"semanticNameDigest": "406cb7e44499069fbb747666aa20b9fc3070698d033d6becc6ed582f5394981b",
"typeId": "PartDesign::Pocket"
}
],
@@ -3261,8 +3261,8 @@
"name": "Body16",
"ordinal": 0,
"relationCount": 50,
"relationDigest": "2d719df5714859cd44047b2e1df1e8fea9b106aaff263981b48cd31c7f55f998",
"semanticNameDigest": "1a26a90f5303344243a006e0b8c5b6685dc0a713e0ede6046b6529267bcc61b4",
"relationDigest": "789f08ee93078a4993256f4f84aaeebeda55b1e4b85ba320bc3bf1660f61424f",
"semanticNameDigest": "a43643c71e07a6f3cf199cf37eda3d1048f7cdbb62c4b069ad64a42d98380d38",
"typeId": "PartDesign::Body"
},
{
@@ -3378,8 +3378,8 @@
"name": "Pocket16",
"ordinal": 11,
"relationCount": 50,
"relationDigest": "df296e78d31bcb9e05107e9c90c4b5d3ab6b9e5118e65b26d827f4aa0b0c42d5",
"semanticNameDigest": "4f4e7febf92fb183e5aebba6f92b295a71c8333c96030907347a64365afe13b4",
"relationDigest": "5342570780854a405f82bfef7a11730681d8f6b71dfd318e482096b1be058678",
"semanticNameDigest": "406cb7e44499069fbb747666aa20b9fc3070698d033d6becc6ed582f5394981b",
"typeId": "PartDesign::Pocket"
}
],
@@ -3396,8 +3396,8 @@
"name": "Body16",
"ordinal": 0,
"relationCount": 50,
"relationDigest": "2d719df5714859cd44047b2e1df1e8fea9b106aaff263981b48cd31c7f55f998",
"semanticNameDigest": "1a26a90f5303344243a006e0b8c5b6685dc0a713e0ede6046b6529267bcc61b4",
"relationDigest": "789f08ee93078a4993256f4f84aaeebeda55b1e4b85ba320bc3bf1660f61424f",
"semanticNameDigest": "a43643c71e07a6f3cf199cf37eda3d1048f7cdbb62c4b069ad64a42d98380d38",
"typeId": "PartDesign::Body"
},
{
@@ -3513,8 +3513,8 @@
"name": "Pocket16",
"ordinal": 11,
"relationCount": 50,
"relationDigest": "df296e78d31bcb9e05107e9c90c4b5d3ab6b9e5118e65b26d827f4aa0b0c42d5",
"semanticNameDigest": "4f4e7febf92fb183e5aebba6f92b295a71c8333c96030907347a64365afe13b4",
"relationDigest": "5342570780854a405f82bfef7a11730681d8f6b71dfd318e482096b1be058678",
"semanticNameDigest": "406cb7e44499069fbb747666aa20b9fc3070698d033d6becc6ed582f5394981b",
"typeId": "PartDesign::Pocket"
}
]
@@ -3539,8 +3539,8 @@
"name": "Body17",
"ordinal": 0,
"relationCount": 51,
"relationDigest": "28160a0ab8b121b4637d821a472da5a3185218b7e6ec13152eec5ea0b1c9afba",
"semanticNameDigest": "c780d168d3ceaf75ce64dd09012e09a6ccf65e2b66faa339eef3aefde20b0233",
"relationDigest": "18f04373fb3582655d5e225456f985d7c20a0142ffe28ebd325aaf93479007cb",
"semanticNameDigest": "5b0acd2a833d282517f489087eb32b9098dd37092dc93ce41cf62f43e5ec60e1",
"typeId": "PartDesign::Body"
},
{
@@ -3656,8 +3656,8 @@
"name": "Pocket17",
"ordinal": 11,
"relationCount": 51,
"relationDigest": "f5e86d9c1e288997a787cb8faaf9a0e89c4143fb68259c96272723166e3d18c3",
"semanticNameDigest": "a18bf0085100e35287dc55da036619ca6b8675e066ddb1bbd345775572049b67",
"relationDigest": "b4cb276cb4aaa207e7e681898324f517794a3003f8ae49d0c221161f423c4504",
"semanticNameDigest": "2cd5034a729734056e855cc31d2e8652221923fe8a2387aeadf770e406e17369",
"typeId": "PartDesign::Pocket"
}
],
@@ -3674,8 +3674,8 @@
"name": "Body17",
"ordinal": 0,
"relationCount": 51,
"relationDigest": "28160a0ab8b121b4637d821a472da5a3185218b7e6ec13152eec5ea0b1c9afba",
"semanticNameDigest": "c780d168d3ceaf75ce64dd09012e09a6ccf65e2b66faa339eef3aefde20b0233",
"relationDigest": "18f04373fb3582655d5e225456f985d7c20a0142ffe28ebd325aaf93479007cb",
"semanticNameDigest": "5b0acd2a833d282517f489087eb32b9098dd37092dc93ce41cf62f43e5ec60e1",
"typeId": "PartDesign::Body"
},
{
@@ -3791,8 +3791,8 @@
"name": "Pocket17",
"ordinal": 11,
"relationCount": 51,
"relationDigest": "f5e86d9c1e288997a787cb8faaf9a0e89c4143fb68259c96272723166e3d18c3",
"semanticNameDigest": "a18bf0085100e35287dc55da036619ca6b8675e066ddb1bbd345775572049b67",
"relationDigest": "b4cb276cb4aaa207e7e681898324f517794a3003f8ae49d0c221161f423c4504",
"semanticNameDigest": "2cd5034a729734056e855cc31d2e8652221923fe8a2387aeadf770e406e17369",
"typeId": "PartDesign::Pocket"
}
],
@@ -3809,8 +3809,8 @@
"name": "Body17",
"ordinal": 0,
"relationCount": 51,
"relationDigest": "28160a0ab8b121b4637d821a472da5a3185218b7e6ec13152eec5ea0b1c9afba",
"semanticNameDigest": "c780d168d3ceaf75ce64dd09012e09a6ccf65e2b66faa339eef3aefde20b0233",
"relationDigest": "18f04373fb3582655d5e225456f985d7c20a0142ffe28ebd325aaf93479007cb",
"semanticNameDigest": "5b0acd2a833d282517f489087eb32b9098dd37092dc93ce41cf62f43e5ec60e1",
"typeId": "PartDesign::Body"
},
{
@@ -3926,8 +3926,8 @@
"name": "Pocket17",
"ordinal": 11,
"relationCount": 51,
"relationDigest": "f5e86d9c1e288997a787cb8faaf9a0e89c4143fb68259c96272723166e3d18c3",
"semanticNameDigest": "a18bf0085100e35287dc55da036619ca6b8675e066ddb1bbd345775572049b67",
"relationDigest": "b4cb276cb4aaa207e7e681898324f517794a3003f8ae49d0c221161f423c4504",
"semanticNameDigest": "2cd5034a729734056e855cc31d2e8652221923fe8a2387aeadf770e406e17369",
"typeId": "PartDesign::Pocket"
}
]
@@ -3952,8 +3952,8 @@
"name": "Body18",
"ordinal": 0,
"relationCount": 52,
"relationDigest": "c7ff8a40c0d46636da7adfae5790056fca26cd6cfb1a4ed4764e69fb13bda510",
"semanticNameDigest": "30f99433b9fb972746315f6833df916e44638090ee4660a0d4d475c108b8c0e5",
"relationDigest": "ac09a053ed37c89a51ff719fd8dbd3d2995a57046f2a0966bd216d4e43c403e7",
"semanticNameDigest": "6dd306ccbeb4bab8bac339ccbaf7ba1b31277502c2d2575b70ed9fc6292586ac",
"typeId": "PartDesign::Body"
},
{
@@ -4069,8 +4069,8 @@
"name": "Pocket18",
"ordinal": 11,
"relationCount": 52,
"relationDigest": "75aad02d9e5d9292306a954633ab61b5cbd7c527babc918fe6c5f8da9174fa78",
"semanticNameDigest": "3f9d77793d7497e2cb3fa763c437ce1653db71cd335405c7452079abcd4691df",
"relationDigest": "17b7e1a8bf07c76449457915d1054099e92b783a37fb47fa25fbd7332b2e739a",
"semanticNameDigest": "2576a581360b1033746428f0e1092c2f952b4dd3515c615787d151c9a69b1371",
"typeId": "PartDesign::Pocket"
}
],
@@ -4087,8 +4087,8 @@
"name": "Body18",
"ordinal": 0,
"relationCount": 52,
"relationDigest": "c7ff8a40c0d46636da7adfae5790056fca26cd6cfb1a4ed4764e69fb13bda510",
"semanticNameDigest": "30f99433b9fb972746315f6833df916e44638090ee4660a0d4d475c108b8c0e5",
"relationDigest": "ac09a053ed37c89a51ff719fd8dbd3d2995a57046f2a0966bd216d4e43c403e7",
"semanticNameDigest": "6dd306ccbeb4bab8bac339ccbaf7ba1b31277502c2d2575b70ed9fc6292586ac",
"typeId": "PartDesign::Body"
},
{
@@ -4204,8 +4204,8 @@
"name": "Pocket18",
"ordinal": 11,
"relationCount": 52,
"relationDigest": "75aad02d9e5d9292306a954633ab61b5cbd7c527babc918fe6c5f8da9174fa78",
"semanticNameDigest": "3f9d77793d7497e2cb3fa763c437ce1653db71cd335405c7452079abcd4691df",
"relationDigest": "17b7e1a8bf07c76449457915d1054099e92b783a37fb47fa25fbd7332b2e739a",
"semanticNameDigest": "2576a581360b1033746428f0e1092c2f952b4dd3515c615787d151c9a69b1371",
"typeId": "PartDesign::Pocket"
}
],
@@ -4222,8 +4222,8 @@
"name": "Body18",
"ordinal": 0,
"relationCount": 52,
"relationDigest": "c7ff8a40c0d46636da7adfae5790056fca26cd6cfb1a4ed4764e69fb13bda510",
"semanticNameDigest": "30f99433b9fb972746315f6833df916e44638090ee4660a0d4d475c108b8c0e5",
"relationDigest": "ac09a053ed37c89a51ff719fd8dbd3d2995a57046f2a0966bd216d4e43c403e7",
"semanticNameDigest": "6dd306ccbeb4bab8bac339ccbaf7ba1b31277502c2d2575b70ed9fc6292586ac",
"typeId": "PartDesign::Body"
},
{
@@ -4339,8 +4339,8 @@
"name": "Pocket18",
"ordinal": 11,
"relationCount": 52,
"relationDigest": "75aad02d9e5d9292306a954633ab61b5cbd7c527babc918fe6c5f8da9174fa78",
"semanticNameDigest": "3f9d77793d7497e2cb3fa763c437ce1653db71cd335405c7452079abcd4691df",
"relationDigest": "17b7e1a8bf07c76449457915d1054099e92b783a37fb47fa25fbd7332b2e739a",
"semanticNameDigest": "2576a581360b1033746428f0e1092c2f952b4dd3515c615787d151c9a69b1371",
"typeId": "PartDesign::Pocket"
}
]
@@ -4365,8 +4365,8 @@
"name": "Body19",
"ordinal": 0,
"relationCount": 52,
"relationDigest": "7ac48255312af69f45b240feb6e2485d81097a8478eb4e61a227812d9d483b0d",
"semanticNameDigest": "d0a87438a01563d7077b011624fb5a10d60f3914fdc9540171bb833a614d1e53",
"relationDigest": "7c1b00fdf204b6bf50cc126fa488aa048bf528ce3d34cd79028eeb1aeca37587",
"semanticNameDigest": "32b62497980ecd089a353b0aeb7c1f6d0681784ee3fafa2b4b0f277696c439d9",
"typeId": "PartDesign::Body"
},
{
@@ -4482,8 +4482,8 @@
"name": "Pocket19",
"ordinal": 11,
"relationCount": 52,
"relationDigest": "f78a86217cd5baac5c480052f092bc7dad9ae743002b1f1ec81adba0451e6c5e",
"semanticNameDigest": "b199cc1fb725b8269da79831c777bd817683c218387ff70e19e61865106aa96e",
"relationDigest": "bd52e5138be030f440045de2c5b1282ccc97bb45ed47c318307632c97a53eb76",
"semanticNameDigest": "c5e1ad7e6d66764cbc7b457b3dddb90051f27aca8464c4cac7cd185d76108b53",
"typeId": "PartDesign::Pocket"
}
],
@@ -4500,8 +4500,8 @@
"name": "Body19",
"ordinal": 0,
"relationCount": 52,
"relationDigest": "7ac48255312af69f45b240feb6e2485d81097a8478eb4e61a227812d9d483b0d",
"semanticNameDigest": "d0a87438a01563d7077b011624fb5a10d60f3914fdc9540171bb833a614d1e53",
"relationDigest": "7c1b00fdf204b6bf50cc126fa488aa048bf528ce3d34cd79028eeb1aeca37587",
"semanticNameDigest": "32b62497980ecd089a353b0aeb7c1f6d0681784ee3fafa2b4b0f277696c439d9",
"typeId": "PartDesign::Body"
},
{
@@ -4617,8 +4617,8 @@
"name": "Pocket19",
"ordinal": 11,
"relationCount": 52,
"relationDigest": "f78a86217cd5baac5c480052f092bc7dad9ae743002b1f1ec81adba0451e6c5e",
"semanticNameDigest": "b199cc1fb725b8269da79831c777bd817683c218387ff70e19e61865106aa96e",
"relationDigest": "bd52e5138be030f440045de2c5b1282ccc97bb45ed47c318307632c97a53eb76",
"semanticNameDigest": "c5e1ad7e6d66764cbc7b457b3dddb90051f27aca8464c4cac7cd185d76108b53",
"typeId": "PartDesign::Pocket"
}
],
@@ -4635,8 +4635,8 @@
"name": "Body19",
"ordinal": 0,
"relationCount": 52,
"relationDigest": "7ac48255312af69f45b240feb6e2485d81097a8478eb4e61a227812d9d483b0d",
"semanticNameDigest": "d0a87438a01563d7077b011624fb5a10d60f3914fdc9540171bb833a614d1e53",
"relationDigest": "7c1b00fdf204b6bf50cc126fa488aa048bf528ce3d34cd79028eeb1aeca37587",
"semanticNameDigest": "32b62497980ecd089a353b0aeb7c1f6d0681784ee3fafa2b4b0f277696c439d9",
"typeId": "PartDesign::Body"
},
{
@@ -4752,8 +4752,8 @@
"name": "Pocket19",
"ordinal": 11,
"relationCount": 52,
"relationDigest": "f78a86217cd5baac5c480052f092bc7dad9ae743002b1f1ec81adba0451e6c5e",
"semanticNameDigest": "b199cc1fb725b8269da79831c777bd817683c218387ff70e19e61865106aa96e",
"relationDigest": "bd52e5138be030f440045de2c5b1282ccc97bb45ed47c318307632c97a53eb76",
"semanticNameDigest": "c5e1ad7e6d66764cbc7b457b3dddb90051f27aca8464c4cac7cd185d76108b53",
"typeId": "PartDesign::Pocket"
}
]
@@ -4778,8 +4778,8 @@
"name": "Body20",
"ordinal": 0,
"relationCount": 51,
"relationDigest": "8500775ec365d405ce5742c1797724a4d7d0fdcc614b45920d595fe70d107280",
"semanticNameDigest": "b469a4746b4b3ad270d35f15fdbadf79144bfce4f4fcb85fcafc695b0a6ca598",
"relationDigest": "d2c546c1b376c3e3e086e52ec2cbbf631126cacd44fccde2480d1ebc87a1e247",
"semanticNameDigest": "a23e1ffaad552432ef689925f97acfe32a0e379e506dc7eb8f5143f3428d20db",
"typeId": "PartDesign::Body"
},
{
@@ -4895,8 +4895,8 @@
"name": "Pocket20",
"ordinal": 11,
"relationCount": 51,
"relationDigest": "da1a0a03b51a789ae779f02be6a5b1baffdca80673909b1b02e1a85963556cdc",
"semanticNameDigest": "8bed43e2124b82ff3c3548f3be6941b288ce2684ecda993a83b640e36dfc45de",
"relationDigest": "7438bef8b5f9d6f1162ba373d1773cdd62af2ebc52c86b35400bf83c5171f5b1",
"semanticNameDigest": "1462a3875d27d44f54e718dd87c347c2beef89c897744b8ebd59a8fc7943dee9",
"typeId": "PartDesign::Pocket"
}
],
@@ -4913,8 +4913,8 @@
"name": "Body20",
"ordinal": 0,
"relationCount": 51,
"relationDigest": "8500775ec365d405ce5742c1797724a4d7d0fdcc614b45920d595fe70d107280",
"semanticNameDigest": "b469a4746b4b3ad270d35f15fdbadf79144bfce4f4fcb85fcafc695b0a6ca598",
"relationDigest": "d2c546c1b376c3e3e086e52ec2cbbf631126cacd44fccde2480d1ebc87a1e247",
"semanticNameDigest": "a23e1ffaad552432ef689925f97acfe32a0e379e506dc7eb8f5143f3428d20db",
"typeId": "PartDesign::Body"
},
{
@@ -5030,8 +5030,8 @@
"name": "Pocket20",
"ordinal": 11,
"relationCount": 51,
"relationDigest": "da1a0a03b51a789ae779f02be6a5b1baffdca80673909b1b02e1a85963556cdc",
"semanticNameDigest": "8bed43e2124b82ff3c3548f3be6941b288ce2684ecda993a83b640e36dfc45de",
"relationDigest": "7438bef8b5f9d6f1162ba373d1773cdd62af2ebc52c86b35400bf83c5171f5b1",
"semanticNameDigest": "1462a3875d27d44f54e718dd87c347c2beef89c897744b8ebd59a8fc7943dee9",
"typeId": "PartDesign::Pocket"
}
],
@@ -5048,8 +5048,8 @@
"name": "Body20",
"ordinal": 0,
"relationCount": 51,
"relationDigest": "8500775ec365d405ce5742c1797724a4d7d0fdcc614b45920d595fe70d107280",
"semanticNameDigest": "b469a4746b4b3ad270d35f15fdbadf79144bfce4f4fcb85fcafc695b0a6ca598",
"relationDigest": "d2c546c1b376c3e3e086e52ec2cbbf631126cacd44fccde2480d1ebc87a1e247",
"semanticNameDigest": "a23e1ffaad552432ef689925f97acfe32a0e379e506dc7eb8f5143f3428d20db",
"typeId": "PartDesign::Body"
},
{
@@ -5165,8 +5165,8 @@
"name": "Pocket20",
"ordinal": 11,
"relationCount": 51,
"relationDigest": "da1a0a03b51a789ae779f02be6a5b1baffdca80673909b1b02e1a85963556cdc",
"semanticNameDigest": "8bed43e2124b82ff3c3548f3be6941b288ce2684ecda993a83b640e36dfc45de",
"relationDigest": "7438bef8b5f9d6f1162ba373d1773cdd62af2ebc52c86b35400bf83c5171f5b1",
"semanticNameDigest": "1462a3875d27d44f54e718dd87c347c2beef89c897744b8ebd59a8fc7943dee9",
"typeId": "PartDesign::Pocket"
}
]
@@ -5390,8 +5390,8 @@
"name": "Cut22",
"ordinal": 2,
"relationCount": 32,
"relationDigest": "313e61ab643adc1fc132732847c963004f7312c3d35307e5e8abc148c51268a1",
"semanticNameDigest": "0e5cd8a02bc1dbf68d1af01e559cc0766a92c5d58975615f26cd32f166f4044b",
"relationDigest": "996db922ea9b879f9f166d344a02e2113e08405c2f7e3bd2c2c30435891e52c5",
"semanticNameDigest": "ae506e04fc0cc271258f65ef5f83d4db416a88ca0aa24c4a2716fd88428dab04",
"typeId": "Part::Cut"
},
{
@@ -5413,8 +5413,8 @@
"name": "CompositeCut22",
"ordinal": 4,
"relationCount": 32,
"relationDigest": "6f68b991611eb08fa97211478b3ad4ecdef5e79fa5e68cf394cd716f94afcffc",
"semanticNameDigest": "e3075f73dfc637c6f0df4c728ccee90f5178100ef4f51b4c60dd99e0f6e02f5a",
"relationDigest": "590320fac4c896df47311594fe48465129ae822f076d2c13f8631fcc21ec6c50",
"semanticNameDigest": "3ee8ce68cce26a0e5695cfa40a50d484b6b934fd2fb87ba727482d7c7d22eb9d",
"typeId": "Part::Cut"
}
],
@@ -5448,8 +5448,8 @@
"name": "Cut22",
"ordinal": 2,
"relationCount": 32,
"relationDigest": "313e61ab643adc1fc132732847c963004f7312c3d35307e5e8abc148c51268a1",
"semanticNameDigest": "0e5cd8a02bc1dbf68d1af01e559cc0766a92c5d58975615f26cd32f166f4044b",
"relationDigest": "996db922ea9b879f9f166d344a02e2113e08405c2f7e3bd2c2c30435891e52c5",
"semanticNameDigest": "ae506e04fc0cc271258f65ef5f83d4db416a88ca0aa24c4a2716fd88428dab04",
"typeId": "Part::Cut"
},
{
@@ -5471,8 +5471,8 @@
"name": "CompositeCut22",
"ordinal": 4,
"relationCount": 32,
"relationDigest": "6f68b991611eb08fa97211478b3ad4ecdef5e79fa5e68cf394cd716f94afcffc",
"semanticNameDigest": "e3075f73dfc637c6f0df4c728ccee90f5178100ef4f51b4c60dd99e0f6e02f5a",
"relationDigest": "590320fac4c896df47311594fe48465129ae822f076d2c13f8631fcc21ec6c50",
"semanticNameDigest": "3ee8ce68cce26a0e5695cfa40a50d484b6b934fd2fb87ba727482d7c7d22eb9d",
"typeId": "Part::Cut"
}
],
@@ -5506,8 +5506,8 @@
"name": "Cut22",
"ordinal": 2,
"relationCount": 32,
"relationDigest": "313e61ab643adc1fc132732847c963004f7312c3d35307e5e8abc148c51268a1",
"semanticNameDigest": "0e5cd8a02bc1dbf68d1af01e559cc0766a92c5d58975615f26cd32f166f4044b",
"relationDigest": "996db922ea9b879f9f166d344a02e2113e08405c2f7e3bd2c2c30435891e52c5",
"semanticNameDigest": "ae506e04fc0cc271258f65ef5f83d4db416a88ca0aa24c4a2716fd88428dab04",
"typeId": "Part::Cut"
},
{
@@ -5529,8 +5529,8 @@
"name": "CompositeCut22",
"ordinal": 4,
"relationCount": 32,
"relationDigest": "6f68b991611eb08fa97211478b3ad4ecdef5e79fa5e68cf394cd716f94afcffc",
"semanticNameDigest": "e3075f73dfc637c6f0df4c728ccee90f5178100ef4f51b4c60dd99e0f6e02f5a",
"relationDigest": "590320fac4c896df47311594fe48465129ae822f076d2c13f8631fcc21ec6c50",
"semanticNameDigest": "3ee8ce68cce26a0e5695cfa40a50d484b6b934fd2fb87ba727482d7c7d22eb9d",
"typeId": "Part::Cut"
}
]
@@ -5572,8 +5572,8 @@
"name": "Fuse23",
"ordinal": 2,
"relationCount": 66,
"relationDigest": "fb5f594e7ecfd1af640e03fd611c3dcfe307a0b73984a84c5f166cdf1bda8b37",
"semanticNameDigest": "27303087c0b01d8d12cf49d74cce534b6fab860be18f8b3e1cacd39c00352265",
"relationDigest": "968f251a6eb6f3ae7dd3238fff6a4df0da78ce59e04834c0e173fe09b379aa72",
"semanticNameDigest": "0c26153700cd1251e4a8f16adc7a704027e7cd1906781864139b3e8f80b0436b",
"typeId": "Part::Fuse"
},
{
@@ -5595,8 +5595,8 @@
"name": "CompositeCut23",
"ordinal": 4,
"relationCount": 92,
"relationDigest": "5bb05cfc4ff94ba18542e1dd528881023d9432a370bd6bf56356688da522f6de",
"semanticNameDigest": "17abafbaffa74dff1962deb78a26083dcfe3cf61e8b4e12ce62199d0eefa41f5",
"relationDigest": "a77f2e171ed54e5f8cfeba97480a3daddf6ce259da1451940c5759e883177d21",
"semanticNameDigest": "6fc68881ed87e4bfde05198303c7334d0f8a7d98900dbf7f7352342a471d5927",
"typeId": "Part::Cut"
}
],
@@ -5630,8 +5630,8 @@
"name": "Fuse23",
"ordinal": 2,
"relationCount": 66,
"relationDigest": "fb5f594e7ecfd1af640e03fd611c3dcfe307a0b73984a84c5f166cdf1bda8b37",
"semanticNameDigest": "27303087c0b01d8d12cf49d74cce534b6fab860be18f8b3e1cacd39c00352265",
"relationDigest": "968f251a6eb6f3ae7dd3238fff6a4df0da78ce59e04834c0e173fe09b379aa72",
"semanticNameDigest": "0c26153700cd1251e4a8f16adc7a704027e7cd1906781864139b3e8f80b0436b",
"typeId": "Part::Fuse"
},
{
@@ -5653,8 +5653,8 @@
"name": "CompositeCut23",
"ordinal": 4,
"relationCount": 92,
"relationDigest": "5bb05cfc4ff94ba18542e1dd528881023d9432a370bd6bf56356688da522f6de",
"semanticNameDigest": "17abafbaffa74dff1962deb78a26083dcfe3cf61e8b4e12ce62199d0eefa41f5",
"relationDigest": "a77f2e171ed54e5f8cfeba97480a3daddf6ce259da1451940c5759e883177d21",
"semanticNameDigest": "6fc68881ed87e4bfde05198303c7334d0f8a7d98900dbf7f7352342a471d5927",
"typeId": "Part::Cut"
}
],
@@ -5688,8 +5688,8 @@
"name": "Fuse23",
"ordinal": 2,
"relationCount": 66,
"relationDigest": "fb5f594e7ecfd1af640e03fd611c3dcfe307a0b73984a84c5f166cdf1bda8b37",
"semanticNameDigest": "27303087c0b01d8d12cf49d74cce534b6fab860be18f8b3e1cacd39c00352265",
"relationDigest": "968f251a6eb6f3ae7dd3238fff6a4df0da78ce59e04834c0e173fe09b379aa72",
"semanticNameDigest": "0c26153700cd1251e4a8f16adc7a704027e7cd1906781864139b3e8f80b0436b",
"typeId": "Part::Fuse"
},
{
@@ -5711,8 +5711,8 @@
"name": "CompositeCut23",
"ordinal": 4,
"relationCount": 92,
"relationDigest": "5bb05cfc4ff94ba18542e1dd528881023d9432a370bd6bf56356688da522f6de",
"semanticNameDigest": "17abafbaffa74dff1962deb78a26083dcfe3cf61e8b4e12ce62199d0eefa41f5",
"relationDigest": "a77f2e171ed54e5f8cfeba97480a3daddf6ce259da1451940c5759e883177d21",
"semanticNameDigest": "6fc68881ed87e4bfde05198303c7334d0f8a7d98900dbf7f7352342a471d5927",
"typeId": "Part::Cut"
}
]
@@ -5754,8 +5754,8 @@
"name": "Cut24",
"ordinal": 2,
"relationCount": 32,
"relationDigest": "05f84541c1424e27110b860941ad4d17c2120c5b1f874a8572218eca31993e46",
"semanticNameDigest": "ae506e04fc0cc271258f65ef5f83d4db416a88ca0aa24c4a2716fd88428dab04",
"relationDigest": "1ed5f1320e33ded3a77834fdb9d943ee7b234db13649c8a9e51f5acd35031626",
"semanticNameDigest": "e57854d68a7acdcdef26c8cbdc6173317eeb040d08331848717165c8294e685f",
"typeId": "Part::Cut"
},
{
@@ -5777,8 +5777,8 @@
"name": "CompositeCut24",
"ordinal": 4,
"relationCount": 32,
"relationDigest": "0a40b440a48e5ae101c9d7c050170a0bfddb5495b84c4c27f8c80ac85cce73e9",
"semanticNameDigest": "3ee8ce68cce26a0e5695cfa40a50d484b6b934fd2fb87ba727482d7c7d22eb9d",
"relationDigest": "3536dcc7f7c1d21f91e6ca952ffdcc7d06581376a807860fda3bee1f34f68519",
"semanticNameDigest": "57985accd97b0ed3c94f0f8b40227bcf86974fafe5ad3a502e64f30d0f138943",
"typeId": "Part::Cut"
}
],
@@ -5812,8 +5812,8 @@
"name": "Cut24",
"ordinal": 2,
"relationCount": 32,
"relationDigest": "05f84541c1424e27110b860941ad4d17c2120c5b1f874a8572218eca31993e46",
"semanticNameDigest": "ae506e04fc0cc271258f65ef5f83d4db416a88ca0aa24c4a2716fd88428dab04",
"relationDigest": "1ed5f1320e33ded3a77834fdb9d943ee7b234db13649c8a9e51f5acd35031626",
"semanticNameDigest": "e57854d68a7acdcdef26c8cbdc6173317eeb040d08331848717165c8294e685f",
"typeId": "Part::Cut"
},
{
@@ -5835,8 +5835,8 @@
"name": "CompositeCut24",
"ordinal": 4,
"relationCount": 32,
"relationDigest": "0a40b440a48e5ae101c9d7c050170a0bfddb5495b84c4c27f8c80ac85cce73e9",
"semanticNameDigest": "3ee8ce68cce26a0e5695cfa40a50d484b6b934fd2fb87ba727482d7c7d22eb9d",
"relationDigest": "3536dcc7f7c1d21f91e6ca952ffdcc7d06581376a807860fda3bee1f34f68519",
"semanticNameDigest": "57985accd97b0ed3c94f0f8b40227bcf86974fafe5ad3a502e64f30d0f138943",
"typeId": "Part::Cut"
}
],
@@ -5870,8 +5870,8 @@
"name": "Cut24",
"ordinal": 2,
"relationCount": 32,
"relationDigest": "05f84541c1424e27110b860941ad4d17c2120c5b1f874a8572218eca31993e46",
"semanticNameDigest": "ae506e04fc0cc271258f65ef5f83d4db416a88ca0aa24c4a2716fd88428dab04",
"relationDigest": "1ed5f1320e33ded3a77834fdb9d943ee7b234db13649c8a9e51f5acd35031626",
"semanticNameDigest": "e57854d68a7acdcdef26c8cbdc6173317eeb040d08331848717165c8294e685f",
"typeId": "Part::Cut"
},
{
@@ -5893,8 +5893,8 @@
"name": "CompositeCut24",
"ordinal": 4,
"relationCount": 32,
"relationDigest": "0a40b440a48e5ae101c9d7c050170a0bfddb5495b84c4c27f8c80ac85cce73e9",
"semanticNameDigest": "3ee8ce68cce26a0e5695cfa40a50d484b6b934fd2fb87ba727482d7c7d22eb9d",
"relationDigest": "3536dcc7f7c1d21f91e6ca952ffdcc7d06581376a807860fda3bee1f34f68519",
"semanticNameDigest": "57985accd97b0ed3c94f0f8b40227bcf86974fafe5ad3a502e64f30d0f138943",
"typeId": "Part::Cut"
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -56,7 +56,7 @@
"cancel": {
"setup": "Create an active Body with a valid closed Sketch and open PartDesign_Pad.",
"action": "Change Length, observe preview, then cancel the Task.",
"expected": "Preview state is discarded, no Pad is committed and selection, active Body and focus return to their pre-command state."
"expected": "Preview state is discarded, no Pad is committed, the selected profile remains consumed as in native FreeCAD, the active Body is preserved and focus moves from the Pad length field to TaskView."
},
"recovery": {
"setup": "Continue from the failed or cancelled Pad workflow in the same document.",
@@ -83,28 +83,44 @@
},
{
"taskId": "ORA-GUI-WF-002",
"status": "in_progress",
"evidence": []
"status": "completed",
"evidence": [
"config/freecad-gui-workflow-oracle.json",
"scripts/check-freecad-gui-workflow-oracle.mjs"
]
},
{
"taskId": "ORA-GUI-WF-003",
"status": "pending",
"evidence": []
"status": "completed",
"evidence": [
"config/freecad-gui-workflow-oracle.json",
"scripts/check-freecad-gui-workflow-oracle.mjs"
]
},
{
"taskId": "ORA-GUI-WF-004",
"status": "pending",
"evidence": []
"status": "completed",
"evidence": [
"config/freecad-gui-workflow-oracle.json",
"scripts/check-freecad-gui-workflow-oracle.mjs"
]
},
{
"taskId": "ORA-GUI-WF-005",
"status": "pending",
"evidence": []
"status": "completed",
"evidence": [
"config/freecad-gui-workflow-oracle.json",
"scripts/check-freecad-gui-workflow-oracle.mjs"
]
},
{
"taskId": "ORA-GUI-WF-006",
"status": "pending",
"evidence": []
"status": "completed",
"evidence": [
"config/freecad-web-exact-parity-plan.json",
"scripts/check-freecad-gui-workflow-plan.mjs",
"scripts/generate-freecad-active-work-queue.mjs"
]
}
]
}

View File

@@ -0,0 +1,468 @@
{
"schemaVersion": 1,
"baseline": {
"freecadVersion": "1.1.1",
"commit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d",
"oracleId": "freecad-1.1.1-composite-history-elementmap2"
},
"generatedBy": "./npmw run probe:freecad-recovered-naming-classification",
"checkedBy": "./npmw run check:freecad-recovered-naming-classification",
"nativeProbe": {
"path": "scripts/freecad-composite-history-elementmap-oracle.py",
"bytes": 30200,
"sha256": "8280a3da7396734659399789d7d6878294be7057628a7f18cb2ebeb8c2cdafd7",
"independentRuns": 2
},
"classifications": [
{
"taskId": "TSN-DRIFT-000",
"caseId": "partdesign-plain",
"classification": "allowed_evolution",
"reasonCode": "native-history-stabilizes-after-edit-restore",
"rationale": "FreeCAD changes the Body and Pad naming history after the first Length edit/restore while restoring geometry exactly; the evolved names repeat deterministically and remain stable through save, reopen, and resave.",
"reference": {
"caseId": "partdesign-plain",
"contract": {
"targetObject": "Pad10",
"targetTypeId": "PartDesign::Pad",
"propertyPath": "Length",
"propertyType": "App::PropertyLength",
"finalObject": "Pad10"
},
"values": {
"before": 7,
"edited": 7.75,
"restored": 7
},
"driftOrdinals": [
0,
9
],
"stages": [
{
"ordinal": 0,
"name": "Body10",
"typeId": "PartDesign::Body",
"nominal": {
"geometryDigest": "2470d037ef123af6ca1a5ff874a20f55d07a66bf56cd92bf3235b3029511be94",
"relationDigest": "df337229bd80a0b7942ecc9c4fbb7146cbe9eb3dca9459c093674f02eaee3c65",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"before": {
"geometryDigest": "2470d037ef123af6ca1a5ff874a20f55d07a66bf56cd92bf3235b3029511be94",
"relationDigest": "fa52eaf3d694f274c37bedfaa576d883eb04d15a8bb4c46b5070dd249e4f77e4",
"semanticNameDigest": "e07d8f0ab682e495d52c97a9d6b90e5118a60b25266785f1fc5b9cf85744ef7d"
},
"edited": {
"geometryDigest": "fed2b972ec166149c219536523b05f1741885d073a6c5dbc7c36e92e047c2a85",
"relationDigest": "48e9b7d748311a779d2c3e84d882ebb0b3023bf33000015f57ed19cbc5a209ac",
"semanticNameDigest": "8f307d5ca4c52c1887be472b56e18442b32d61713c815ca48ca5ab42c49b1ce4"
},
"restored": {
"geometryDigest": "2470d037ef123af6ca1a5ff874a20f55d07a66bf56cd92bf3235b3029511be94",
"relationDigest": "df337229bd80a0b7942ecc9c4fbb7146cbe9eb3dca9459c093674f02eaee3c65",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"persistence": {
"initial": {
"relationDigest": "df337229bd80a0b7942ecc9c4fbb7146cbe9eb3dca9459c093674f02eaee3c65",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"reopened": {
"relationDigest": "df337229bd80a0b7942ecc9c4fbb7146cbe9eb3dca9459c093674f02eaee3c65",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"resaved": {
"relationDigest": "df337229bd80a0b7942ecc9c4fbb7146cbe9eb3dca9459c093674f02eaee3c65",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
}
}
},
{
"ordinal": 9,
"name": "Pad10",
"typeId": "PartDesign::Pad",
"nominal": {
"geometryDigest": "2470d037ef123af6ca1a5ff874a20f55d07a66bf56cd92bf3235b3029511be94",
"relationDigest": "999099e645e2fe946dcc016eb3ee0271821854853545306b3d1c06a22f32ad2c",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"before": {
"geometryDigest": "2470d037ef123af6ca1a5ff874a20f55d07a66bf56cd92bf3235b3029511be94",
"relationDigest": "66fe58ac4a4e582e5ce5026f59af7afcfbc8b12bd8efc233e66d36e716fea025",
"semanticNameDigest": "7016833e7f7d1ea833c88070c3976df629b312251c0b45da355b937ca2425eaa"
},
"edited": {
"geometryDigest": "fed2b972ec166149c219536523b05f1741885d073a6c5dbc7c36e92e047c2a85",
"relationDigest": "c87b2a99e65ad7ca1f7f42863c6c75ef21b92a7cd5fb04722a9d7fe5fb61509d",
"semanticNameDigest": "e60c184158a6d432b63a52e686588e64b204949b19118add959d63c15895a2b4"
},
"restored": {
"geometryDigest": "2470d037ef123af6ca1a5ff874a20f55d07a66bf56cd92bf3235b3029511be94",
"relationDigest": "999099e645e2fe946dcc016eb3ee0271821854853545306b3d1c06a22f32ad2c",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"persistence": {
"initial": {
"relationDigest": "999099e645e2fe946dcc016eb3ee0271821854853545306b3d1c06a22f32ad2c",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"reopened": {
"relationDigest": "999099e645e2fe946dcc016eb3ee0271821854853545306b3d1c06a22f32ad2c",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"resaved": {
"relationDigest": "999099e645e2fe946dcc016eb3ee0271821854853545306b3d1c06a22f32ad2c",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
}
}
}
],
"checks": {
"propertyRestored": true,
"geometryRestored": true,
"namingEvolved": true,
"restoredMatchesNominal": true,
"persistenceStable": true
}
},
"replay": {
"caseId": "partdesign-plain",
"contract": {
"targetObject": "Pad10",
"targetTypeId": "PartDesign::Pad",
"propertyPath": "Length",
"propertyType": "App::PropertyLength",
"finalObject": "Pad10"
},
"values": {
"before": 7,
"edited": 7.75,
"restored": 7
},
"driftOrdinals": [
0,
9
],
"stages": [
{
"ordinal": 0,
"name": "Body10",
"typeId": "PartDesign::Body",
"nominal": {
"geometryDigest": "2470d037ef123af6ca1a5ff874a20f55d07a66bf56cd92bf3235b3029511be94",
"relationDigest": "df337229bd80a0b7942ecc9c4fbb7146cbe9eb3dca9459c093674f02eaee3c65",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"before": {
"geometryDigest": "2470d037ef123af6ca1a5ff874a20f55d07a66bf56cd92bf3235b3029511be94",
"relationDigest": "fa52eaf3d694f274c37bedfaa576d883eb04d15a8bb4c46b5070dd249e4f77e4",
"semanticNameDigest": "e07d8f0ab682e495d52c97a9d6b90e5118a60b25266785f1fc5b9cf85744ef7d"
},
"edited": {
"geometryDigest": "fed2b972ec166149c219536523b05f1741885d073a6c5dbc7c36e92e047c2a85",
"relationDigest": "48e9b7d748311a779d2c3e84d882ebb0b3023bf33000015f57ed19cbc5a209ac",
"semanticNameDigest": "8f307d5ca4c52c1887be472b56e18442b32d61713c815ca48ca5ab42c49b1ce4"
},
"restored": {
"geometryDigest": "2470d037ef123af6ca1a5ff874a20f55d07a66bf56cd92bf3235b3029511be94",
"relationDigest": "df337229bd80a0b7942ecc9c4fbb7146cbe9eb3dca9459c093674f02eaee3c65",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"persistence": {
"initial": {
"relationDigest": "df337229bd80a0b7942ecc9c4fbb7146cbe9eb3dca9459c093674f02eaee3c65",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"reopened": {
"relationDigest": "df337229bd80a0b7942ecc9c4fbb7146cbe9eb3dca9459c093674f02eaee3c65",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"resaved": {
"relationDigest": "df337229bd80a0b7942ecc9c4fbb7146cbe9eb3dca9459c093674f02eaee3c65",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
}
}
},
{
"ordinal": 9,
"name": "Pad10",
"typeId": "PartDesign::Pad",
"nominal": {
"geometryDigest": "2470d037ef123af6ca1a5ff874a20f55d07a66bf56cd92bf3235b3029511be94",
"relationDigest": "999099e645e2fe946dcc016eb3ee0271821854853545306b3d1c06a22f32ad2c",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"before": {
"geometryDigest": "2470d037ef123af6ca1a5ff874a20f55d07a66bf56cd92bf3235b3029511be94",
"relationDigest": "66fe58ac4a4e582e5ce5026f59af7afcfbc8b12bd8efc233e66d36e716fea025",
"semanticNameDigest": "7016833e7f7d1ea833c88070c3976df629b312251c0b45da355b937ca2425eaa"
},
"edited": {
"geometryDigest": "fed2b972ec166149c219536523b05f1741885d073a6c5dbc7c36e92e047c2a85",
"relationDigest": "c87b2a99e65ad7ca1f7f42863c6c75ef21b92a7cd5fb04722a9d7fe5fb61509d",
"semanticNameDigest": "e60c184158a6d432b63a52e686588e64b204949b19118add959d63c15895a2b4"
},
"restored": {
"geometryDigest": "2470d037ef123af6ca1a5ff874a20f55d07a66bf56cd92bf3235b3029511be94",
"relationDigest": "999099e645e2fe946dcc016eb3ee0271821854853545306b3d1c06a22f32ad2c",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"persistence": {
"initial": {
"relationDigest": "999099e645e2fe946dcc016eb3ee0271821854853545306b3d1c06a22f32ad2c",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"reopened": {
"relationDigest": "999099e645e2fe946dcc016eb3ee0271821854853545306b3d1c06a22f32ad2c",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"resaved": {
"relationDigest": "999099e645e2fe946dcc016eb3ee0271821854853545306b3d1c06a22f32ad2c",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
}
}
}
],
"checks": {
"propertyRestored": true,
"geometryRestored": true,
"namingEvolved": true,
"restoredMatchesNominal": true,
"persistenceStable": true
}
},
"replayStable": true,
"implementationDefect": false
},
{
"taskId": "TSN-DRIFT-001",
"caseId": "partdesign-midplane",
"classification": "allowed_evolution",
"reasonCode": "native-history-stabilizes-after-edit-restore",
"rationale": "FreeCAD applies the same deterministic history evolution to a Midplane Pad Length edit/restore: Body and Pad geometry returns exactly, and the evolved names remain stable through save, reopen, and resave.",
"reference": {
"caseId": "partdesign-midplane",
"contract": {
"targetObject": "Pad11",
"targetTypeId": "PartDesign::Pad",
"propertyPath": "Length",
"propertyType": "App::PropertyLength",
"finalObject": "Pad11"
},
"values": {
"before": 8,
"edited": 8.75,
"restored": 8
},
"driftOrdinals": [
0,
9
],
"stages": [
{
"ordinal": 0,
"name": "Body11",
"typeId": "PartDesign::Body",
"nominal": {
"geometryDigest": "cd9766573816715b02d2f470bf39f7d25903d9ebd899216f4ca0b1e5b7f5ec61",
"relationDigest": "9a9b06f6a9ed4e0960ca994e30be5dcc46b106c040fb3de214c1ad9e04a32df6",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"before": {
"geometryDigest": "cd9766573816715b02d2f470bf39f7d25903d9ebd899216f4ca0b1e5b7f5ec61",
"relationDigest": "0af3ea2e0a408ecb48e05cdb6fe9d8c902448a0fcd5de032b9d277fc18c3506c",
"semanticNameDigest": "e07d8f0ab682e495d52c97a9d6b90e5118a60b25266785f1fc5b9cf85744ef7d"
},
"edited": {
"geometryDigest": "8c54f6f4cca5d847f3a5573265e141a6ba9b073523f7e9bfc2b534c122ac262b",
"relationDigest": "c582774f25bda9641a075a0dd0849138d58a4964f47d9ac23af2e7296e20dfe6",
"semanticNameDigest": "8f307d5ca4c52c1887be472b56e18442b32d61713c815ca48ca5ab42c49b1ce4"
},
"restored": {
"geometryDigest": "cd9766573816715b02d2f470bf39f7d25903d9ebd899216f4ca0b1e5b7f5ec61",
"relationDigest": "9a9b06f6a9ed4e0960ca994e30be5dcc46b106c040fb3de214c1ad9e04a32df6",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"persistence": {
"initial": {
"relationDigest": "9a9b06f6a9ed4e0960ca994e30be5dcc46b106c040fb3de214c1ad9e04a32df6",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"reopened": {
"relationDigest": "9a9b06f6a9ed4e0960ca994e30be5dcc46b106c040fb3de214c1ad9e04a32df6",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"resaved": {
"relationDigest": "9a9b06f6a9ed4e0960ca994e30be5dcc46b106c040fb3de214c1ad9e04a32df6",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
}
}
},
{
"ordinal": 9,
"name": "Pad11",
"typeId": "PartDesign::Pad",
"nominal": {
"geometryDigest": "cd9766573816715b02d2f470bf39f7d25903d9ebd899216f4ca0b1e5b7f5ec61",
"relationDigest": "9681bb9e87e6ac36a624592e0758a16ebca09dcb2a784da2c068ab4d0471e833",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"before": {
"geometryDigest": "cd9766573816715b02d2f470bf39f7d25903d9ebd899216f4ca0b1e5b7f5ec61",
"relationDigest": "85322bc9601e07fd55571161c64c91950e5f57303c3d349aeaac2d3f3f1b6dd8",
"semanticNameDigest": "7016833e7f7d1ea833c88070c3976df629b312251c0b45da355b937ca2425eaa"
},
"edited": {
"geometryDigest": "8c54f6f4cca5d847f3a5573265e141a6ba9b073523f7e9bfc2b534c122ac262b",
"relationDigest": "b37bd6d5a67f283d645e407f4f2a71bb9676d8d61fe37ab694ec5b524e6a233c",
"semanticNameDigest": "e60c184158a6d432b63a52e686588e64b204949b19118add959d63c15895a2b4"
},
"restored": {
"geometryDigest": "cd9766573816715b02d2f470bf39f7d25903d9ebd899216f4ca0b1e5b7f5ec61",
"relationDigest": "9681bb9e87e6ac36a624592e0758a16ebca09dcb2a784da2c068ab4d0471e833",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"persistence": {
"initial": {
"relationDigest": "9681bb9e87e6ac36a624592e0758a16ebca09dcb2a784da2c068ab4d0471e833",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"reopened": {
"relationDigest": "9681bb9e87e6ac36a624592e0758a16ebca09dcb2a784da2c068ab4d0471e833",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"resaved": {
"relationDigest": "9681bb9e87e6ac36a624592e0758a16ebca09dcb2a784da2c068ab4d0471e833",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
}
}
}
],
"checks": {
"propertyRestored": true,
"geometryRestored": true,
"namingEvolved": true,
"restoredMatchesNominal": true,
"persistenceStable": true
}
},
"replay": {
"caseId": "partdesign-midplane",
"contract": {
"targetObject": "Pad11",
"targetTypeId": "PartDesign::Pad",
"propertyPath": "Length",
"propertyType": "App::PropertyLength",
"finalObject": "Pad11"
},
"values": {
"before": 8,
"edited": 8.75,
"restored": 8
},
"driftOrdinals": [
0,
9
],
"stages": [
{
"ordinal": 0,
"name": "Body11",
"typeId": "PartDesign::Body",
"nominal": {
"geometryDigest": "cd9766573816715b02d2f470bf39f7d25903d9ebd899216f4ca0b1e5b7f5ec61",
"relationDigest": "9a9b06f6a9ed4e0960ca994e30be5dcc46b106c040fb3de214c1ad9e04a32df6",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"before": {
"geometryDigest": "cd9766573816715b02d2f470bf39f7d25903d9ebd899216f4ca0b1e5b7f5ec61",
"relationDigest": "0af3ea2e0a408ecb48e05cdb6fe9d8c902448a0fcd5de032b9d277fc18c3506c",
"semanticNameDigest": "e07d8f0ab682e495d52c97a9d6b90e5118a60b25266785f1fc5b9cf85744ef7d"
},
"edited": {
"geometryDigest": "8c54f6f4cca5d847f3a5573265e141a6ba9b073523f7e9bfc2b534c122ac262b",
"relationDigest": "c582774f25bda9641a075a0dd0849138d58a4964f47d9ac23af2e7296e20dfe6",
"semanticNameDigest": "8f307d5ca4c52c1887be472b56e18442b32d61713c815ca48ca5ab42c49b1ce4"
},
"restored": {
"geometryDigest": "cd9766573816715b02d2f470bf39f7d25903d9ebd899216f4ca0b1e5b7f5ec61",
"relationDigest": "9a9b06f6a9ed4e0960ca994e30be5dcc46b106c040fb3de214c1ad9e04a32df6",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"persistence": {
"initial": {
"relationDigest": "9a9b06f6a9ed4e0960ca994e30be5dcc46b106c040fb3de214c1ad9e04a32df6",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"reopened": {
"relationDigest": "9a9b06f6a9ed4e0960ca994e30be5dcc46b106c040fb3de214c1ad9e04a32df6",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
},
"resaved": {
"relationDigest": "9a9b06f6a9ed4e0960ca994e30be5dcc46b106c040fb3de214c1ad9e04a32df6",
"semanticNameDigest": "0aea366491ac416aec8e6e055434063d2e0645f1f347b1f3b60363ac135c3e85"
}
}
},
{
"ordinal": 9,
"name": "Pad11",
"typeId": "PartDesign::Pad",
"nominal": {
"geometryDigest": "cd9766573816715b02d2f470bf39f7d25903d9ebd899216f4ca0b1e5b7f5ec61",
"relationDigest": "9681bb9e87e6ac36a624592e0758a16ebca09dcb2a784da2c068ab4d0471e833",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"before": {
"geometryDigest": "cd9766573816715b02d2f470bf39f7d25903d9ebd899216f4ca0b1e5b7f5ec61",
"relationDigest": "85322bc9601e07fd55571161c64c91950e5f57303c3d349aeaac2d3f3f1b6dd8",
"semanticNameDigest": "7016833e7f7d1ea833c88070c3976df629b312251c0b45da355b937ca2425eaa"
},
"edited": {
"geometryDigest": "8c54f6f4cca5d847f3a5573265e141a6ba9b073523f7e9bfc2b534c122ac262b",
"relationDigest": "b37bd6d5a67f283d645e407f4f2a71bb9676d8d61fe37ab694ec5b524e6a233c",
"semanticNameDigest": "e60c184158a6d432b63a52e686588e64b204949b19118add959d63c15895a2b4"
},
"restored": {
"geometryDigest": "cd9766573816715b02d2f470bf39f7d25903d9ebd899216f4ca0b1e5b7f5ec61",
"relationDigest": "9681bb9e87e6ac36a624592e0758a16ebca09dcb2a784da2c068ab4d0471e833",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"persistence": {
"initial": {
"relationDigest": "9681bb9e87e6ac36a624592e0758a16ebca09dcb2a784da2c068ab4d0471e833",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"reopened": {
"relationDigest": "9681bb9e87e6ac36a624592e0758a16ebca09dcb2a784da2c068ab4d0471e833",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
},
"resaved": {
"relationDigest": "9681bb9e87e6ac36a624592e0758a16ebca09dcb2a784da2c068ab4d0471e833",
"semanticNameDigest": "6590487bf8f5e5962b6c640db274d950f0fa6d6a44fa97231e72d7587e3aac3e"
}
}
}
],
"checks": {
"propertyRestored": true,
"geometryRestored": true,
"namingEvolved": true,
"restoredMatchesNominal": true,
"persistenceStable": true
}
},
"replayStable": true,
"implementationDefect": false
}
],
"summary": {
"classifiedCases": 2,
"classifiedStages": 4,
"stableSemantics": 0,
"allowedEvolution": 2,
"implementationDefects": 0,
"unknown": 0
}
}

View File

@@ -20,7 +20,7 @@
"id": "EX01",
"title": "Exhaustive FreeCAD oracle and optional dependency baseline",
"tasks": [
{ "id": "EX-ORA-01", "title": "Capture exhaustive runtime objects, properties, commands and GUI states", "priority": "P0", "status": "in_progress", "dependencies": [], "deliverables": ["Runtime oracle for every registered TypeId and command", "Success, failure, disabled and cancel fixtures", "Source-to-runtime trace"], "acceptance": ["All inventory candidates are promoted or explicitly excluded", "No command or property has an unknown runtime state"], "evidence": ["check:freecad-inventory", "check:freecad-type-properties", "check:freecad-gui-commands", "check:freecad-oracle-coverage", "check:freecad-gui-workflow"], "exactBlockedBy": ["The isolated desktop oracle covers 1111 workbench-startup commands in 100 fail-closed shards, 19 source-only commands and 2 context-only commands; the PartDesign Pad success Task workflow is captured, while disabled, failure, cancel, recovery and the remaining command-family workflows are still open"] },
{ "id": "EX-ORA-01", "title": "Capture exhaustive runtime objects, properties, commands and GUI states", "priority": "P0", "status": "in_progress", "dependencies": [], "deliverables": ["Runtime oracle for every registered TypeId and command", "Success, failure, disabled and cancel fixtures", "Source-to-runtime trace"], "acceptance": ["All inventory candidates are promoted or explicitly excluded", "No command or property has an unknown runtime state"], "evidence": ["check:freecad-inventory", "check:freecad-type-properties", "check:freecad-gui-commands", "check:freecad-oracle-coverage", "check:freecad-gui-workflow"], "exactBlockedBy": ["The isolated desktop oracle covers 1111 workbench-startup commands in 100 fail-closed shards, 19 source-only commands and 2 context-only commands; the first PartDesign Pad family now has native success, disabled, failure, cancel and recovery workflows, while the remaining command-family workflows and runtime object/property exclusions are still open"] },
{ "id": "EX-ORA-02", "title": "Expand geometry, history and FCStd golden corpus to every feature family", "priority": "P0", "status": "in_progress", "dependencies": ["EX-ORA-01"], "deliverables": ["Parameterized success and failure corpus", "Native Shape, history, ElementMap2 and StringHasher evidence", "FreeCAD save-reopen-resave evidence"], "acceptance": ["Every editable feature family has boundary and mutation fixtures", "Unknown semantic differences are zero"], "evidence": ["check:freecad-golden-fixtures", "check:freecad-composite-history-elementmap", "check:freecad-golden-coverage", "check:freecad-parameter-mutations", "check:freecad-partdesign-failures", "check:freecad-partdesign-revolution-groove", "check:freecad-partdesign-transform", "check:freecad-part-builders"], "exactBlockedBy": ["Parameter mutation matrix is fail-closed until every family has explicit edit/recompute/recovery and round-trip evidence"] },
{ "id": "EX-ORA-03", "title": "Lock all optional libraries, platform capabilities and license boundaries", "priority": "P1", "status": "pending", "dependencies": ["EX-ORA-01"], "deliverables": ["Optional dependency manifest", "Browser, sidecar or proxy decision per dependency", "Reproducible hashes and licenses"], "acceptance": ["Every optional FreeCAD capability has one executable strategy", "No undeclared host-only dependency affects a claim"], "evidence": ["check:baseline", "check:sbom"], "exactBlockedBy": ["Several desktop-only optional libraries remain proxy or sidecar capabilities"] }
]
@@ -34,7 +34,7 @@
{ "id": "EX-TSN-01", "title": "Capture native stage Shape and Generated/Modified/Deleted for every builder", "priority": "P0", "status": "completed", "dependencies": ["EX-KER-01", "EX-TSN-00"], "deliverables": ["All builder-stage captures", "Multi-input and one-to-many lineage", "Local feature recompute history"], "acceptance": ["Every feature result relation has a producing native stage", "No final-result index is applied to an intermediate Shape"], "evidence": ["check:freecad-composite-history-elementmap", "check:freecad-tsn-stage-evidence"], "exactBlockedBy": [] },
{ "id": "EX-TSN-02", "title": "Expose native MappedNameRef and StringHasher decisions for every feature", "priority": "P0", "status": "completed", "dependencies": ["EX-TSN-01"], "deliverables": ["Native naming callback ABI", "Per-stage StringHasher evidence", "Lossless ElementMap2 token generation"], "acceptance": ["All new tokens originate in native evidence", "FreeCAD round-trip names remain byte and semantic stable"], "evidence": ["check:freecad-native-naming-evidence", "check:freecad-sketcher-partdesign-abi", "check:freecad-naming-sdk", "check:freecad-naming-production", "check:chrome-freecad-naming-production", "check:freecad-private-naming-boundary", "check:occt-history-artifact"], "exactBlockedBy": [] },
{ "id": "EX-TSN-03", "title": "Resolve isomorphic topology only from unique native provenance", "priority": "P0", "status": "completed", "dependencies": ["EX-TSN-01", "EX-TSN-02"], "deliverables": ["Symmetric Boolean corpus", "Candidate provenance graph", "Ambiguity repair lifecycle"], "acceptance": ["Unique native sources resolve deterministically", "Non-unique sources remain ambiguous across save, undo and recompute"], "evidence": ["test:topology-replay", "test:facade", "check:freecad-isomorphic-provenance", "check:freecad-naming-production", "check:chrome-freecad-naming-production", "check:freecad-exact-history-elementmap-gate"], "exactBlockedBy": [] },
{ "id": "EX-TSN-04", "title": "Close topology naming across all feature edits and FCStd round-trips", "priority": "P0", "status": "in_progress", "dependencies": ["EX-TSN-02", "EX-TSN-03"], "deliverables": ["Cross-feature mutation matrix", "ElementMap2 history migration", "Long-chain LinkSub stability report"], "acceptance": ["Wrong bindings, unexplained relations and name drift are zero for the exhaustive corpus"], "evidence": ["check:freecad-isomorphic-provenance", "check:freecad-composite-history-elementmap", "check:freecad-tsn-stage-correlation", "check:freecad-fcstd-roundtrip", "check:freecad-exact-history-elementmap-gate"], "exactBlockedBy": ["The locked 30-case corpus now passes 30/30 native property mutations, final-shape propagation and 219/219 geometry-stage restores, but 11 PartDesign cases evolve 28 recovered naming-stage digests; the five-stage chain still has 3/5 generic Shape proxies, no rotate mutation family, 5 semantic topology restore drifts, and only 4/361 ordered operation pairs classified"] }
{ "id": "EX-TSN-04", "title": "Close topology naming across all feature edits and FCStd round-trips", "priority": "P0", "status": "in_progress", "dependencies": ["EX-TSN-02", "EX-TSN-03"], "deliverables": ["Cross-feature mutation matrix", "ElementMap2 history migration", "Long-chain LinkSub stability report"], "acceptance": ["Wrong bindings, unexplained relations and name drift are zero for the exhaustive corpus"], "evidence": ["check:freecad-isomorphic-provenance", "check:freecad-composite-history-elementmap", "check:freecad-tsn-stage-correlation", "check:freecad-fcstd-roundtrip", "check:freecad-exact-history-elementmap-gate", "check:freecad-recovered-naming-classification"], "exactBlockedBy": ["The locked 30-case corpus passes 30/30 native property mutations, final-shape propagation and 219/219 geometry-stage restores; partdesign-plain has classified 2 recovered naming stages as allowed native evolution from two deterministic runs with stable resave, while 10 PartDesign cases/26 recovered naming stages remain unclassified; the five-stage chain still has 3/5 generic Shape proxies, no rotate mutation family, 5 semantic topology restore drifts, and only 4/361 ordered operation pairs classified"] }
]
},
{
@@ -76,7 +76,7 @@
"tasks": [
{ "id": "EX-UI-01", "title": "Complete automated FreeCAD GUI oracle for all application states", "priority": "P0", "status": "in_progress", "dependencies": ["EX-ORA-01"], "deliverables": ["Menu, toolbar, dialog and focus manifest", "Selection-state fixtures", "Desktop screenshots and accessibility trees"], "acceptance": ["Empty, document, selection, task, failure and recovery states have no unknown command state"] , "evidence": ["check:freecad-gui-commands", "check:chrome-app-e2e"], "exactBlockedBy": ["Current GUI oracle does not cover every dynamic state"] },
{ "id": "EX-UI-02", "title": "Match application shell, workbench switching, tree, docks and MDI lifecycle", "priority": "P0", "status": "in_progress", "dependencies": ["EX-UI-01", "EX-DOC-04"], "deliverables": ["Dynamic workbench contributions", "Tree and dock lifecycle", "Multi-document tab behavior"], "acceptance": ["Visible, enabled, ordering and focus states match the GUI oracle"] , "evidence": ["check:chrome-app-e2e"], "exactBlockedBy": ["Dynamic command states and full MDI behavior are incomplete"] },
{ "id": "EX-UI-03", "title": "Match Task panels, property editors, dialogs and validation lifecycle", "priority": "P0", "status": "in_progress", "dependencies": ["EX-UI-02", "EX-DOC-01"], "deliverables": ["Generic native editor mapping", "Apply/OK/Cancel transactions", "Keyboard and focus traps"], "acceptance": ["All dialogs and tasks match validation, preview, commit, cancel and recovery behavior"] , "evidence": ["check:chrome-app-e2e", "check:chrome-partdesign-lifecycle"], "exactBlockedBy": ["Only representative tasks and dialogs have complete browser replay"] },
{ "id": "EX-UI-03", "title": "Match Task panels, property editors, dialogs and validation lifecycle", "priority": "P0", "status": "in_progress", "dependencies": ["EX-UI-02", "EX-DOC-01"], "deliverables": ["Generic native editor mapping", "Apply/OK/Cancel transactions", "Keyboard and focus traps"], "acceptance": ["All dialogs and tasks match validation, preview, commit, cancel and recovery behavior"] , "evidence": ["check:freecad-gui-workflow", "check:chrome-app-e2e", "check:chrome-partdesign-lifecycle"], "exactBlockedBy": ["PartDesign Pad has a complete native success, disabled, failure, cancel and recovery Task lifecycle, but the remaining Task panels, property editors and dialogs do not yet have exhaustive native and browser replay"] },
{ "id": "EX-UI-04", "title": "Match 3D selection, context, shortcuts, status, report and long tasks", "priority": "P0", "status": "in_progress", "dependencies": ["EX-UI-02", "EX-TSN-04"], "deliverables": ["Object/subshape selection oracle", "Shortcut conflict map", "Progress, cancel and diagnostics lifecycle"], "acceptance": ["Pointer, keyboard, preselection and report states match for every command context"] , "evidence": ["check:chrome-topology", "check:chrome-app-e2e"], "exactBlockedBy": ["All workbench context combinations and browser-reserved shortcuts are not closed"] },
{ "id": "EX-UI-05", "title": "Replay complete workflows for every workbench and close visual/accessibility diffs", "priority": "P1", "status": "pending", "dependencies": ["EX-UI-03", "EX-UI-04", "EX-PLATFORM-01"], "deliverables": ["Per-workbench recordings", "Desktop and mobile visual baselines", "Accessibility and localization matrix"], "acceptance": ["No unregistered UI difference, overlap, unnamed control or blocked core workflow"] , "evidence": ["check:chrome-app-e2e", "check:chrome-qa08"], "exactBlockedBy": ["Per-workbench exact workflow replay is incomplete"] }
]

View File

@@ -390,7 +390,7 @@
{
"path": "sw.js",
"bytes": 1390,
"sha256": "98ec667a774f8370ed654e457b86d4943b2f1eb89e228bd4e7e652b31a51b982"
"sha256": "8b0477955514535bb11cef3733135b67422cc8c085b4331c3b9cb1a1adc9d852"
},
{
"path": "vendor/camotics/camotics-sweep.wasm",
@@ -412,7 +412,7 @@
"status": "signed",
"algorithm": "Ed25519",
"keyId": "bitbybit-local-release-2026",
"payloadSha256": "5172b35d4d194483ac1934a1653259b1c7f5bb78c5ab4a68f7e5033cf2a22b86",
"signature": "FoaYFhliT+/njf8hXECKj5IoCeHPj1MnpbTBIw/FxzYpfyrgQ01nuPkwslM4okUk3d97cncjaXnEkDA7lZBtDw=="
"payloadSha256": "ad5f25aeb7e1382b3172d9919357a9831ac198841e54304a6789665cd21cda31",
"signature": "c8xaS8E7BOThrQKOK9WlCLpMazuqlBZ6eMKbCTt+tzeC0b4TyEEaiVvjelUtFDjo5PK0fVYPeYISrS2895LTDA=="
}
}

View File

@@ -1911,3 +1911,53 @@ Node 与真实 Chrome 的 19-operation 生产矩阵均重跑通过。17 类样
`ORA-GUI-WF-001` 随后在隔离 Xvfb 的锁定 FreeCAD 1.1.1 桌面进程真实执行。原生命令从已选矩形 Sketch 打开 Pad TaskTaskView 的实际接受按钮为 `Done`;接受并清理 edit 状态后得到 `PartDesign::Pad`Body Group 为 `Sketch,Pad`、Tip=Pad、Profile=Sketch、Length=10实体为 6 面/12 边/8 点、体积 120、状态 Up-to-dateUndoCount=1。报告只声明 successremainingStates 仍为 disabled/failure/cancel/recovery。队列前移为 112 completed、1 in_progress、374 pending唯一下一项是 `ORA-GUI-WF-002`
本轮完整 `./npmw run verify` 以退出码 0 完成生产构建、79 个签名发布文件、34 模块 release closure 和 Ed25519 验签通过;能力矩阵仍准确保持 0 exact、26 compatible、5 proxy、3 development。
## 244. 2026-08-14 ORA-GUI-WF-002 Pad 无文档禁用工作流
Pad GUI oracle 现以两个独立的隔离 Xvfb/FreeCAD 1.1.1 进程采集 success 与 disabled 状态。disabled 进程激活 Part Design 工作台但不创建文档,原生 `PartDesign_Pad``isActive()` 与 QAction `isEnabled()` 均为 false对 disabled QAction 调用 `trigger()` 不产生 triggered 信号,直接调用 `Gui.runCommand("PartDesign_Pad")` 也被命令管理器的 `isActive()` 门阻止。
两条激活入口前后均采集应用文档集合、活动 App/Gui 文档、选择、`Gui.Control.activeDialog()`、编辑对象、全局 active transaction 与逐文档 Undo/Redo 作用域。实际结果始终为 0 个文档、无活动 Task/edit、无全局事务且无文档事务作用域聚焦 checker 对四个前后快照 fail-closed。`ORA-GUI-WF-002` 已关闭,队列前移为 113 completed、1 in_progress、373 pending唯一下一项是 `ORA-GUI-WF-003` failure 工作流。
本轮聚焦原生 probe/check、工作流计划、PartDesign Chrome 生命周期、Facade 测试和 `check:execution-plan` 均通过;完整 `./npmw run verify` 也以退出码 0 完成包含生产构建、79 个签名发布文件、34 模块 release closure 与 Ed25519 验签。
## 245. 2026-08-14 ORA-GUI-WF-003 Pad 原生失败工作流
Pad GUI oracle 新增隔离的 failure 进程:活动 Body 中选择由两条线段构成的 open-wire Sketch原生命令创建 `PartDesign::Pad` 无效预览并保持 Pad Task 打开;点击接受后捕获 FreeCAD `Input error` 对话框及精确诊断 `Wire is not closed.`。诊断关闭后 Task、活动事务和无效预览仍保留可继续修正参数。
失败态采集完成后仅执行 Task reject 做资源清理;临时 Pad 被删除活动事务归零Body Tip、原始对象全集、Sketch 两条几何、Shape 有效性和文档基线均恢复。聚焦 checker 对诊断、失败保留态与清理回滚逐项 fail-closed。`ORA-GUI-WF-003` 已关闭,队列前移为 114 completed、1 in_progress、372 pending唯一下一项是 `ORA-GUI-WF-004` cancel 工作流。
## 246. 2026-08-14 ORA-GUI-WF-004 Pad 原生取消工作流
cancel 进程从闭合 4x3 Sketch 打开 Pad Task将 Length 改为 17.5 mm并确认预览为体积 210 的有效单实体、Body Tip 临时指向 Pad 且 `Make Pad` 事务处于活动状态。Task reject 后临时 Pad 被删除Body Tip 与对象全集恢复,活动 Body 保持为 BodySketch 的四条几何、Shape 有效性和可见性均保持,事务与 Undo/Redo 作用域归零。
原生结果纠正了预写配方中的两项假设Sketch 选择在命令启动时被消费为空,取消后不恢复;焦点由 3D View 进入 `lengthEdit`,取消后落在 TaskView也不返回原 3D View。配方和 checker 已按实际 FreeCAD 1.1.1 三阶段轨迹 fail-closed。`ORA-GUI-WF-004` 已关闭,队列前移为 115 completed、1 in_progress、371 pending唯一下一项是 `ORA-GUI-WF-005` recovery 工作流。
## 247. 2026-08-14 ORA-GUI-WF-005 Pad 原生恢复工作流
recovery 进程从两段 open-wire Sketch 启动 Pad首次接受捕获 `Input error: Wire is not closed.`,同时保持无效 Pad、`Make Pad` 事务和 Task 可修正状态。同一 Task 内补齐另外两条边,并通过 FreeCAD `Gui::PrefQuantitySpinBox``rawValue` 属性把 Length 输入为 12 mm预览和提交均为 6 面/12 边/8 点、体积 144 的有效单实体Body Tip=Pad、Profile=Sketch、状态 Up-to-date。
提交后的 Undo 恢复到原始两段 open-wire、删除 Pad 并产生 RedoRedo 恢复四段闭合 Sketch 与 12 mm Pad。随后 FCStd 保存、关闭重开、再次保存、再次关闭重开均保持 Length=12、体积 144、Body Group=`Sketch,Pad` 和 Tip=Pad。原生探针还纠正了预写控件类型假设`lengthEdit` 不能按标准 `QDoubleSpinBox` 驱动,必须使用自定义控件的 double 型 `rawValue` Qt 属性。`ORA-GUI-WF-005` 已关闭。
## 248. 2026-08-14 ORA-GUI-WF-006 Pad 工作流门禁收口
工作流计划现为 7/7 completedchecker 同时锁定 success、disabled、failure、cancel、recovery 五态和三阶段持久化精确键集合,不再依赖 JSON 对象序列化顺序。`EX-ORA-01``EX-UI-03` blocker 已同步为“首个 Pad 命令族完整、其他命令族仍开放”,系统 exact 继续为 false模块能力仍为 0 exact、26 compatible、5 proxy、3 development。
活动队列前移为 117 completed、1 in_progress、369 pending唯一下一项为 `TSN-DRIFT-000`,即只解释 `partdesign-plain` 的恢复命名演进。原生 probe/check、工作流计划、Facade 199/199、Chrome PartDesign 生命周期、执行计划和活动队列检查均通过;完整 `./npmw run verify` 以退出码 0 完成生产构建、79 个签名发布文件、34 模块 release closure 和 Ed25519 验签通过。
## 249. 2026-08-15 TSN-DRIFT-000 Plain Pad 恢复命名分类
新增恢复命名分类证据链,以现有锁定的 FreeCAD 1.1.1 composite oracle 为参考,再独立运行一次相同原生 30-case harness。`partdesign-plain` 编辑 `Pad10.Length` 的轨迹为 `7 -> 7.75 -> 7`Body10 与 Pad10 两个受影响阶段的几何摘要在恢复后与初值完全一致,但 relation/semantic-name 摘要从首次创建态演进到新的恢复态。参考运行和独立复跑的 before/edited/restored 三相摘要逐项一致,恢复态又与保存前 nominal 一致,并在 save/reopen/resave 三态保持不变。
该差异因此分类为 `allowed_evolution`,不是 Web 实现缺陷。机器报告 `config/freecad-recovered-naming-classification.json` 固定原生 harness 的 bytes/SHA-256、两次运行证据、漂移 ordinal 和唯一分类;`check:freecad-recovered-naming-classification` 对当前 composite/resave 报告重新推导全部摘要并 fail-closed。分类同时进入 TSN stage correlation blocker 与 `check:execution-plan`
活动队列前移为 118 completed、1 in_progress、368 pending唯一下一项为 `TSN-DRIFT-001`,即只解释 `partdesign-midplane` 的恢复命名演进。`EX-TSN-04` 仍为 in_progress11 个 PartDesign case/28 个恢复命名阶段中只关闭 1 个 case/2 个阶段,剩余 10/26系统 exact 与模块等级均不提升。
## 250. 2026-08-15 TSN-DRIFT-001 Midplane Pad 恢复命名分类
`partdesign-midplane` 在锁定 FreeCAD 1.1.1 中编辑 `Pad11.Length`,属性轨迹为 `8 -> 8.75 -> 8`。Body11 与 Pad11 两个漂移阶段的 geometry digest 恢复后与初值完全一致relation/semantic-name digest 则从首次创建态演进到新的恢复态。锁定参考报告与第二次独立原生复跑的 before/edited/restored 三相摘要逐项一致;恢复态与保存前 nominal 一致,并在 save/reopen/resave 三态保持不变。
该差异因此分类为 `allowed_evolution``implementationDefect=false`。连续决策表现包含 `TSN-DRIFT-000..001` 两个且无未知分类;聚焦 checker 对两个 case 的属性恢复、几何恢复、命名演进、nominal 对齐、持久化稳定和独立复跑一致性 fail-closed。
活动队列已机器前移为 119 completed、1 in_progress、367 pending唯一下一项为 `TSN-DRIFT-002`,即只解释 `partdesign-reverse` 的恢复命名演进。`EX-TSN-04` 仍为 in_progress11 个 PartDesign case/28 个恢复命名阶段中已关闭 2 个 case/4 个阶段,剩余 9/24系统 exact 与模块等级均不提升。
本轮聚焦分类、30-case composite、TSN correlation、exact history、执行计划与队列门禁均通过完整 `./npmw run verify` 以退出码 0 完成,包含 199 项 Facade 测试、生产构建、79 个签名发布文件、34 模块 release closure 和 Ed25519 验签。验证后 `git diff --check`、310 个 JSON 解析与发布产物一致性复核再次通过。

View File

@@ -1,6 +1,6 @@
# Web FreeCAD 当前工作主线
更新时间2026-08-14
更新时间2026-08-15
## 1. 唯一目标
@@ -10,7 +10,7 @@
## 2. 当前真实状态
截至 2026-08-14
截至 2026-08-15
- 系统级 FreeCAD exact claim 为 `false`
- 34 个模块中 `0 exact``26 compatible``5 proxy``3 development`
@@ -18,7 +18,7 @@
- exact 计划共 52 项:`5 completed``36 in_progress``11 pending`
- 已完成的 exact 项只有 `EX-TSN-00..03``EX-PD-02`:原生命名证据边界、逐阶段历史、生产私有命名、同构来源处理,以及 Attachment/XLink 生命周期。
- exact 发布任务 `EX-REL-01` 仍为 `pending`34 个模块都不得提升为 exact。
- 当前工作树包含 TSN 逐阶段相关性、Property 状态/codec、CAM/LinuxCNC 验证资产、oracle 清册分类和首个 Pad GUI 工作流增量;聚焦门禁、完整 `verify`、发布产物验签和最终差异校验均已通过。
- 当前工作树包含 TSN 逐阶段相关性、Property 状态/codec、CAM/LinuxCNC 验证资产、oracle 清册分类和首个 Pad GUI 五态工作流;聚焦门禁、完整 `verify`、发布产物验签和最终差异校验均已通过。
状态冲突时按以下顺序取真值:
@@ -73,11 +73,11 @@ EX-DOC-01 -> EX-DOC-03 ---------^
### P0-A锁定完整原生 oracle
对应 `EX-ORA-01/02`。当前隔离配置下有 1111 个工作台启动命令、19 个 source-only 命令和 2 个上下文延迟命令1111 个启动命令已拆为 100 个独立原生分片,全部完成 no-document/document/selection 三状态采样。首个 `PartDesign_Pad` 命令族已完成选择和原生成功 Task 工作流,下一项只采集无活动文档时的 disabled 状态;failure、cancel、recovery 与其他命令族仍保持开放。没有原生基线的功能不进入 exact 实现判断。
对应 `EX-ORA-01/02`。当前隔离配置下有 1111 个工作台启动命令、19 个 source-only 命令和 2 个上下文延迟命令1111 个启动命令已拆为 100 个独立原生分片,全部完成 no-document/document/selection 三状态采样。首个 `PartDesign_Pad` 命令族的 success、disabledfailure、cancel、recovery 五态工作流和 blocker 更新已全部完成;其他命令族及运行时对象/Property 的未知项仍保持开放。没有原生基线的功能不进入 exact 实现判断。
### P0-B关闭跨特征拓扑命名
对应 `EX-KER-01``EX-TSN-04`。锁定 30-case corpus 已按 `9 Boolean + 11 PartDesign + 6 复合 Boolean + 4 dress-up` 逐案例执行原生属性编辑30/30 都传播到最终 Shape219/219 阶段几何恢复657 条 before/edited/restored 阶段记录完成 fail-closed 对账恢复状态再保存、重开、二次保存后的错误绑定、未解释关系和名称漂移均为零。FreeCAD 重算使 11 个 PartDesign 案例的 28 个 mutation 恢复阶段产生原生命名摘要演进。生产 Worker 五阶段链另有 5/5 几何编辑恢复,但顺序链只有 2/5 原生 builder3/5 为通用 Shape 代理rotate 缺少原生 mutation family2/5 恢复用例的 5 个下游语义拓扑摘要发生变化19 类生产操作的 361 个有序组合仅分类回放 4 个,因此门禁继续 fail-closed。
对应 `EX-KER-01``EX-TSN-04`。锁定 30-case corpus 已按 `9 Boolean + 11 PartDesign + 6 复合 Boolean + 4 dress-up` 逐案例执行原生属性编辑30/30 都传播到最终 Shape219/219 阶段几何恢复657 条 before/edited/restored 阶段记录完成 fail-closed 对账恢复状态再保存、重开、二次保存后的错误绑定、未解释关系和名称漂移均为零。FreeCAD 重算使 11 个 PartDesign 案例的 28 个 mutation 恢复阶段产生原生命名摘要演进;其中 `partdesign-plain``partdesign-midplane` 的 Body/Pad 共 4 个阶段已用第二次独立原生复跑分类为允许演进,恢复几何与初值一致且演进命名在 save/reopen/resave 后稳定,剩余 9 个案例/24 个阶段待解释。生产 Worker 五阶段链另有 5/5 几何编辑恢复,但顺序链只有 2/5 原生 builder3/5 为通用 Shape 代理rotate 缺少原生 mutation family2/5 恢复用例的 5 个下游语义拓扑摘要发生变化19 类生产操作的 361 个有序组合仅分类回放 4 个,因此门禁继续 fail-closed。
### P0-C关闭 App 文档语义
@@ -101,7 +101,7 @@ P1/P2 的 GUI 全量对标、其他工作台、代理模块和跨浏览器发布
| --- | --- | --- | --- | --- |
| M0 | 收口当前工作树 | `completed` | 将 TSN、Property、CAM/LinuxCNC 三组已有增量分别核对证据和边界,不再混入新功能 | 聚焦门禁、完整 `verify``git diff --check`、全部 JSON 解析通过;计划与报告无陈旧哈希 |
| M1 | `EX-ORA-01` 运行时清册闭合 | `in_progress` | 对 352 个注册对象、1111 个启动 GUI command、19 个 source-only command、2 个 context-only command 和 GUI 工作流状态生成差异账本 | 每个候选均为 runtime-backed 或有机器可读排除原因;成功/禁用/失败/取消/恢复工作流未知项为零 |
| M2 | `EX-TSN-04A` 漂移判定 | `ready-after-M0` | 逐项解释 11 个 PartDesign case 的 28 个恢复命名摘要演进,以及五阶段链的 5 个恢复拓扑变化 | 每个差异都有 FreeCAD 复跑证据并被判定为稳定语义、允许演进或实现缺陷;未知项为零,缺陷有回归测试 |
| M2 | `EX-TSN-04A` 漂移判定 | `in_progress` | 逐项解释 11 个 PartDesign case 的 28 个恢复命名摘要演进,以及五阶段链的 5 个恢复拓扑变化 | 每个差异都有 FreeCAD 复跑证据并被判定为稳定语义、允许演进或实现缺陷;未知项为零,缺陷有回归测试 |
| M3 | `EX-TSN-04B` 转换矩阵闭合 | `after-M2` | 将 19×19 的 361 个有序操作对按原生输入/输出类型分类;原生允许的组合使用真实 builder 回放,原生拒绝的组合保存失败 oracle | 361/361 已分类;可执行组合的 history/naming/mutation/resave 通过;不可执行组合的原生错误可复现;不再用通用 Shape 代理冒充 feature builder |
| M4 | `EX-DOC-01` Property 批次 | `after-M1` | 先做高复用 App 类型与链接/列表类型,再处理 Material、File、PythonObject 等带安全或工作台边界的类型 | 每批均具备原生值/状态/editor oracle、Facade codec、FCStd reopen/resave、Undo/Redo 和 Chrome 证据opaque 数量只按机器报告下降 |
| M5 | `EX-DOC-02/03` 文档闭环 | `after-M3+M4` | observer 顺序、事务 abort、局部重算失败、Extension、FeaturePython、copy/clone/delete/relink | 原生与浏览器事件序列一致;失败不污染最后有效状态;资源归零;随后再推进 `EX-DOC-04` |
@@ -113,18 +113,18 @@ M3 不要求把 FreeCAD 本身拒绝的组合强行实现为顺序特征链。
### 6.1 微任务队列
机器真值为 `config/freecad-active-work-queue.json`,由 `generate:freecad-active-work-queue` 从原生报告生成,并由 `check:freecad-active-work-queue` fail-closed 校验。当前共 487 个微任务112 `completed`、1 `in_progress`、374 `pending``maxInProgress=1`,唯一下一任务是 `ORA-GUI-WF-002`
机器真值为 `config/freecad-active-work-queue.json`,由 `generate:freecad-active-work-queue` 从原生报告生成,并由 `check:freecad-active-work-queue` fail-closed 校验。当前共 487 个微任务119 `completed`、1 `in_progress`、367 `pending``maxInProgress=1`,唯一下一任务是 `TSN-DRIFT-002`
| 微里程碑 | 任务粒度 | 数量 | 状态 | 下一退出条件 |
| --- | --- | ---: | --- | --- |
| `ORA-GUI-BASELINE` | 1 个配置边界或 1 个 GUI 命令分片 | 106 | 106 completed | 已关闭100/100 分片、1111/1111 命令状态、2 个上下文延迟命令均有证据 |
| `ORA-GUI-WORKFLOWS` | 1 个命令族的 1 种工作流状态 | 7 | 2 completed / 1 in_progress / 4 pending | `PartDesign_Pad` 选择与成功 Task 已完成;当前只执行 `ORA-GUI-WF-002` disabled 状态 |
| `TSN-RECOVERY-DRIFT` | 1 个原生 case 或 1 个生产 mutation | 13 | 13 pending | GUI workflow 七步关闭后,从 `partdesign-plain` 单 case 开始 |
| `ORA-GUI-WORKFLOWS` | 1 个命令族的 1 种工作流状态 | 7 | 7 completed | `PartDesign_Pad` 五态原生工作流、聚焦断言和 exact blocker 更新已关闭 |
| `TSN-RECOVERY-DRIFT` | 1 个原生 case 或 1 个生产 mutation | 13 | 2 completed / 1 in_progress / 10 pending | `partdesign-plain` `partdesign-midplane` 已分类为允许演进;当前只解释 `partdesign-reverse` |
| `TSN-ORDERED-PAIRS` | 1 个有序操作对 | 361 | 4 completed / 357 pending | 每次只分类并回放一个 `from->to`,不按特征族批量关闭 |
`ORA-GUI-WORKFLOWS` 严格按七步执行:选定命令族、成功、禁用、失败、取消、恢复、更新 blocker/门禁。一步只新增一种原生状态证据,前一步未通过时不启动后一步。
`ORA-GUI-WORKFLOWS` 按七步关闭:选定命令族、成功、禁用、失败、取消、恢复、更新 blocker/门禁。一步只新增一种原生状态证据,前一步未通过时不启动后一步。
`TSN-RECOVERY-DRIFT` 的 13 个独立任务按以下顺序执行:`partdesign-plain``partdesign-midplane``partdesign-reverse``partdesign-taper``partdesign-twoside``partdesign-pocket``partdesign-pocket-through``partdesign-pocket-midplane``partdesign-pocket-twoside``partdesign-pocket-taper``partdesign-pocket-up-to-face`、生产 `cut`、生产 `rotate`。每个任务只允许一种结论:稳定语义、允许演进或实现缺陷;不得以几何恢复代替命名解释。
`TSN-RECOVERY-DRIFT` 的 13 个独立任务按以下顺序执行:`partdesign-plain`(已完成,允许演进)`partdesign-midplane`(已完成,允许演进)`partdesign-reverse`(当前)`partdesign-taper``partdesign-twoside``partdesign-pocket``partdesign-pocket-through``partdesign-pocket-midplane``partdesign-pocket-twoside``partdesign-pocket-taper``partdesign-pocket-up-to-face`、生产 `cut`、生产 `rotate`。每个任务只允许一种结论:稳定语义、允许演进或实现缺陷;不得以几何恢复代替命名解释。分类账本为 `config/freecad-recovered-naming-classification.json`,其原生 harness 哈希、两次运行一致性和持久化稳定性由 `check:freecad-recovered-naming-classification` 校验。
100 个 GUI 分片分别对应 `ORA-GUI-SHARD-000..099`,每片覆盖排序命令全集中的 11 或 12 个命令。每片有独立报告、SHA-256、起止索引和退出条件合并器要求 100 个区间连续、1111 个 ID 唯一且全集哈希一致。

View File

@@ -1,6 +1,6 @@
# FreeCAD Web 转换执行总计划
更新时间2026-08-14
更新时间2026-08-15
## 1. 目标、口径与真值
@@ -48,7 +48,7 @@
- Oracle352 个注册对象、348 个可实例化对象、4 个不可实例化对象、1111 个启动 GUI command、19 个 source-only command 和 2 个 context-only command 已分类;命令族的成功、禁用、失败、取消、恢复流程仍未穷尽。
- Property85 种运行时类型、5510 条记录中22 种/4304 条为通用原生 codec5 种/683 条为专用 codec58 种/523 条仍为 opaque proxy。
- Property status15 类已观察状态均已进入类型化 Facade 行为;`LockDynamic``PartialTrigger` 已有原生事务证据,但完整 observer 与局部重算序列未关闭。
- TSN30/30 原生 mutation、219/219 几何阶段恢复通过;11 个 PartDesign case28 个恢复命名摘要演进仍需逐 case 解释。
- TSN30/30 原生 mutation、219/219 几何阶段恢复通过;`partdesign-plain``partdesign-midplane`4 个恢复命名阶段已通过第二次独立原生复跑分类为允许演进,剩余 9 个 PartDesign case/24 个阶段待解释。
- 生产命名链:五阶段 5/5 几何恢复,但仍有 3/5 通用 Shape 代理、缺 rotate mutation family、5 个下游语义拓扑恢复漂移。
- 有序操作19×19 共 361 对,仅 4 对已有分类回放357 对待原生接受/拒绝判定。
- FCStd支持的原生 codec 与未知资源保留已有证据,但所有对象/属性/Extension/Shape/ElementMap2/StringHasher 的 FreeCAD-Web-FreeCAD 双向变异未穷尽。
@@ -110,23 +110,23 @@ FCStd / STEP / IGES / mesh / CAM / engineering resources
- `ORA-GUI-MERGE-000`:合并分片并校验 ID 全集、连续区间和 SHA-256。
- `ORA-GUI-CONTEXT-000..001`:分类 `Import_ReadBREP``NaviCubeDraggableCmd` 的延迟注册边界。
### 5.2 ORA GUI 工作流7 项,执行中
### 5.2 ORA GUI 工作流7 项,已完成
- `ORA-GUI-WF-000`:选择首个命令族。已选 `PartDesign_Pad`,映射主任务 `EX-UI-03`,关联 `EX-ORA-01/EX-PD-01`
- `ORA-GUI-WF-001`Pad 原生成功工作流和聚焦断言已完成Task 打开/接受、Body Tip、体积、拓扑和 Undo 证据通过。
- `ORA-GUI-WF-002`只采集无活动文档时的 disabled 工作流,执行中
- `ORA-GUI-WF-003`只采集无效 open-wire profile 的 failure 工作流。
- `ORA-GUI-WF-004`只采集预览后 Cancel 的回滚、选择焦点工作流
- `ORA-GUI-WF-005`只采集失败/取消后修复、成功、Undo/Redo、保存重开的恢复工作流
- `ORA-GUI-WF-006`更新 exact blocker、聚焦门禁和下一命令族选择规则
- `ORA-GUI-WF-002`:无活动文档时的 disabled 工作流已完成QAction 与命令管理器入口均被阻止文档、Task 和事务保持为空
- `ORA-GUI-WF-003`open-wire profile 的 failure 工作流已完成原生诊断、Task 可修正状态和失败清理回滚均已锁定
- `ORA-GUI-WF-004`有效预览后 Cancel 工作流已完成;回滚、活动 Body 及原生选择/焦点轨迹均已锁定
- `ORA-GUI-WF-005`失败后在同一 Task 内修复 profile、提交、Undo/Redo、保存重开和二次保存,已完成
- `ORA-GUI-WF-006`exact blocker、聚焦门禁和后继 TSN 队列已更新,已完成
### 5.3 TSN 恢复漂移13 项,待 ORA 工作流
### 5.3 TSN 恢复漂移13 项,执行中
按以下顺序一次解释一个 case
1. `partdesign-plain`
2. `partdesign-midplane`
3. `partdesign-reverse`
1. `partdesign-plain`,已完成,分类为允许演进
2. `partdesign-midplane`,已完成,分类为允许演进
3. `partdesign-reverse`,当前唯一执行项
4. `partdesign-taper`
5. `partdesign-twoside`
6. `partdesign-pocket`
@@ -140,6 +140,8 @@ FCStd / STEP / IGES / mesh / CAM / engineering resources
每项必须得到且只能得到一种结论FreeCAD 稳定语义、允许的命名演进、实现缺陷。实现缺陷必须增加回归测试;未知不能作为结论。
每个 case 再拆成六个不可合并的最小检查点:先锁定属性、初值/编辑值/恢复值和漂移 ordinal再执行第二次独立 FreeCAD 1.1.1 探针;然后逐阶段比较 geometry/relation/semantic-name 三摘要;接着验证恢复阶段与保存前 nominal 一致;再验证 save/reopen/resave 三态不继续漂移;最后写入分类账本、运行聚焦 checker 并只推进一个队列项。任一检查点失败时,当前任务保持 `in_progress`,不得领取下一 case。
### 5.4 TSN 有序操作对361 项4 完成/357 待办
操作全集为 Fuse、Cut、Common、Rotate、Pad、Pocket、Loft、Pipe、Revolution、Groove、Fillet、Chamfer、Hole、Draft、Thickness、LinearPattern、PolarPattern、Mirrored、MultiTransform。
@@ -173,7 +175,7 @@ FCStd / STEP / IGES / mesh / CAM / engineering resources
### M01 `EX-ORA-01` GUI 工作流闭合
状态:执行 `ORA-GUI-WF-002`
状态:首个 Pad 命令族 7/7 已完成;`EX-ORA-01` 因其他命令族和运行时对象/Property 未穷尽而继续 `in_progress`
先完成 Pad 七步,再按风险选择下一族。每个后续命令族仍拆成 success/disabled/failure/cancel/recovery/update 六个状态任务。选择顺序:核心文档命令 -> Sketcher 创建/编辑 -> PartDesign 参数 Task -> Part builder -> FCStd import/export -> 其他工作台。
@@ -181,9 +183,9 @@ FCStd / STEP / IGES / mesh / CAM / engineering resources
### M02 `EX-TSN-04A` 恢复漂移闭合
状态:等待 M01
状态:`TSN-DRIFT-000..001` 已完成,执行 `TSN-DRIFT-002`
- M02.1-M02.1111 个 PartDesign case逐 case 解释 28 个命名摘要演进。
- M02.1-M02.1111 个 PartDesign case逐 case 解释 28 个命名摘要演进;当前完成 2/11 case、4/28 阶段,剩余 9/11 case、24/28 阶段
- M02.12:生产 Cut 恢复拓扑漂移。
- M02.13:生产 Rotate 恢复拓扑漂移。
- M02.14:若发现实现缺陷,只修改一个 operation/case 并补回归。
@@ -370,4 +372,18 @@ M00
`ORA-GUI-WF-001` 也已完成:锁定 FreeCAD 1.1.1 的 Pad 成功 GUI 工作流通过原生命令打开 Task点击 `Done` 后清理 edit 状态,得到 `PartDesign::Pad`、Body Tip=Pad、体积 120、6 面/12 边/8 点和 UndoCount=1。
当前唯一下一任务为 `ORA-GUI-WF-002`:只采集无活动文档时 `PartDesign_Pad`disabled 状态并证明调用被阻止且不创建文档、Task 或事务。failure/cancel/recovery 必须留给各自后继任务。
`ORA-GUI-WF-002` 已完成:在独立原生进程中,`PartDesign_Pad``isActive()` 与 QAction 均为 falseQAction trigger 和 `Gui.runCommand()` 两条入口都未创建文档、Task 或事务。
`ORA-GUI-WF-003` 已完成open-wire profile 打开无效 Pad 预览,接受时原生报告 `Wire is not closed.`Task 保持打开;失败清理 reject 后临时 Pad 与事务被回滚,原有 Body/Sketch 状态保持不变。
`ORA-GUI-WF-004` 已完成17.5 mm Pad 预览为 210 体积的有效单实体reject 后临时 Pad、Tip 变化与事务全部回滚,活动 Body 保持不变。原生选择从 Sketch 被消费为空并保持为空;焦点从 3D View 进入 `lengthEdit`,取消后落到 TaskView而非恢复原 3D View。
`ORA-GUI-WF-005` 已完成open-wire Pad 首次接受产生 `Wire is not closed.`Task 保持可修正;同一 Task 内补齐闭合矩形并通过 `lengthEdit.rawValue` 输入 12 mm 后,预览和提交均为 6 面/12 边/8 点、体积 144 的有效单实体。Undo 回到两段 open-wire 且 Pad 不存在Redo 恢复 12 mm Pad保存、关闭重开、再次保存和再次重开均保持相同语义。
`ORA-GUI-WF-006` 已完成工作流计划、exact blocker、聚焦 checker 和活动队列同步Pad 命令族 7/7 关闭,但没有据此提升 `EX-ORA-01``EX-UI-03` 或系统 exact。
`TSN-DRIFT-000` 已完成:`partdesign-plain``Length 7 -> 7.75 -> 7` 在 Body 和 Pad 两个阶段恢复相同几何,但从首次创建命名演进为新的稳定历史。锁定报告与第二次独立 FreeCAD 1.1.1 复跑的逐阶段摘要一致,演进值又在 save/reopen/resave 中保持,因此分类为 `allowed_evolution`,不是 Web 实现缺陷。
`TSN-DRIFT-001` 已完成:`partdesign-midplane``Length 8 -> 8.75 -> 8` 在 Body11 和 Pad11 两个阶段恢复相同几何,关系与语义命名摘要演进为新的稳定历史。锁定报告与第二次独立 FreeCAD 1.1.1 复跑的逐阶段摘要一致,演进值在 save/reopen/resave 中保持,因此同样分类为 `allowed_evolution`,不是 Web 实现缺陷。
当前唯一下一任务为 `TSN-DRIFT-002`:只解释 `partdesign-reverse` 的恢复命名阶段。退出条件仍是第二次原生复跑一致、恢复几何一致、持久化稳定,并且在稳定语义、允许演进、实现缺陷三者中得到唯一机器可读结论。

View File

@@ -14,7 +14,7 @@
"check:baseline": "node scripts/check-baseline.mjs",
"check:freecad-source": "node scripts/verify-freecad-source.mjs",
"check:bitbybit-history": "node scripts/check-bitbybit-history-surface.mjs",
"check:execution-plan": "node scripts/check-freecad-execution-plan.mjs && node scripts/check-freecad-web-exact-parity-plan.mjs && node scripts/check-freecad-web-exact-promotion.mjs && node scripts/check-freecad-oracle-coverage.mjs && node scripts/check-freecad-gui-workflow-plan.mjs && node scripts/check-freecad-gui-workflow-oracle.mjs && node scripts/check-freecad-golden-coverage.mjs && node scripts/check-freecad-core-parameter-mutation-oracle.mjs && node scripts/generate-freecad-parameter-mutation-report.mjs --check && node scripts/check-freecad-tsn-stage-evidence.mjs && node scripts/check-freecad-tsn-stage-correlation.mjs && node scripts/generate-freecad-active-work-queue.mjs --check",
"check:execution-plan": "node scripts/check-freecad-execution-plan.mjs && node scripts/check-freecad-web-exact-parity-plan.mjs && node scripts/check-freecad-web-exact-promotion.mjs && node scripts/check-freecad-oracle-coverage.mjs && node scripts/check-freecad-gui-workflow-plan.mjs && node scripts/check-freecad-gui-workflow-oracle.mjs && node scripts/check-freecad-golden-coverage.mjs && node scripts/check-freecad-core-parameter-mutation-oracle.mjs && node scripts/generate-freecad-parameter-mutation-report.mjs --check && node scripts/check-freecad-tsn-stage-evidence.mjs && node scripts/check-freecad-tsn-stage-correlation.mjs && node scripts/check-freecad-recovered-naming-classification.mjs && node scripts/generate-freecad-active-work-queue.mjs --check",
"generate:freecad-active-work-queue": "node scripts/generate-freecad-active-work-queue.mjs",
"check:freecad-active-work-queue": "node scripts/generate-freecad-active-work-queue.mjs --check",
"check:freecad-web-exact-parity-plan": "node scripts/check-freecad-web-exact-parity-plan.mjs",
@@ -52,6 +52,8 @@
"check:freecad-fcstd-roundtrip": "node scripts/check-freecad-fcstd-roundtrip-verification.mjs",
"probe:freecad-composite-history-elementmap": "node scripts/run-freecad-composite-history-elementmap-oracle.mjs",
"check:freecad-composite-history-elementmap": "npx tsx scripts/check-freecad-composite-history-elementmap-oracle.mjs",
"probe:freecad-recovered-naming-classification": "node scripts/run-freecad-recovered-naming-classification.mjs",
"check:freecad-recovered-naming-classification": "node scripts/check-freecad-recovered-naming-classification.mjs",
"probe:freecad-tsn-stage-correlation": "node scripts/run-freecad-tsn-stage-correlation-oracle.mjs",
"check:freecad-tsn-stage-correlation": "node scripts/check-freecad-tsn-stage-correlation.mjs",
"check:freecad-exact-history-elementmap-gate": "npx tsx scripts/check-freecad-exact-history-elementmap-gate.mjs",

View File

@@ -19,13 +19,74 @@ if (success.after?.acceptClicked !== true || success.taskPanel?.closed?.activeDi
const pad = success.after?.pad
if (success.after?.bodyTip !== 'Pad' || success.after.bodyGroup?.join(',') !== 'Sketch,Pad' || pad?.typeId !== 'PartDesign::Pad' || pad.length !== 10 || pad.shapeValid !== true || pad.solidCount !== 1 || pad.faceCount !== 6 || pad.edgeCount !== 12 || pad.vertexCount !== 8 || pad.volume !== 120 || pad.profile !== 'Sketch' || pad.state?.join(',') !== 'Up-to-date') fail('native Pad result semantics are incomplete.')
if ((!Number.isInteger(success.after.undoCount) || success.after.undoCount < 1) && (!Array.isArray(success.after.undoNames) || success.after.undoNames.length < 1)) fail('native Pad transaction evidence is missing.')
if (JSON.stringify(report.remainingStates) !== JSON.stringify(['disabled', 'failure', 'cancel', 'recovery']) || report.exactPromotionReady !== false) fail('remaining workflow boundary is incorrect.')
const disabled = report.workflows?.disabled
if (disabled?.workflowId !== report.family.id || disabled.commandId !== report.family.primaryCommand || disabled.state !== 'disabled' || disabled.success !== true) fail('disabled workflow identity is invalid.')
if (disabled.before?.activeWorkbench !== 'PartDesignWorkbench' || disabled.before.commandRegistered !== true || disabled.before.commandActive !== false || disabled.before.actionCount < 1 || disabled.before.actionEnabled !== false) fail('native disabled command state is incomplete.')
const checkpoints = [
disabled.before?.sideEffects,
disabled.activationAttempts?.actionTrigger?.after,
disabled.activationAttempts?.runCommand?.after,
disabled.after?.sideEffects,
]
for (const checkpoint of checkpoints) {
if (!checkpoint || checkpoint.documentCount !== 0 || checkpoint.documents?.length !== 0 || checkpoint.activeDocument !== '' || checkpoint.activeGuiDocument !== false || checkpoint.selection?.length !== 0) fail('disabled Pad created or activated document state.')
if (checkpoint.taskPanel?.activeDialog !== false || checkpoint.taskPanel.inEdit !== '') fail('disabled Pad created a Task or edit state.')
if (checkpoint.transaction?.active !== false || checkpoint.transaction.name !== '' || checkpoint.transaction.id !== null || checkpoint.transaction.documents?.length !== 0) fail('disabled Pad created a transaction scope.')
}
if (disabled.activationAttempts.actionTrigger.requested !== true || disabled.activationAttempts.actionTrigger.triggeredSignalCount !== 0 || disabled.activationAttempts.actionTrigger.sideEffectsUnchanged !== true) fail('disabled QAction activation was not blocked cleanly.')
if (disabled.activationAttempts.runCommand.requested !== true || disabled.activationAttempts.runCommand.sideEffectsUnchanged !== true || disabled.after.commandActive !== false || disabled.after.actionEnabled !== false) fail('disabled command-manager activation was not blocked cleanly.')
const failure = report.workflows?.failure
if (failure?.workflowId !== report.family.id || failure.commandId !== report.family.primaryCommand || failure.state !== 'failure' || failure.success !== true) fail('failure workflow identity is invalid.')
if (failure.before?.activeWorkbench !== 'PartDesignWorkbench' || failure.before.commandRegistered !== true || failure.before.commandActive !== true || failure.before.actionCount < 1 || typeof failure.before.actionEnabled !== 'boolean' || failure.before.baseline?.selection?.join(',') !== 'Sketch' || failure.before.baseline.sketchGeometryCount !== 2 || failure.before.baseline.bodyTip !== '') fail('native failure preconditions are incomplete.')
if (failure.taskPanel?.opened?.activeDialog !== true || failure.taskPanel.opened.inEdit !== 'Pad' || failure.taskPanel.preview?.exists !== true || failure.taskPanel.preview.typeId !== 'PartDesign::Pad' || failure.taskPanel.preview.profile !== 'Sketch' || failure.taskPanel.preview.shapeNull !== true || !failure.taskPanel.preview.state?.includes('Invalid') || !failure.taskPanel.preview.statusString) fail('invalid Pad preview evidence is incomplete.')
const warning = failure.diagnostic?.warnings?.[0]
if (failure.diagnostic?.requested !== true || failure.diagnostic.warnings.length !== 1 || warning.title !== 'Input error' || warning.text !== failure.taskPanel.preview.statusString) fail('native Pad failure diagnostic is incomplete.')
if (failure.taskPanel.afterAttempt?.taskPanel?.activeDialog !== true || failure.taskPanel.afterAttempt.taskPanel.inEdit !== 'Pad' || failure.taskPanel.afterAttempt.padExists !== true || failure.taskPanel.afterAttempt.padShapeNull !== true || failure.taskPanel.afterAttempt.transaction?.active !== true) fail('failed Pad Task was not retained for correction.')
if (failure.after?.rejectRequested !== true || failure.after.taskPanel?.activeDialog !== false || failure.after.taskPanel.inEdit !== '' || failure.after.padExists !== false || JSON.stringify(failure.after.objectNames) !== JSON.stringify(failure.before.baseline.objectNames) || failure.after.bodyTip !== failure.before.baseline.bodyTip || failure.after.sketchGeometryCount !== failure.before.baseline.sketchGeometryCount || failure.after.sketchShapeValid !== failure.before.baseline.sketchShapeValid || failure.after.transaction?.active !== false) fail('failed Pad cleanup did not restore the last valid document state.')
const cancel = report.workflows?.cancel
if (cancel?.workflowId !== report.family.id || cancel.commandId !== report.family.primaryCommand || cancel.state !== 'cancel' || cancel.success !== true || cancel.nativeSelectionOutcome !== 'selected-profile-consumed' || cancel.nativeFocusOutcome !== 'task-view-after-cancel') fail('cancel workflow identity is invalid.')
if (cancel.before?.activeWorkbench !== 'PartDesignWorkbench' || cancel.before.commandRegistered !== true || cancel.before.commandActive !== true || cancel.before.actionCount < 1 || typeof cancel.before.actionEnabled !== 'boolean' || cancel.before.baseline?.selection?.join(',') !== 'Sketch' || cancel.before.baseline.activeBody !== 'Body' || cancel.before.baseline.focus?.className !== 'Gui::View3DInventorViewer' || cancel.before.baseline.focus.objectName !== '') fail('native cancel preconditions are incomplete.')
if (cancel.taskPanel?.opened?.activeDialog !== true || cancel.taskPanel.opened.inEdit !== 'Pad' || cancel.taskPanel.preview?.exists !== true || cancel.taskPanel.preview.typeId !== 'PartDesign::Pad' || cancel.taskPanel.preview.length !== 17.5 || cancel.taskPanel.preview.shapeValid !== true || cancel.taskPanel.preview.solidCount !== 1 || cancel.taskPanel.preview.volume !== 210 || cancel.taskPanel.preview.bodyTip !== 'Pad' || cancel.taskPanel.preview.activeBody !== 'Body' || cancel.taskPanel.preview.selection?.length !== 0 || cancel.taskPanel.preview.transaction?.active !== true) fail('native Pad cancel preview evidence is incomplete.')
if (cancel.after?.rejectRequested !== true || cancel.after.taskPanel?.activeDialog !== false || cancel.after.taskPanel.inEdit !== '' || cancel.after.padExists !== false || JSON.stringify(cancel.after.objectNames) !== JSON.stringify(cancel.before.baseline.objectNames) || cancel.after.bodyTip !== cancel.before.baseline.bodyTip || cancel.after.activeBody !== cancel.before.baseline.activeBody || cancel.after.selection?.length !== 0 || cancel.after.focus?.className !== 'Gui::TaskView::TaskView' || cancel.after.focus.objectName !== 'Tasks' || cancel.after.sketchGeometryCount !== cancel.before.baseline.sketchGeometryCount || cancel.after.sketchShapeValid !== cancel.before.baseline.sketchShapeValid || cancel.after.sketchVisible !== true || cancel.after.transaction?.active !== false) fail('cancelled Pad did not restore the native document and active Body or expose the native TaskView focus outcome.')
const recovery = report.workflows?.recovery
if (recovery?.workflowId !== report.family.id || recovery.commandId !== report.family.primaryCommand || recovery.state !== 'recovery' || recovery.success !== true) fail('recovery workflow identity is invalid.')
if (recovery.before?.sketchGeometryCount !== 2 || recovery.before.padExists !== false || recovery.before.bodyTip !== '' || recovery.before.transaction?.active !== false) fail('native recovery open-wire baseline is incomplete.')
const recoveryWarning = recovery.initialFailure?.diagnostic?.warnings?.[0]
if (recovery.initialFailure?.taskPanel?.activeDialog !== true || recovery.initialFailure.taskPanel.inEdit !== 'Pad' || recovery.initialFailure.padShapeNull !== true || !recovery.initialFailure.padState?.includes('Invalid') || recoveryWarning?.title !== 'Input error' || recoveryWarning.text !== 'Wire is not closed.' || recovery.initialFailure.afterAttempt?.activeDialog !== true || recovery.initialFailure.afterAttempt.inEdit !== 'Pad') fail('native recovery failure phase is incomplete.')
if (recovery.repairedPreview?.taskPanel?.activeDialog !== true || recovery.repairedPreview.taskPanel.inEdit !== 'Pad' || recovery.repairedPreview.lengthInputUpdated !== true || recovery.repairedPreview.sketchGeometryCount !== 4 || recovery.repairedPreview.padLength !== 12 || recovery.repairedPreview.padShapeValid !== true || recovery.repairedPreview.padSolidCount !== 1 || recovery.repairedPreview.padVolume !== 144 || recovery.repairedPreview.transaction?.active !== true) fail('native repaired Pad preview is incomplete.')
const assertRecoveredPad = (value, phase) => {
if (!value || value.padExists !== true || value.bodyTip !== 'Pad' || value.bodyGroup?.join(',') !== 'Sketch,Pad' || value.sketchGeometryCount !== 4 || value.padTypeId !== 'PartDesign::Pad' || value.padLength !== 12 || value.padProfile !== 'Sketch' || value.padShapeValid !== true || value.padSolidCount !== 1 || value.padFaceCount !== 6 || value.padEdgeCount !== 12 || value.padVertexCount !== 8 || value.padVolume !== 144 || value.padState?.join(',') !== 'Up-to-date' || value.transaction?.active !== false) fail(`native recovered Pad ${phase} state is incomplete.`)
}
if (recovery.committed?.acceptRequested !== true || recovery.committed.taskPanel?.activeDialog !== false || recovery.committed.taskPanel.inEdit !== '') fail('repaired Pad Task did not commit cleanly.')
assertRecoveredPad(recovery.committed, 'commit')
if (recovery.undo?.padExists !== false || recovery.undo.sketchGeometryCount !== 2 || recovery.undo.bodyTip !== '' || recovery.undo.redoCount < 1 || recovery.undo.transaction?.active !== false) fail('native recovery Undo did not restore the failed-workflow baseline.')
assertRecoveredPad(recovery.redo, 'redo')
if (recovery.redo.undoCount < 1) fail('native recovery Redo did not restore an undoable Pad.')
const persistencePhases = {
saved: 'saved',
reopened: 'reopened',
resavedReopened: 'resaved-reopened',
}
if (Object.keys(recovery.persistence ?? {}).sort().join(',') !== Object.keys(persistencePhases).sort().join(',')) fail('native recovery persistence phases are incomplete.')
for (const [phase, value] of Object.entries(recovery.persistence)) {
assertRecoveredPad(value, phase)
if (value.phase !== persistencePhases[phase]) fail(`native recovery ${phase} phase identity is incomplete.`)
if (!value.fileName?.endsWith('/partdesign-pad-recovery.FCStd')) fail(`native recovery ${phase} file provenance is incomplete.`)
}
if (JSON.stringify(report.remainingStates) !== JSON.stringify([]) || report.exactPromotionReady !== false) fail('remaining workflow boundary is incorrect.')
console.log(JSON.stringify({
status: 'freecad-gui-workflow-pass',
family: report.family.id,
command: report.family.primaryCommand,
capturedStates: Object.keys(report.workflows),
remainingStates: report.remainingStates,
disabledNoSideEffects: true,
failureDiagnostic: warning.text,
failureRolledBack: true,
cancelPreviewVolume: cancel.taskPanel.preview.volume,
cancelRolledBack: true,
recoveryVolume: recovery.persistence.resavedReopened.padVolume,
recoveryResaved: true,
bodyTip: success.after.bodyTip,
volume: pad.volume,
undoNames: success.after.undoNames,

View File

@@ -41,9 +41,11 @@ for (const stateName of stateNames) {
const expectedTaskIds = Array.from({ length: 7 }, (_, index) => `ORA-GUI-WF-${String(index).padStart(3, '0')}`)
if (!Array.isArray(workflow.progress) || workflow.progress.map((entry) => entry.taskId).join(',') !== expectedTaskIds.join(',')) fail('workflow progress must contain ordered ORA-GUI-WF-000 through 006 tasks.')
const statuses = workflow.progress.map((entry) => entry.status)
if (statuses.filter((status) => status === 'in_progress').length !== 1 || statuses.some((status) => !['completed', 'in_progress', 'pending'].includes(status))) fail('workflow progress must have exactly one in-progress task.')
const inProgressCount = statuses.filter((status) => status === 'in_progress').length
if (inProgressCount > 1 || statuses.some((status) => !['completed', 'in_progress', 'pending'].includes(status))) fail('workflow progress has invalid statuses.')
const inProgressIndex = statuses.indexOf('in_progress')
if (statuses.slice(0, inProgressIndex).some((status) => status !== 'completed') || statuses.slice(inProgressIndex + 1).some((status) => status !== 'pending')) fail('workflow progress is not a contiguous serial queue.')
if (inProgressCount === 0 && statuses.some((status) => status !== 'completed')) fail('workflow progress without an in-progress task must be fully completed.')
if (inProgressCount === 1 && (statuses.slice(0, inProgressIndex).some((status) => status !== 'completed') || statuses.slice(inProgressIndex + 1).some((status) => status !== 'pending'))) fail('workflow progress is not a contiguous serial queue.')
for (const [index, entry] of workflow.progress.entries()) {
if (!Array.isArray(entry.evidence)) fail(`${entry.taskId} evidence must be an array.`)
if (index < inProgressIndex && entry.evidence.length === 0) fail(`${entry.taskId} is completed without evidence.`)
@@ -55,6 +57,6 @@ console.log(JSON.stringify({
primaryCommand: family.primaryCommand,
primaryExactTask: family.primaryExactTask,
completed: statuses.filter((status) => status === 'completed').length,
inProgress: workflow.progress[inProgressIndex].taskId,
inProgress: inProgressIndex >= 0 ? workflow.progress[inProgressIndex].taskId : null,
pending: statuses.filter((status) => status === 'pending').length,
}, null, 2))

View File

@@ -0,0 +1,49 @@
import { createHash } from 'node:crypto'
import { readFile, stat } from 'node:fs/promises'
import { resolve } from 'node:path'
import { buildRecoveredNamingSnapshot, recoveredNamingDecisions, same, validateRecoveredNamingSnapshot } from './freecad-recovered-naming-classification.mjs'
const root = resolve(new URL('..', import.meta.url).pathname)
const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse)
const fail = (message) => { throw new Error(`FreeCAD recovered naming classification: ${message}`) }
const [report, oracle, resave] = await Promise.all([
load('config/freecad-recovered-naming-classification.json'),
load('config/freecad-composite-history-elementmap-oracle.json'),
load('config/freecad-composite-history-resave-verification.json'),
])
if (report.schemaVersion !== 1 || report.baseline?.freecadVersion !== '1.1.1' || report.baseline.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.baseline.oracleId !== oracle.baselineId) fail('baseline is invalid.')
const harnessPath = resolve(root, report.nativeProbe?.path ?? '')
const [harnessContent, harnessBytes] = await Promise.all([readFile(harnessPath), stat(harnessPath).then(({ size }) => size)])
if (report.nativeProbe.path !== 'scripts/freecad-composite-history-elementmap-oracle.py' || report.nativeProbe.bytes !== harnessBytes || report.nativeProbe.sha256 !== createHash('sha256').update(harnessContent).digest('hex') || report.nativeProbe.independentRuns < 2) fail('native replay provenance is missing or stale.')
if (!Array.isArray(report.classifications) || report.classifications.length !== recoveredNamingDecisions.length) fail('classification report does not contain the complete serial decision prefix.')
let classifiedStages = 0
for (const [index, entry] of report.classifications.entries()) {
const decision = recoveredNamingDecisions[index]
if (!same({ taskId: entry.taskId, caseId: entry.caseId, classification: entry.classification, reasonCode: entry.reasonCode, rationale: entry.rationale }, decision) || entry.replayStable !== true || entry.implementationDefect !== (decision.classification === 'implementation_defect')) fail(`${decision.taskId} classification contract is invalid.`)
const fixture = oracle.cases.find(({ id }) => id === entry.caseId)
const resaveFixture = resave.cases.find(({ id }) => id === entry.caseId)
if (!fixture || !resaveFixture) fail(`${entry.caseId} is absent from the locked native corpus.`)
const expected = buildRecoveredNamingSnapshot(fixture, resaveFixture)
validateRecoveredNamingSnapshot(expected)
validateRecoveredNamingSnapshot(entry.reference)
validateRecoveredNamingSnapshot(entry.replay)
if (!same(entry.reference, expected) || !same(entry.replay, expected)) fail(`${entry.caseId} classification evidence is stale or the independent replay differs.`)
classifiedStages += expected.driftOrdinals.length
}
const countClassification = (classification) => recoveredNamingDecisions.filter((entry) => entry.classification === classification).length
const expectedSummary = { classifiedCases: recoveredNamingDecisions.length, classifiedStages, stableSemantics: countClassification('stable_semantics'), allowedEvolution: countClassification('allowed_evolution'), implementationDefects: countClassification('implementation_defect'), unknown: 0 }
if (!same(report.summary, expectedSummary)) fail('classification summary is inconsistent.')
const driftCases = oracle.cases.filter((candidate) => candidate.mutation.metrics.namingRestorationDriftStages > 0)
const totalDriftStages = driftCases.reduce((sum, candidate) => sum + candidate.mutation.metrics.namingRestorationDriftStages, 0)
console.log(JSON.stringify({
status: 'freecad-recovered-naming-classification-pass',
completedTasks: report.classifications.map(({ taskId }) => taskId),
cases: report.classifications.map(({ caseId, classification }) => ({ caseId, classification })),
independentRuns: report.nativeProbe.independentRuns,
classifiedCases: report.summary.classifiedCases,
classifiedStages: report.summary.classifiedStages,
remainingCases: driftCases.length - report.summary.classifiedCases,
remainingStages: totalDriftStages - report.summary.classifiedStages,
}, null, 2))

View File

@@ -6,13 +6,14 @@ import { validateCompositeMutationEvidence } from './freecad-composite-mutation-
const root = resolve(new URL('..', import.meta.url).pathname)
const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse)
const fail = (message) => { throw new Error(`FreeCAD TSN stage correlation: ${message}`) }
const [desktop, production, productionRegistry, composite, resave, mutations] = await Promise.all([
const [desktop, production, productionRegistry, composite, resave, mutations, recoveredNaming] = await Promise.all([
load('config/freecad-tsn-stage-correlation-oracle.json'),
load('config/freecad-isomorphic-provenance-verification.json'),
load('config/chrome-freecad-naming-production-verification.json'),
load('config/freecad-composite-history-elementmap-oracle.json'),
load('config/freecad-composite-history-resave-verification.json'),
load('config/freecad-parameter-mutation-report.json'),
load('config/freecad-recovered-naming-classification.json'),
])
const operations = ['cut', 'rotate', 'fillet', 'mirrored', 'linear-pattern']
if (desktop.schemaVersion !== 1 || desktop.baselineId !== 'freecad-1.1.1-tsn-stage-correlation' || desktop.freecadVersion !== '1.1.1' || desktop.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || desktop.status !== 'pass') fail('desktop oracle is not locked to FreeCAD 1.1.1.')
@@ -33,6 +34,18 @@ if (desktop.nativeDesktopResaveCovered !== true || desktop.summary?.mutationCase
for (const [index, mutation] of desktop.mutations.entries()) if (mutation.operation !== operations[index] || mutation.status !== 'pass' || mutation.parameterChanged !== true || mutation.downstreamShapeChanged !== true || mutation.restoredExactly !== true) fail(`desktop ${operations[index]} mutation did not edit, recompute and restore the chain.`)
if (composite.summary?.cases !== 30 || composite.summary?.passed !== 30 || resave.summary?.nativeDesktopResaveCases !== 30 || resave.summary?.roundtripNameDrift !== 0 || resave.summary?.resaveNameDrift !== 0) fail('the locked 30-case native desktop resave corpus is incomplete.')
const compositeMutation = validateCompositeMutationEvidence(composite)
const driftCases = composite.cases.filter((fixture) => fixture.mutation.metrics.namingRestorationDriftStages > 0)
const driftById = new Map(driftCases.map((fixture) => [fixture.id, fixture]))
const recoveredNamingClassifications = recoveredNaming.classifications || []
if (recoveredNaming.baseline?.freecadVersion !== desktop.freecadVersion || recoveredNaming.baseline?.commit !== desktop.gitCommit || recoveredNamingClassifications.length !== new Set(recoveredNamingClassifications.map(({ caseId }) => caseId)).size) fail('recovered naming classifications have invalid baseline or duplicate cases.')
let classifiedNamingDriftStages = 0
for (const entry of recoveredNamingClassifications) {
const fixture = driftById.get(entry.caseId)
if (!fixture || !['stable_semantics', 'allowed_evolution', 'implementation_defect'].includes(entry.classification) || entry.replayStable !== true) fail(`recovered naming classification ${entry.caseId} is invalid.`)
classifiedNamingDriftStages += fixture.mutation.metrics.namingRestorationDriftStages
}
const unclassifiedNamingDriftCases = compositeMutation.mutationNamingRestoreDriftCases - recoveredNamingClassifications.length
const unclassifiedNamingDriftStages = compositeMutation.mutationNamingRestoreDriftStages - classifiedNamingDriftStages
const resaveById = new Map((resave.cases || []).map((fixture) => [fixture.id, fixture]))
let compositeCaseReconciliations = 0
if (resaveById.size !== compositeMutation.mutationCases || resave.cases?.length !== compositeMutation.mutationCases) fail('the 30-case resave report is not joined one-to-one with mutation evidence.')
@@ -70,7 +83,7 @@ const blockers = [
`Native desktop builder stages missing from the sequential chain: ${proxyStageOperations.join(', ')}`,
`Native parameter-mutation family missing for production operation: ${missingNativeMutationOperations.join(', ')}`,
`${desktop.summary.mutationRestoreTopologyDriftCases}/5 edit/restore cases changed ${desktop.summary.mutationRestoreTopologyDriftStages} downstream semantic topology digests without wrong binding`,
`${compositeMutation.mutationNamingRestoreDriftCases}/30 native case mutations changed ${compositeMutation.mutationNamingRestoreDriftStages} recovered naming-stage digests while all 219 geometry stages restored`,
`${unclassifiedNamingDriftCases}/${compositeMutation.mutationNamingRestoreDriftCases} native naming-drift cases and ${unclassifiedNamingDriftStages}/${compositeMutation.mutationNamingRestoreDriftStages} recovered naming stages remain unclassified; ${recoveredNamingClassifications.length} case is classified from an independent native replay`,
`Only ${coveredTransitions.length}/${orderedOperationPairs} ordered production operation pairs are classified and replayed; ${orderedOperationPairs - coveredTransitions.length} remain unclassified for type compatibility`,
]
console.log(JSON.stringify({
@@ -80,7 +93,7 @@ console.log(JSON.stringify({
desktopStages: desktop.summary.stages,
nativeBuilderStages: desktop.summary.nativeBuilderStages,
proxyStageOperations,
compositeMutations: { cases: compositeMutation.mutationCases, passed: compositeMutation.mutationPassed, caseReconciliations: compositeCaseReconciliations, categoryCases: compositeMutation.categoryCases, stageRecords: compositeMutation.mutationStageRecords, phaseStageRecords: compositeMutation.mutationPhaseStageRecords, finalPropagationFailures: compositeMutation.mutationFinalPropagationFailures, geometryRestoreFailures: compositeMutation.mutationStageRestoreFailures, namingRestoreDriftCases: compositeMutation.mutationNamingRestoreDriftCases, namingRestoreDriftStages: compositeMutation.mutationNamingRestoreDriftStages },
compositeMutations: { cases: compositeMutation.mutationCases, passed: compositeMutation.mutationPassed, caseReconciliations: compositeCaseReconciliations, categoryCases: compositeMutation.categoryCases, stageRecords: compositeMutation.mutationStageRecords, phaseStageRecords: compositeMutation.mutationPhaseStageRecords, finalPropagationFailures: compositeMutation.mutationFinalPropagationFailures, geometryRestoreFailures: compositeMutation.mutationStageRestoreFailures, namingRestoreDriftCases: compositeMutation.mutationNamingRestoreDriftCases, namingRestoreDriftStages: compositeMutation.mutationNamingRestoreDriftStages, classifiedNamingDriftCases: recoveredNamingClassifications.length, classifiedNamingDriftStages, unclassifiedNamingDriftCases, unclassifiedNamingDriftStages },
mutations: { cases: desktop.summary.mutationCases, passed: desktop.summary.mutationPassed, nativeCorrelated: nativeMutationCorrelation.filter(({ complete }) => complete).length, missingNativeMutationOperations, restoreTopologyDriftCases: desktop.summary.mutationRestoreTopologyDriftCases, restoreTopologyDriftStages: desktop.summary.mutationRestoreTopologyDriftStages },
transitions: { registeredOperations: registeredOperations.length, orderedOperationPairs, covered: coveredTransitions.length, unclassified: orderedOperationPairs - coveredTransitions.length, coveredTransitions },
nativeDesktopResaveCases: resave.summary.nativeDesktopResaveCases + 1,

View File

@@ -6,10 +6,11 @@ import FreeCAD as App
import FreeCADGui as Gui
import Part
import Sketcher
from PySide import QtWidgets
from PySide import QtCore, QtWidgets
MARKER = "FREECAD_GUI_WORKFLOW_RESULT="
REQUESTED_STATE = os.environ.get("FREECAD_GUI_WORKFLOW_STATE", "success")
def progress(phase):
@@ -165,7 +166,693 @@ def counter(document, name):
return None
def active_transaction_state():
transaction = App.getActiveTransaction()
return {
"active": transaction is not None,
"name": str(transaction[0]) if transaction else "",
"id": int(transaction[1]) if transaction else None,
"documents": [
{
"name": str(document.Name),
"hasPendingTransaction": bool(document.HasPendingTransaction),
"undoCount": counter(document, "UndoCount"),
"redoCount": counter(document, "RedoCount"),
"undoNames": available_transactions(document, "getAvailableUndoNames"),
"redoNames": available_transactions(document, "getAvailableRedoNames"),
}
for document in App.listDocuments().values()
],
}
def disabled_side_effect_state(phase):
app_document = App.ActiveDocument
gui_document = Gui.activeDocument()
return {
"phase": phase,
"documents": sorted(str(name) for name in App.listDocuments()),
"documentCount": len(App.listDocuments()),
"activeDocument": str(app_document.Name) if app_document else "",
"activeGuiDocument": gui_document is not None,
"selection": selection_names(),
"taskPanel": {
"activeDialog": bool(Gui.Control.activeDialog()),
"inEdit": active_edit_name(),
},
"transaction": active_transaction_state(),
}
def focus_state():
focus = QtWidgets.QApplication.focusWidget()
return None if focus is None else {
"className": str(focus.metaObject().className()),
"objectName": str(focus.objectName()),
}
def active_body_name():
active_body = Gui.activeView().getActiveObject("pdbody") if Gui.activeDocument() else None
return str(getattr(active_body, "Name", "")) if active_body else ""
def run_disabled_workflow():
progress("activate-workbench:start")
Gui.activateWorkbench("PartDesignWorkbench")
flush_gui()
progress("activate-workbench:done")
Gui.Selection.clearSelection()
flush_gui()
command = Gui.Command.get("PartDesign_Pad")
actions = list(command.getAction()) if command else []
before_side_effects = disabled_side_effect_state("before")
before = {
"activeWorkbench": str(Gui.activeWorkbench().name()),
"commandRegistered": command is not None,
"commandActive": bool(command.isActive()) if command else False,
"actionCount": len(actions),
"actionEnabled": bool(actions[0].isEnabled()) if actions else None,
"sideEffects": before_side_effects,
}
action_signal_count = [0]
if actions:
actions[0].triggered.connect(lambda _checked=False: action_signal_count.__setitem__(0, action_signal_count[0] + 1))
progress("disabled-action-trigger:start")
actions[0].trigger()
flush_gui()
progress("disabled-action-trigger:done")
after_action_trigger = disabled_side_effect_state("after-action-trigger")
progress("disabled-run-command:start")
Gui.runCommand("PartDesign_Pad", 0)
flush_gui()
progress("disabled-run-command:done")
after_run_command = disabled_side_effect_state("after-run-command")
after = {
"commandActive": bool(command.isActive()) if command else False,
"actionEnabled": bool(actions[0].isEnabled()) if actions else None,
"sideEffects": after_run_command,
}
def stable_side_effects(value):
return {key: item for key, item in value.items() if key != "phase"}
unchanged_after_action = stable_side_effects(after_action_trigger) == stable_side_effects(before_side_effects)
unchanged_after_run_command = stable_side_effects(after_run_command) == stable_side_effects(before_side_effects)
version = App.Version()
result = {
"schemaVersion": 1,
"baselineId": "freecad-1.1.1",
"freecadVersion": ".".join(str(value) for value in version[:3]),
"gitCommit": str(version[7]) if len(version) > 7 else "",
"workflowId": "partdesign-pad-task",
"commandId": "PartDesign_Pad",
"state": "disabled",
"before": before,
"activationAttempts": {
"actionTrigger": {
"requested": bool(actions),
"triggeredSignalCount": action_signal_count[0],
"sideEffectsUnchanged": unchanged_after_action,
"after": after_action_trigger,
},
"runCommand": {
"requested": True,
"sideEffectsUnchanged": unchanged_after_run_command,
"after": after_run_command,
},
},
"after": after,
"success": bool(
command is not None
and actions
and not before["commandActive"]
and before["actionEnabled"] is False
and action_signal_count[0] == 0
and unchanged_after_action
and unchanged_after_run_command
and after_run_command["documentCount"] == 0
and not after_run_command["taskPanel"]["activeDialog"]
and not after_run_command["taskPanel"]["inEdit"]
and not after_run_command["transaction"]["active"]
and not after_run_command["transaction"]["documents"]
),
}
progress("result:done")
emit(result, 0 if result["success"] else 2)
def accept_task_capturing_warning():
captured = []
polls = [0]
def capture_warning():
modal = QtWidgets.QApplication.activeModalWidget()
if isinstance(modal, QtWidgets.QMessageBox):
captured.append({
"title": str(modal.windowTitle()),
"text": str(modal.text()),
"informativeText": str(modal.informativeText()),
"detailedText": str(modal.detailedText()),
"standardButtons": int(modal.standardButtons()),
})
button = modal.button(QtWidgets.QMessageBox.Ok)
if button:
button.click()
else:
modal.accept()
return
polls[0] += 1
if polls[0] < 200:
QtCore.QTimer.singleShot(5, capture_warning)
task = Gui.Control.activeTaskDialog()
if task is None:
return {"requested": False, "warnings": captured, "polls": polls[0]}
QtCore.QTimer.singleShot(0, capture_warning)
task.accept()
flush_gui()
return {"requested": True, "warnings": captured, "polls": polls[0]}
def run_failure_workflow():
progress("activate-workbench:start")
Gui.activateWorkbench("PartDesignWorkbench")
flush_gui()
progress("activate-workbench:done")
document = App.newDocument("PadGuiFailure")
document.UndoMode = 1
body = document.addObject("PartDesign::Body", "Body")
sketch = body.newObject("Sketcher::SketchObject", "Sketch")
points = [App.Vector(0, 0, 0), App.Vector(4, 0, 0), App.Vector(4, 3, 0)]
for index in range(2):
sketch.addGeometry(Part.LineSegment(points[index], points[index + 1]), False)
document.recompute()
Gui.activeView().setActiveObject("pdbody", body)
Gui.Selection.clearSelection()
Gui.Selection.addSelection(document.Name, sketch.Name)
flush_gui()
baseline = {
"objectNames": [str(obj.Name) for obj in document.Objects],
"bodyTip": str(getattr(body.Tip, "Name", "")) if body.Tip else "",
"selection": selection_names(),
"sketchGeometryCount": int(sketch.GeometryCount),
"sketchShapeValid": bool(sketch.Shape.isValid()),
"sketchState": [str(value) for value in sketch.State],
"transaction": active_transaction_state(),
}
command = Gui.Command.get("PartDesign_Pad")
actions = list(command.getAction()) if command else []
before = {
"activeWorkbench": str(Gui.activeWorkbench().name()),
"commandRegistered": command is not None,
"commandActive": bool(command.isActive()) if command else False,
"actionCount": len(actions),
"actionEnabled": bool(actions[0].isEnabled()) if actions else None,
"baseline": baseline,
}
progress("failure-run-command:start")
Gui.runCommand("PartDesign_Pad", 0)
flush_gui()
progress("failure-run-command:done")
pad = document.getObject("Pad")
opened = task_panel_state("failure-opened")
preview = {
"exists": pad is not None,
"typeId": str(pad.TypeId) if pad else "",
"profile": profile_name(pad) if pad else "",
"shapeNull": bool(pad.Shape.isNull()) if pad else None,
"shapeValid": bool(pad.Shape.isValid()) if pad and not pad.Shape.isNull() else False if pad else None,
"state": [str(value) for value in pad.State] if pad else [],
"statusString": str(pad.getStatusString()) if pad else "",
"bodyTip": str(getattr(body.Tip, "Name", "")) if body.Tip else "",
"transaction": active_transaction_state(),
}
progress("failure-accept:start")
diagnostic = accept_task_capturing_warning()
progress("failure-accept:done")
pad = document.getObject("Pad")
after_attempt = {
"taskPanel": task_panel_state("failure-after-accept"),
"objectNames": [str(obj.Name) for obj in document.Objects],
"bodyTip": str(getattr(body.Tip, "Name", "")) if body.Tip else "",
"padExists": pad is not None,
"padShapeNull": bool(pad.Shape.isNull()) if pad else None,
"padState": [str(value) for value in pad.State] if pad else [],
"padStatusString": str(pad.getStatusString()) if pad else "",
"transaction": active_transaction_state(),
}
progress("failure-cleanup:start")
task = Gui.Control.activeTaskDialog()
cleanup_requested = task is not None
if task:
task.reject()
flush_gui()
document.recompute()
flush_gui()
progress("failure-cleanup:done")
after_cleanup = {
"rejectRequested": cleanup_requested,
"taskPanel": task_panel_state("failure-cleaned"),
"objectNames": [str(obj.Name) for obj in document.Objects],
"bodyTip": str(getattr(body.Tip, "Name", "")) if body.Tip else "",
"padExists": document.getObject("Pad") is not None,
"selection": selection_names(),
"sketchGeometryCount": int(sketch.GeometryCount),
"sketchShapeValid": bool(sketch.Shape.isValid()),
"sketchState": [str(value) for value in sketch.State],
"sketchVisible": bool(sketch.Visibility),
"transaction": active_transaction_state(),
}
warning = diagnostic["warnings"][0] if diagnostic["warnings"] else None
version = App.Version()
result = {
"schemaVersion": 1,
"baselineId": "freecad-1.1.1",
"freecadVersion": ".".join(str(value) for value in version[:3]),
"gitCommit": str(version[7]) if len(version) > 7 else "",
"workflowId": "partdesign-pad-task",
"commandId": "PartDesign_Pad",
"state": "failure",
"before": before,
"taskPanel": {"opened": opened, "preview": preview, "afterAttempt": after_attempt},
"diagnostic": diagnostic,
"after": after_cleanup,
"success": bool(
before["commandActive"]
and opened["activeDialog"]
and opened["inEdit"] == "Pad"
and preview["exists"]
and preview["shapeNull"]
and "Invalid" in preview["state"]
and diagnostic["requested"]
and warning
and warning["title"] == "Input error"
and warning["text"] == preview["statusString"]
and after_attempt["taskPanel"]["activeDialog"]
and after_attempt["taskPanel"]["inEdit"] == "Pad"
and after_attempt["padExists"]
and after_attempt["padShapeNull"]
and after_cleanup["rejectRequested"]
and not after_cleanup["taskPanel"]["activeDialog"]
and not after_cleanup["taskPanel"]["inEdit"]
and not after_cleanup["padExists"]
and after_cleanup["objectNames"] == baseline["objectNames"]
and after_cleanup["bodyTip"] == baseline["bodyTip"]
and after_cleanup["sketchGeometryCount"] == baseline["sketchGeometryCount"]
and after_cleanup["sketchShapeValid"] == baseline["sketchShapeValid"]
and not after_cleanup["transaction"]["active"]
),
}
progress("result:done")
App.closeDocument(document.Name)
emit(result, 0 if result["success"] else 2)
def run_cancel_workflow():
progress("activate-workbench:start")
Gui.activateWorkbench("PartDesignWorkbench")
flush_gui()
progress("activate-workbench:done")
document = App.newDocument("PadGuiCancel")
document.UndoMode = 1
body = document.addObject("PartDesign::Body", "Body")
sketch = body.newObject("Sketcher::SketchObject", "Sketch")
points = [
App.Vector(0, 0, 0),
App.Vector(4, 0, 0),
App.Vector(4, 3, 0),
App.Vector(0, 3, 0),
]
for index in range(4):
sketch.addGeometry(Part.LineSegment(points[index], points[(index + 1) % 4]), False)
document.recompute()
Gui.activeView().setActiveObject("pdbody", body)
Gui.Selection.clearSelection()
Gui.Selection.addSelection(document.Name, sketch.Name)
viewport_widgets = [
widget
for widget in Gui.getMainWindow().findChildren(QtWidgets.QWidget)
if str(widget.metaObject().className()) == "Gui::View3DInventorViewer"
]
if viewport_widgets:
viewport_widgets[0].setFocus(QtCore.Qt.OtherFocusReason)
flush_gui()
baseline = {
"objectNames": [str(obj.Name) for obj in document.Objects],
"bodyTip": str(getattr(body.Tip, "Name", "")) if body.Tip else "",
"activeBody": active_body_name(),
"selection": selection_names(),
"focus": focus_state(),
"sketchGeometryCount": int(sketch.GeometryCount),
"sketchShapeValid": bool(sketch.Shape.isValid()),
"transaction": active_transaction_state(),
}
command = Gui.Command.get("PartDesign_Pad")
actions = list(command.getAction()) if command else []
before = {
"activeWorkbench": str(Gui.activeWorkbench().name()),
"commandRegistered": command is not None,
"commandActive": bool(command.isActive()) if command else False,
"actionCount": len(actions),
"actionEnabled": bool(actions[0].isEnabled()) if actions else None,
"baseline": baseline,
}
progress("cancel-run-command:start")
Gui.runCommand("PartDesign_Pad", 0)
flush_gui()
progress("cancel-run-command:done")
opened = task_panel_state("cancel-opened")
pad = document.getObject("Pad")
if pad:
pad.Length = 17.5
document.recompute()
flush_gui()
preview = {
"exists": pad is not None,
"typeId": str(pad.TypeId) if pad else "",
"length": round(float(pad.Length.Value), 9) if pad else None,
"shapeValid": bool(pad.Shape.isValid()) if pad else None,
"solidCount": len(pad.Shape.Solids) if pad else None,
"volume": round(float(pad.Shape.Volume), 9) if pad else None,
"bodyTip": str(getattr(body.Tip, "Name", "")) if body.Tip else "",
"activeBody": active_body_name(),
"selection": selection_names(),
"focus": focus_state(),
"transaction": active_transaction_state(),
}
progress("cancel-reject:start")
task = Gui.Control.activeTaskDialog()
reject_requested = task is not None
if task:
task.reject()
flush_gui()
document.recompute()
flush_gui()
progress("cancel-reject:done")
after = {
"rejectRequested": reject_requested,
"taskPanel": task_panel_state("cancel-closed"),
"objectNames": [str(obj.Name) for obj in document.Objects],
"bodyTip": str(getattr(body.Tip, "Name", "")) if body.Tip else "",
"activeBody": active_body_name(),
"padExists": document.getObject("Pad") is not None,
"selection": selection_names(),
"focus": focus_state(),
"sketchGeometryCount": int(sketch.GeometryCount),
"sketchShapeValid": bool(sketch.Shape.isValid()),
"sketchVisible": bool(sketch.Visibility),
"transaction": active_transaction_state(),
}
version = App.Version()
result = {
"schemaVersion": 1,
"baselineId": "freecad-1.1.1",
"freecadVersion": ".".join(str(value) for value in version[:3]),
"gitCommit": str(version[7]) if len(version) > 7 else "",
"workflowId": "partdesign-pad-task",
"commandId": "PartDesign_Pad",
"state": "cancel",
"before": before,
"taskPanel": {"opened": opened, "preview": preview, "closed": after["taskPanel"]},
"after": after,
"nativeSelectionOutcome": "selected-profile-consumed",
"nativeFocusOutcome": "task-view-after-cancel",
"success": bool(
before["commandActive"]
and baseline["selection"] == ["Sketch"]
and baseline["activeBody"] == "Body"
and baseline["focus"] == {"className": "Gui::View3DInventorViewer", "objectName": ""}
and opened["activeDialog"]
and opened["inEdit"] == "Pad"
and preview["exists"]
and preview["typeId"] == "PartDesign::Pad"
and preview["length"] == 17.5
and preview["shapeValid"]
and preview["solidCount"] == 1
and abs(preview["volume"] - 210.0) < 1e-7
and preview["selection"] == []
and preview["activeBody"] == baseline["activeBody"]
and preview["transaction"]["active"]
and after["rejectRequested"]
and not after["taskPanel"]["activeDialog"]
and not after["taskPanel"]["inEdit"]
and not after["padExists"]
and after["objectNames"] == baseline["objectNames"]
and after["bodyTip"] == baseline["bodyTip"]
and after["activeBody"] == baseline["activeBody"]
and after["selection"] == []
and after["focus"] == {"className": "Gui::TaskView::TaskView", "objectName": "Tasks"}
and after["sketchGeometryCount"] == baseline["sketchGeometryCount"]
and after["sketchShapeValid"] == baseline["sketchShapeValid"]
and after["sketchVisible"]
and not after["transaction"]["active"]
),
}
progress("result:done")
App.closeDocument(document.Name)
emit(result, 0 if result["success"] else 2)
def recovery_document_state(document, phase):
body = document.getObject("Body")
sketch = document.getObject("Sketch")
pad = document.getObject("Pad")
return {
"phase": phase,
"objectNames": [str(obj.Name) for obj in document.Objects],
"bodyGroup": [str(obj.Name) for obj in body.Group] if body else [],
"bodyTip": str(getattr(body.Tip, "Name", "")) if body and body.Tip else "",
"sketchGeometryCount": int(sketch.GeometryCount) if sketch else None,
"sketchShapeValid": bool(sketch.Shape.isValid()) if sketch else None,
"padExists": pad is not None,
"padTypeId": str(pad.TypeId) if pad else "",
"padLength": round(float(pad.Length.Value), 9) if pad else None,
"padProfile": profile_name(pad) if pad else "",
"padShapeValid": bool(pad.Shape.isValid()) if pad and not pad.Shape.isNull() else False if pad else None,
"padSolidCount": len(pad.Shape.Solids) if pad and not pad.Shape.isNull() else 0 if pad else None,
"padFaceCount": len(pad.Shape.Faces) if pad and not pad.Shape.isNull() else 0 if pad else None,
"padEdgeCount": len(pad.Shape.Edges) if pad and not pad.Shape.isNull() else 0 if pad else None,
"padVertexCount": len(pad.Shape.Vertexes) if pad and not pad.Shape.isNull() else 0 if pad else None,
"padVolume": round(float(pad.Shape.Volume), 9) if pad and not pad.Shape.isNull() else 0 if pad else None,
"padState": [str(value) for value in pad.State] if pad else [],
"undoCount": counter(document, "UndoCount"),
"redoCount": counter(document, "RedoCount"),
"undoNames": available_transactions(document, "getAvailableUndoNames"),
"redoNames": available_transactions(document, "getAvailableRedoNames"),
"transaction": active_transaction_state(),
}
def run_recovery_workflow():
recovery_file = os.environ.get("FREECAD_GUI_WORKFLOW_RECOVERY_FILE", "")
if not recovery_file:
raise RuntimeError("FREECAD_GUI_WORKFLOW_RECOVERY_FILE is required")
progress("activate-workbench:start")
Gui.activateWorkbench("PartDesignWorkbench")
flush_gui()
progress("activate-workbench:done")
document = App.newDocument("PadGuiRecovery")
document.UndoMode = 1
body = document.addObject("PartDesign::Body", "Body")
sketch = body.newObject("Sketcher::SketchObject", "Sketch")
points = [
App.Vector(0, 0, 0),
App.Vector(4, 0, 0),
App.Vector(4, 3, 0),
App.Vector(0, 3, 0),
]
for index in range(2):
sketch.addGeometry(Part.LineSegment(points[index], points[index + 1]), False)
document.recompute()
Gui.activeView().setActiveObject("pdbody", body)
Gui.Selection.clearSelection()
Gui.Selection.addSelection(document.Name, sketch.Name)
flush_gui()
baseline = recovery_document_state(document, "open-wire-baseline")
progress("recovery-run-command:start")
Gui.runCommand("PartDesign_Pad", 0)
flush_gui()
progress("recovery-run-command:done")
initial_pad = document.getObject("Pad")
initial_failure = {
"taskPanel": task_panel_state("recovery-invalid-opened"),
"padShapeNull": bool(initial_pad.Shape.isNull()) if initial_pad else None,
"padState": [str(value) for value in initial_pad.State] if initial_pad else [],
"padStatusString": str(initial_pad.getStatusString()) if initial_pad else "",
"diagnostic": accept_task_capturing_warning(),
}
initial_failure["afterAttempt"] = task_panel_state("recovery-invalid-after-accept")
progress("recovery-repair:start")
sketch.addGeometry(Part.LineSegment(points[2], points[3]), False)
sketch.addGeometry(Part.LineSegment(points[3], points[0]), False)
document.recompute()
pad = document.getObject("Pad")
# FreeCAD exposes Gui::PrefQuantitySpinBox to PySide as a generic QWidget;
# rawValue is its double Qt property, while value expects Base::Quantity.
length_edit = Gui.getMainWindow().findChild(QtWidgets.QWidget, "lengthEdit")
if length_edit:
length_edit.setProperty("rawValue", 12.0)
flush_gui()
elif pad:
pad.Length = 12.0
document.recompute()
flush_gui()
repaired_preview = recovery_document_state(document, "repaired-preview")
repaired_preview["lengthInputUpdated"] = bool(
length_edit is not None and abs(float(length_edit.property("rawValue")) - 12.0) < 1e-9
)
repaired_preview["taskPanel"] = task_panel_state("recovery-repaired")
progress("recovery-repair:done")
progress("recovery-accept:start")
task = Gui.Control.activeTaskDialog()
accept_requested = task is not None
if task:
task.accept()
flush_gui()
document.recompute()
flush_gui()
progress("recovery-accept:done")
committed = recovery_document_state(document, "committed")
committed["acceptRequested"] = accept_requested
committed["taskPanel"] = task_panel_state("recovery-committed")
progress("recovery-undo:start")
document.undo()
document.recompute()
flush_gui()
undo = recovery_document_state(document, "undo")
progress("recovery-undo:done")
progress("recovery-redo:start")
document.redo()
document.recompute()
flush_gui()
redo = recovery_document_state(document, "redo")
progress("recovery-redo:done")
progress("recovery-save:start")
document.saveAs(recovery_file)
flush_gui()
saved = recovery_document_state(document, "saved")
saved["fileName"] = str(document.FileName)
document_name = document.Name
App.closeDocument(document_name)
reopened_document = App.openDocument(recovery_file)
reopened_document.recompute()
flush_gui()
reopened = recovery_document_state(reopened_document, "reopened")
reopened["fileName"] = str(reopened_document.FileName)
reopened_document.save()
App.closeDocument(reopened_document.Name)
resaved_document = App.openDocument(recovery_file)
resaved_document.recompute()
flush_gui()
resaved = recovery_document_state(resaved_document, "resaved-reopened")
resaved["fileName"] = str(resaved_document.FileName)
progress("recovery-save:done")
warning = initial_failure["diagnostic"]["warnings"][0] if initial_failure["diagnostic"]["warnings"] else None
def valid_pad_state(value):
return bool(
value["padExists"]
and value["bodyTip"] == "Pad"
and value["bodyGroup"] == ["Sketch", "Pad"]
and value["sketchGeometryCount"] == 4
and value["padTypeId"] == "PartDesign::Pad"
and value["padLength"] == 12
and value["padProfile"] == "Sketch"
and value["padShapeValid"]
and value["padSolidCount"] == 1
and value["padFaceCount"] == 6
and value["padEdgeCount"] == 12
and value["padVertexCount"] == 8
and abs(value["padVolume"] - 144.0) < 1e-7
and value["padState"] == ["Up-to-date"]
and not value["transaction"]["active"]
)
version = App.Version()
result = {
"schemaVersion": 1,
"baselineId": "freecad-1.1.1",
"freecadVersion": ".".join(str(value) for value in version[:3]),
"gitCommit": str(version[7]) if len(version) > 7 else "",
"workflowId": "partdesign-pad-task",
"commandId": "PartDesign_Pad",
"state": "recovery",
"before": baseline,
"initialFailure": initial_failure,
"repairedPreview": repaired_preview,
"committed": committed,
"undo": undo,
"redo": redo,
"persistence": {"saved": saved, "reopened": reopened, "resavedReopened": resaved},
"success": bool(
baseline["sketchGeometryCount"] == 2
and not baseline["padExists"]
and initial_failure["taskPanel"]["activeDialog"]
and initial_failure["taskPanel"]["inEdit"] == "Pad"
and initial_failure["padShapeNull"]
and "Invalid" in initial_failure["padState"]
and warning
and warning["title"] == "Input error"
and warning["text"] == "Wire is not closed."
and initial_failure["afterAttempt"]["activeDialog"]
and initial_failure["afterAttempt"]["inEdit"] == "Pad"
and repaired_preview["taskPanel"]["activeDialog"]
and repaired_preview["taskPanel"]["inEdit"] == "Pad"
and repaired_preview["lengthInputUpdated"]
and repaired_preview["padShapeValid"]
and repaired_preview["padVolume"] == 144
and committed["acceptRequested"]
and not committed["taskPanel"]["activeDialog"]
and not committed["taskPanel"]["inEdit"]
and valid_pad_state(committed)
and not undo["padExists"]
and undo["sketchGeometryCount"] == 2
and undo["bodyTip"] == ""
and undo["redoCount"] >= 1
and valid_pad_state(redo)
and redo["undoCount"] >= 1
and valid_pad_state(saved)
and saved["fileName"] == recovery_file
and valid_pad_state(reopened)
and reopened["fileName"] == recovery_file
and valid_pad_state(resaved)
and resaved["fileName"] == recovery_file
),
}
progress("result:done")
App.closeDocument(resaved_document.Name)
emit(result, 0 if result["success"] else 2)
try:
if REQUESTED_STATE == "disabled":
run_disabled_workflow()
if REQUESTED_STATE == "failure":
run_failure_workflow()
if REQUESTED_STATE == "cancel":
run_cancel_workflow()
if REQUESTED_STATE == "recovery":
run_recovery_workflow()
if REQUESTED_STATE != "success":
raise RuntimeError("Unsupported workflow state: " + REQUESTED_STATE)
progress("activate-workbench:start")
Gui.activateWorkbench("PartDesignWorkbench")
flush_gui()
@@ -264,7 +951,7 @@ try:
"gitCommit": str(version[7]) if len(version) > 7 else "",
"workflowId": "partdesign-pad-task",
"commandId": "PartDesign_Pad",
"state": "success",
"state": REQUESTED_STATE,
"before": before,
"taskPanel": {
"opened": opened,
@@ -294,7 +981,7 @@ except Exception as error:
"baselineId": "freecad-1.1.1",
"workflowId": "partdesign-pad-task",
"commandId": "PartDesign_Pad",
"state": "success",
"state": REQUESTED_STATE,
"success": False,
"error": {"type": type(error).__name__, "message": str(error)},
}, 2)

View File

@@ -0,0 +1,107 @@
const sha256 = (value) => typeof value === 'string' && /^[0-9a-f]{64}$/.test(value)
const canonical = (value) => Array.isArray(value)
? value.map(canonical)
: value && typeof value === 'object'
? Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, canonical(entry)]))
: value
export const recoveredNamingDecisions = [
{
taskId: 'TSN-DRIFT-000',
caseId: 'partdesign-plain',
classification: 'allowed_evolution',
reasonCode: 'native-history-stabilizes-after-edit-restore',
rationale: 'FreeCAD changes the Body and Pad naming history after the first Length edit/restore while restoring geometry exactly; the evolved names repeat deterministically and remain stable through save, reopen, and resave.',
},
{
taskId: 'TSN-DRIFT-001',
caseId: 'partdesign-midplane',
classification: 'allowed_evolution',
reasonCode: 'native-history-stabilizes-after-edit-restore',
rationale: 'FreeCAD applies the same deterministic history evolution to a Midplane Pad Length edit/restore: Body and Pad geometry returns exactly, and the evolved names remain stable through save, reopen, and resave.',
},
]
export const same = (left, right) => JSON.stringify(canonical(left)) === JSON.stringify(canonical(right))
const namingFingerprint = (record) => ({
relationDigest: record.relationDigest,
semanticNameDigest: record.semanticNameDigest,
})
const phaseFingerprint = (record) => ({
geometryDigest: record.shape.geometryDigest,
relationDigest: record.relationDigest,
semanticNameDigest: record.semanticNameDigest,
})
export const buildRecoveredNamingSnapshot = (fixture, resaveFixture) => {
const driftOrdinals = fixture?.mutation?.metrics?.namingRestorationDriftOrdinals
if (!Array.isArray(driftOrdinals) || driftOrdinals.length === 0) throw new Error(`${fixture?.id ?? 'unknown case'} has no recovered naming drift to classify.`)
if (!resaveFixture || !['initial', 'reopened', 'resaved'].every((phase) => Array.isArray(resaveFixture.stageCorrelations?.[phase]))) throw new Error(`${fixture.id} lacks native save/reopen/resave evidence.`)
const stages = driftOrdinals.map((ordinal) => {
const nominal = fixture.stages?.[ordinal]
const before = fixture.mutation.phases?.before?.[ordinal]
const edited = fixture.mutation.phases?.edited?.[ordinal]
const restored = fixture.mutation.phases?.restored?.[ordinal]
if (![nominal, before, edited, restored].every(Boolean)) throw new Error(`${fixture.id}/${ordinal} lacks a complete mutation phase.`)
const persistence = Object.fromEntries(['initial', 'reopened', 'resaved'].map((phase) => {
const record = resaveFixture.stageCorrelations[phase][ordinal]
if (!record) throw new Error(`${fixture.id}/${ordinal} lacks ${phase} persistence evidence.`)
return [phase, namingFingerprint(record)]
}))
return {
ordinal,
name: nominal.name,
typeId: nominal.typeId,
nominal: phaseFingerprint(nominal),
before: phaseFingerprint(before),
edited: phaseFingerprint(edited),
restored: phaseFingerprint(restored),
persistence,
}
})
return {
caseId: fixture.id,
contract: {
targetObject: fixture.mutation.contract.targetObject,
targetTypeId: fixture.mutation.contract.targetTypeId,
propertyPath: fixture.mutation.contract.propertyPath,
propertyType: fixture.mutation.contract.propertyType,
finalObject: fixture.mutation.contract.finalObject,
},
values: fixture.mutation.values,
driftOrdinals,
stages,
checks: {
propertyRestored: fixture.mutation.metrics.propertyRestored === true,
geometryRestored: stages.every(({ before, restored }) => before.geometryDigest === restored.geometryDigest),
namingEvolved: stages.every(({ before, restored }) => !same({ relationDigest: before.relationDigest, semanticNameDigest: before.semanticNameDigest }, { relationDigest: restored.relationDigest, semanticNameDigest: restored.semanticNameDigest })),
restoredMatchesNominal: stages.every(({ nominal, restored }) => same(nominal, restored)),
persistenceStable: resaveFixture.roundtripNameDrift === 0
&& resaveFixture.resaveNameDrift === 0
&& resaveFixture.nativeDesktopResaveCovered === true
&& stages.every(({ restored, persistence }) => ['initial', 'reopened', 'resaved'].every((phase) => same({ relationDigest: restored.relationDigest, semanticNameDigest: restored.semanticNameDigest }, persistence[phase]))),
},
}
}
export const validateRecoveredNamingSnapshot = (snapshot) => {
if (!snapshot || typeof snapshot.caseId !== 'string' || !snapshot.caseId) throw new Error('classification snapshot has no case identity.')
if (!Array.isArray(snapshot.driftOrdinals) || snapshot.driftOrdinals.length === 0 || snapshot.stages?.length !== snapshot.driftOrdinals.length) throw new Error(`${snapshot.caseId} has an invalid drift-stage set.`)
if (!same(snapshot.stages.map(({ ordinal }) => ordinal), snapshot.driftOrdinals)) throw new Error(`${snapshot.caseId} drift-stage identities are inconsistent.`)
for (const stage of snapshot.stages) {
if (!Number.isInteger(stage.ordinal) || !stage.name || !stage.typeId) throw new Error(`${snapshot.caseId} has an invalid stage identity.`)
for (const phase of ['nominal', 'before', 'edited', 'restored']) {
const value = stage[phase]
if (!sha256(value?.geometryDigest) || !sha256(value?.relationDigest) || !sha256(value?.semanticNameDigest)) throw new Error(`${snapshot.caseId}/${stage.name}/${phase} has an invalid fingerprint.`)
}
for (const phase of ['initial', 'reopened', 'resaved']) {
const value = stage.persistence?.[phase]
if (!sha256(value?.relationDigest) || !sha256(value?.semanticNameDigest)) throw new Error(`${snapshot.caseId}/${stage.name}/${phase} has invalid persistence evidence.`)
}
}
if (!Object.values(snapshot.checks ?? {}).every((value) => value === true)) throw new Error(`${snapshot.caseId} does not satisfy the allowed-evolution evidence contract.`)
}

View File

@@ -4,12 +4,13 @@ import { resolve } from 'node:path'
const root = resolve(new URL('..', import.meta.url).pathname)
const outputPath = resolve(root, 'config/freecad-active-work-queue.json')
const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse)
const [gui, workflowPlan, composite, correlation, production] = await Promise.all([
const [gui, workflowPlan, composite, correlation, production, recoveredNaming] = await Promise.all([
load('.cache/freecad/reference-desktop-gui-commands.json'),
load('config/freecad-gui-workflow-plan.json'),
load('config/freecad-composite-history-elementmap-oracle.json'),
load('config/freecad-tsn-stage-correlation-oracle.json'),
load('config/chrome-freecad-naming-production-verification.json'),
load('config/freecad-recovered-naming-classification.json'),
])
const guiShardTasks = gui.guiCommands.mergedShards.map((shard, index) => ({
@@ -39,15 +40,25 @@ const workflowTasks = workflowTitles.map((title, index) => ({
...(workflowProgress.get(`ORA-GUI-WF-${String(index).padStart(3, '0')}`).evidence.length > 0 ? { evidence: workflowProgress.get(`ORA-GUI-WF-${String(index).padStart(3, '0')}`).evidence } : {}),
exit: index === 0 ? 'one command family and its exact task are recorded' : 'one native workflow fixture and its focused assertion pass',
}))
const workflowClosed = workflowTasks.every((task) => task.status === 'completed')
const driftCases = composite.cases.filter((fixture) => fixture.mutation.metrics.namingRestorationDriftStages > 0)
const classifications = new Map(recoveredNaming.classifications.map((entry) => [entry.caseId, entry]))
if (classifications.size !== recoveredNaming.classifications.length) throw new Error('Recovered naming classifications contain duplicate cases.')
for (const entry of recoveredNaming.classifications) {
if (!driftCases.some(({ id }) => id === entry.caseId) || !['stable_semantics', 'allowed_evolution', 'implementation_defect'].includes(entry.classification)) throw new Error(`Recovered naming classification ${entry.caseId} is invalid.`)
}
const classifiedPrefix = driftCases.findIndex(({ id }) => !classifications.has(id))
const completedDriftCases = classifiedPrefix < 0 ? driftCases.length : classifiedPrefix
if (driftCases.slice(completedDriftCases).some(({ id }) => classifications.has(id))) throw new Error('Recovered naming classifications must form a contiguous serial prefix.')
const driftTasks = driftCases.map((fixture, index) => ({
id: `TSN-DRIFT-${String(index).padStart(3, '0')}`,
title: `Classify recovered naming evolution for ${fixture.id}`,
status: 'pending',
status: index < completedDriftCases ? 'completed' : workflowClosed && index === completedDriftCases ? 'in_progress' : 'pending',
dependencies: index === 0 ? ['ORA-GUI-WF-006'] : [`TSN-DRIFT-${String(index - 1).padStart(3, '0')}`],
caseId: fixture.id,
driftStages: fixture.mutation.metrics.namingRestorationDriftStages,
driftOrdinals: fixture.mutation.metrics.namingRestorationDriftOrdinals,
...(classifications.has(fixture.id) ? { classification: classifications.get(fixture.id).classification, evidence: ['config/freecad-recovered-naming-classification.json', classifications.get(fixture.id).reasonCode] } : {}),
exit: 'the case is classified as stable semantics, allowed evolution, or an implementation defect',
}))
const productionDriftTasks = correlation.mutations
@@ -86,8 +97,8 @@ const closureTasks = [
]
const milestones = [
{ id: 'ORA-GUI-BASELINE', exactTask: 'EX-ORA-01', status: 'completed', tasks: [...setupTasks, ...guiShardTasks, ...closureTasks] },
{ id: 'ORA-GUI-WORKFLOWS', exactTask: 'EX-ORA-01', status: 'in_progress', tasks: workflowTasks },
{ id: 'TSN-RECOVERY-DRIFT', exactTask: 'EX-TSN-04', status: 'pending', tasks: [...driftTasks, ...productionDriftTasks] },
{ id: 'ORA-GUI-WORKFLOWS', exactTask: 'EX-ORA-01', status: workflowClosed ? 'completed' : 'in_progress', tasks: workflowTasks },
{ id: 'TSN-RECOVERY-DRIFT', exactTask: 'EX-TSN-04', status: workflowClosed ? 'in_progress' : 'pending', tasks: [...driftTasks, ...productionDriftTasks] },
{ id: 'TSN-ORDERED-PAIRS', exactTask: 'EX-TSN-04', status: 'pending', tasks: transitionTasks },
]
const allTasks = milestones.flatMap((milestone) => milestone.tasks)
@@ -97,7 +108,7 @@ if (!nextTask) throw new Error('FreeCAD active work queue requires one in-progre
const queue = {
schemaVersion: 1,
baseline: { freecadVersion: '1.1.1', commit: '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' },
updatedAt: '2026-08-14',
updatedAt: '2026-08-15',
generatedBy: 'scripts/generate-freecad-active-work-queue.mjs',
policy: {
maxInProgress: 1,

View File

@@ -1,5 +1,5 @@
import { spawnSync } from 'node:child_process'
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { mkdir, writeFile } from 'node:fs/promises'
import { resolve } from 'node:path'
@@ -8,63 +8,81 @@ const executable = resolve(root, '.cache/freecad/install-desktop/bin/FreeCAD')
const probe = resolve(root, 'scripts/freecad-gui-workflow-oracle.py')
const sysroot = resolve(root, '.cache/freecad/sysroot')
const outputDirectory = resolve(root, '.cache/freecad/gui-workflow-oracle')
const resultFile = resolve(outputDirectory, 'partdesign-pad-success-result.json')
const reportPath = resolve(root, 'config/freecad-gui-workflow-oracle.json')
const configDirectory = resolve(outputDirectory, 'config')
const userConfig = resolve(configDirectory, 'user.cfg')
const systemConfig = resolve(configDirectory, 'system.cfg')
const marker = 'FREECAD_GUI_WORKFLOW_RESULT='
const timeoutMs = Number(process.env.FREECAD_GUI_WORKFLOW_TIMEOUT_MS || 120_000)
const fail = (message) => { throw new Error(`FreeCAD GUI workflow oracle: ${message}`) }
await mkdir(outputDirectory, { recursive: true })
mkdirSync(configDirectory, { recursive: true })
const emptyConfig = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n<FCParameters><FCParamGroup Name="Root"/></FCParameters>\n'
writeFileSync(userConfig, emptyConfig)
writeFileSync(systemConfig, emptyConfig)
const runtimeEnv = {
...process.env,
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}` : ''}`,
MATPLOTLIBRC: resolve(sysroot, 'usr/share/matplotlib/mpl-data/matplotlibrc'),
MPLBACKEND: 'Agg',
FREECAD_GUI_WORKFLOW_RESULT_FILE: resultFile,
const runProbe = (state) => {
const resultFile = resolve(outputDirectory, `partdesign-pad-${state}-result.json`)
const recoveryFile = resolve(outputDirectory, 'partdesign-pad-recovery.FCStd')
const configDirectory = resolve(outputDirectory, `config-${state}`)
const userConfig = resolve(configDirectory, 'user.cfg')
const systemConfig = resolve(configDirectory, 'system.cfg')
mkdirSync(configDirectory, { recursive: true })
writeFileSync(userConfig, emptyConfig)
writeFileSync(systemConfig, emptyConfig)
rmSync(resultFile, { force: true })
if (state === 'recovery') rmSync(recoveryFile, { force: true })
const runtimeEnv = {
...process.env,
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}` : ''}`,
MATPLOTLIBRC: resolve(sysroot, 'usr/share/matplotlib/mpl-data/matplotlibrc'),
MPLBACKEND: 'Agg',
FREECAD_GUI_WORKFLOW_STATE: state,
FREECAD_GUI_WORKFLOW_RESULT_FILE: resultFile,
FREECAD_GUI_WORKFLOW_RECOVERY_FILE: recoveryFile,
}
const execution = spawnSync('xvfb-run', ['-a', executable, '--user-cfg', userConfig, '--system-cfg', systemConfig, '--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), probe], {
cwd: root,
encoding: 'utf8',
maxBuffer: 32 * 1024 * 1024,
timeout: timeoutMs,
env: runtimeEnv,
})
const output = `${execution.stdout ?? ''}\n${execution.stderr ?? ''}`
const markerLine = output.split(/\r?\n/).find((line) => line.startsWith(marker))
const resultBytes = (() => { try { return readFileSync(resultFile, 'utf8') } catch { return '' } })()
if (execution.error || (execution.status !== 0 && !resultBytes && !markerLine)) fail(`${state} native probe exited with ${execution.status}: ${[execution.error?.message, output.trim()].filter(Boolean).join('\n')}`)
const result = JSON.parse(resultBytes || markerLine.slice(marker.length))
if (result.freecadVersion !== '1.1.1' || result.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail(`${state} native baseline mismatch: ${JSON.stringify(result)}`)
if (result.workflowId !== 'partdesign-pad-task' || result.commandId !== 'PartDesign_Pad' || result.state !== state || result.success !== true) fail(`Pad ${state} workflow failed: ${JSON.stringify(result)}`)
return result
}
const execution = spawnSync('xvfb-run', ['-a', executable, '--user-cfg', userConfig, '--system-cfg', systemConfig, '--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), probe], {
cwd: root,
encoding: 'utf8',
maxBuffer: 32 * 1024 * 1024,
timeout: timeoutMs,
env: runtimeEnv,
})
const output = `${execution.stdout ?? ''}\n${execution.stderr ?? ''}`
const markerLine = output.split(/\r?\n/).find((line) => line.startsWith(marker))
const resultBytes = (() => { try { return readFileSync(resultFile, 'utf8') } catch { return '' } })()
if (execution.error || (execution.status !== 0 && !resultBytes && !markerLine)) fail(`native probe exited with ${execution.status}: ${[execution.error?.message, output.trim()].filter(Boolean).join('\n')}`)
const result = JSON.parse(resultBytes || markerLine.slice(marker.length))
if (result.freecadVersion !== '1.1.1' || result.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail(`native baseline mismatch: ${JSON.stringify(result)}`)
if (result.workflowId !== 'partdesign-pad-task' || result.commandId !== 'PartDesign_Pad' || result.state !== 'success' || result.success !== true) fail(`Pad success workflow failed: ${JSON.stringify(result)}`)
const success = runProbe('success')
const disabled = runProbe('disabled')
const failure = runProbe('failure')
const cancel = runProbe('cancel')
const recovery = runProbe('recovery')
const report = {
schemaVersion: 1,
baseline: {
freecadVersion: result.freecadVersion,
commit: result.gitCommit,
freecadVersion: success.freecadVersion,
commit: success.gitCommit,
profile: 'desktop-xvfb-isolated-config',
},
generatedBy: './npmw run probe:freecad-gui-workflow',
checkedBy: './npmw run check:freecad-gui-workflow',
family: {
id: result.workflowId,
primaryCommand: result.commandId,
id: success.workflowId,
primaryCommand: success.commandId,
exactTask: 'EX-UI-03',
source: '.cache/freecad/FreeCAD/src/Mod/PartDesign/Gui/Command.cpp',
},
workflows: {
success: result,
success,
disabled,
failure,
cancel,
recovery,
},
remainingStates: ['disabled', 'failure', 'cancel', 'recovery'],
remainingStates: [],
exactPromotionReady: false,
}
await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`)
@@ -72,10 +90,19 @@ console.log(JSON.stringify({
status: 'freecad-gui-workflow-generated',
family: report.family.id,
command: report.family.primaryCommand,
state: result.state,
bodyTip: result.after.bodyTip,
volume: result.after.pad.volume,
taskOpened: result.taskPanel.opened.activeDialog,
taskClosed: !result.taskPanel.closed.activeDialog,
capturedStates: Object.keys(report.workflows),
bodyTip: success.after.bodyTip,
volume: success.after.pad.volume,
taskOpened: success.taskPanel.opened.activeDialog,
taskClosed: !success.taskPanel.closed.activeDialog,
disabledWithoutDocument: disabled.after.sideEffects.documentCount === 0,
disabledWithoutTask: !disabled.after.sideEffects.taskPanel.activeDialog,
disabledWithoutTransaction: !disabled.after.sideEffects.transaction.active,
failureDiagnostic: failure.diagnostic.warnings[0].text,
failureRolledBack: !failure.after.padExists && !failure.after.transaction.active,
cancelPreviewVolume: cancel.taskPanel.preview.volume,
cancelRolledBack: !cancel.after.padExists && !cancel.after.transaction.active,
recoveryVolume: recovery.persistence.resavedReopened.padVolume,
recoveryResaved: recovery.persistence.resavedReopened.padExists,
report: 'config/freecad-gui-workflow-oracle.json',
}, null, 2))

View File

@@ -0,0 +1,103 @@
import { spawnSync } from 'node:child_process'
import { createHash } from 'node:crypto'
import { existsSync } from 'node:fs'
import { readFile, stat, writeFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { buildRecoveredNamingSnapshot, recoveredNamingDecisions, same, validateRecoveredNamingSnapshot } from './freecad-recovered-naming-classification.mjs'
const root = resolve(new URL('..', import.meta.url).pathname)
const executable = process.env.FREECAD_CMD || resolve(root, '.cache/freecad/install-desktop/bin/FreeCADCmd')
const pythonPath = resolve(root, '.cache/freecad/sysroot/usr/lib/python3/dist-packages')
const harnessPath = resolve(root, 'scripts/freecad-composite-history-elementmap-oracle.py')
const oraclePath = resolve(root, 'config/freecad-composite-history-elementmap-oracle.json')
const resavePath = resolve(root, 'config/freecad-composite-history-resave-verification.json')
const outputPath = resolve(root, 'config/freecad-recovered-naming-classification.json')
const fail = (message) => { throw new Error(`FreeCAD recovered naming classification: ${message}`) }
if (!existsSync(executable)) fail(`native executable is missing: ${executable}`)
const [reference, referenceResave, harnessContent, harnessBytes] = await Promise.all([
readFile(oraclePath, 'utf8').then(JSON.parse),
readFile(resavePath, 'utf8').then(JSON.parse),
readFile(harnessPath),
stat(harnessPath).then(({ size }) => size),
])
const execution = spawnSync(executable, ['--python-path', pythonPath, harnessPath], {
cwd: root,
encoding: 'utf8',
timeout: 180_000,
maxBuffer: 80 * 1024 * 1024,
env: {
...process.env,
PYTHONPATH: `${pythonPath}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`,
LD_LIBRARY_PATH: `${resolve(root, '.cache/freecad/sysroot/usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}`,
MATPLOTLIBRC: resolve(root, '.cache/freecad/sysroot/etc/matplotlibrc'),
MPLBACKEND: 'Agg',
},
})
const output = `${execution.stdout || ''}\n${execution.stderr || ''}`
const marker = 'FREECAD_COMPOSITE_HISTORY_ELEMENTMAP_RESULT='
const markerIndex = output.indexOf(marker)
const payload = markerIndex >= 0 ? output.slice(markerIndex + marker.length).match(/\{.*\}/s)?.[0] : undefined
if (execution.error || execution.status !== 0 || !payload) fail(`native replay exited with ${execution.status}: ${execution.error?.message || output.trim()}`)
const replay = JSON.parse(payload)
if (reference.freecadVersion !== '1.1.1' || reference.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || replay.freecadVersion !== reference.freecadVersion || replay.gitCommit !== reference.gitCommit || replay.status !== 'pass') fail('native baseline or replay status is invalid.')
const classifications = recoveredNamingDecisions.map((decision) => {
const referenceFixture = reference.cases.find(({ id }) => id === decision.caseId)
const referenceResaveFixture = referenceResave.cases.find(({ id }) => id === decision.caseId)
const replayFixture = replay.cases.find(({ id }) => id === decision.caseId)
if (![referenceFixture, referenceResaveFixture, replayFixture].every(Boolean)) fail(`${decision.caseId} is missing from reference or replay evidence.`)
const referenceSnapshot = buildRecoveredNamingSnapshot(referenceFixture, referenceResaveFixture)
const replaySnapshot = buildRecoveredNamingSnapshot(replayFixture, {
stageCorrelations: replayFixture.stageCorrelations,
roundtripNameDrift: replayFixture.roundtripNameDrift,
resaveNameDrift: replayFixture.resaveNameDrift,
nativeDesktopResaveCovered: replayFixture.nativeDesktopResaveCovered,
})
validateRecoveredNamingSnapshot(referenceSnapshot)
validateRecoveredNamingSnapshot(replaySnapshot)
if (!same(referenceSnapshot, replaySnapshot)) fail(`${decision.caseId} fingerprints differ between the locked reference and independent native replay.`)
return {
...decision,
reference: referenceSnapshot,
replay: replaySnapshot,
replayStable: true,
implementationDefect: decision.classification === 'implementation_defect',
}
})
const countClassification = (classification) => classifications.filter((entry) => entry.classification === classification).length
const classifiedStages = classifications.reduce((sum, entry) => sum + entry.reference.driftOrdinals.length, 0)
const report = {
schemaVersion: 1,
baseline: {
freecadVersion: reference.freecadVersion,
commit: reference.gitCommit,
oracleId: reference.baselineId,
},
generatedBy: './npmw run probe:freecad-recovered-naming-classification',
checkedBy: './npmw run check:freecad-recovered-naming-classification',
nativeProbe: {
path: 'scripts/freecad-composite-history-elementmap-oracle.py',
bytes: harnessBytes,
sha256: createHash('sha256').update(harnessContent).digest('hex'),
independentRuns: 2,
},
classifications,
summary: {
classifiedCases: classifications.length,
classifiedStages,
stableSemantics: countClassification('stable_semantics'),
allowedEvolution: countClassification('allowed_evolution'),
implementationDefects: countClassification('implementation_defect'),
unknown: 0,
},
}
await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify({
status: 'freecad-recovered-naming-classification-generated',
cases: report.classifications.map(({ taskId, caseId, classification, reference: snapshot }) => ({ taskId, caseId, classification, driftOrdinals: snapshot.driftOrdinals })),
independentRuns: report.nativeProbe.independentRuns,
output: 'config/freecad-recovered-naming-classification.json',
}, null, 2))