fix: make p-rec-update initialization load-safe

This commit is contained in:
codex
2026-08-07 07:34:54 +03:00
parent 20960ae91f
commit 70ae18981a
4 changed files with 63 additions and 6 deletions
+4
View File
@@ -1,6 +1,10 @@
/link/ /link/
/link /link
# Generated build output is never source or package input.
**/bin/
**/obj/
# DocumentAutomation generated files and local source data # DocumentAutomation generated files and local source data
DocumentAutomation/**/bin/ DocumentAutomation/**/bin/
DocumentAutomation/**/obj/ DocumentAutomation/**/obj/
@@ -9,8 +9,18 @@ public sealed class EntryPoint : IExtensionApplication
{ {
public void Initialize() public void Initialize()
{ {
RuntimeMarker.Write("AssemblyInitializeStarted"); // Keep the load boundary free of Plant/DataLinks access. Any exception
RuntimeMarker.Write("AssemblyInitializeCompleted"); // here must remain observable independently of a project fixture.
RuntimeMarker.Write("InitializeEntered");
try
{
RuntimeMarker.Write("AssemblyInitializeCompleted");
}
catch (System.Exception exception)
{
RuntimeMarker.Write("AssemblyInitializeFailed", exception.GetType().Name + ":" + exception.Message);
throw;
}
} }
public void Terminate() => RuntimeMarker.Write("AssemblyTerminateCompleted"); public void Terminate() => RuntimeMarker.Write("AssemblyTerminateCompleted");
@@ -37,8 +47,27 @@ public sealed class EntryPoint : IExtensionApplication
}, },
UpdateExecutionMode.DryRun, UpdateExecutionMode.DryRun,
ControlledFixturePermit: false)); ControlledFixturePermit: false));
var status = new
{
schemaVersion = 1,
operationId = Environment.GetEnvironmentVariable("P_REC_UPDATE_OPERATION_ID") ?? "unknown",
processId = Environment.ProcessId,
command,
assemblyVersion = typeof(EntryPoint).Assembly.GetName().Version?.ToString(),
assemblySha256 = RuntimeMarker.AssemblySha256(),
product = "Plant3D",
productYear = 2026,
activeDocumentPresent = document is not null,
activePlantProjectPresent = HasPlantProject(),
dryRunInfrastructure = "Available",
domainApply = "BlockedByExactFixture",
diagnostics = plan.Diagnostics
};
document?.Editor.WriteMessage($"\nP_Rec_Update {command}: {(plan.Accepted ? "DryRun planned; no project data changed." : string.Join(", ", plan.Diagnostics))}"); document?.Editor.WriteMessage($"\nP_Rec_Update {command}: {(plan.Accepted ? "DryRun planned; no project data changed." : string.Join(", ", plan.Diagnostics))}");
RuntimeMarker.Write(plan.Accepted ? "CommandRegistrationVerified" : "CommandPreflightRejected"); // STATUS proves registration and reports preflight state; it must not
// be classified as a failed command merely because no controlled Plant
// project fixture is open.
RuntimeMarker.Write(command == "P-R-UPD-STATUS" || plan.Accepted ? "CommandRegistrationVerified" : "CommandPreflightRejected", System.Text.Json.JsonSerializer.Serialize(status));
} }
private static bool HasPlantProject() private static bool HasPlantProject()
@@ -1,19 +1,30 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Json;
namespace PRecUpdate.Plant3D2026; namespace PRecUpdate.Plant3D2026;
internal static class RuntimeMarker internal static class RuntimeMarker
{ {
internal static void Write(string state) internal static void Write(string state, string? detail = null)
{ {
var root = Environment.GetEnvironmentVariable("P_REC_UPDATE_MARKER_ROOT"); var root = Environment.GetEnvironmentVariable("P_REC_UPDATE_MARKER_ROOT");
if (string.IsNullOrWhiteSpace(root)) return; if (string.IsNullOrWhiteSpace(root)) return;
try try
{ {
Directory.CreateDirectory(root); Directory.CreateDirectory(root);
var payload = $"{DateTimeOffset.UtcNow:O}|{state}|{typeof(EntryPoint).Assembly.GetName().Name}"; var payload = JsonSerializer.Serialize(new
File.WriteAllText(Path.Combine(root, state + ".marker"), payload, new UTF8Encoding(false)); {
schemaVersion = 1,
operationId = Environment.GetEnvironmentVariable("P_REC_UPDATE_OPERATION_ID") ?? "unknown",
processId = Environment.ProcessId,
timestampUtc = DateTimeOffset.UtcNow,
state,
detail,
assembly = typeof(EntryPoint).Assembly.GetName().Name,
assemblySha256 = AssemblySha256()
});
File.WriteAllText(Path.Combine(root, state + ".marker.json"), payload, new UTF8Encoding(false));
} }
catch { /* Evidence cannot make Plant initialization fail. */ } catch { /* Evidence cannot make Plant initialization fail. */ }
} }
@@ -46,4 +46,17 @@ public sealed class UpdatePlanBuilderTests
Assert.True(plan.MayWriteDrawing); Assert.True(plan.MayWriteDrawing);
Assert.True(plan.MayWriteProjectData); Assert.True(plan.MayWriteProjectData);
} }
[Fact]
public void Status_probe_preflight_is_deterministic_and_never_write_capable()
{
var request = new UpdateRequest(true, false, false, new HashSet<UpdateOperation>(), UpdateExecutionMode.DryRun, false);
var first = builder.Build(request);
var second = builder.Build(request);
Assert.False(first.Accepted);
Assert.Equal(first.Diagnostics, second.Diagnostics);
Assert.False(first.MayWriteDrawing);
Assert.False(first.MayWriteProjectData);
Assert.Contains("PLANT_PROJECT_REQUIRED", first.Diagnostics);
}
} }