Summary
Make matcher and analyzer execution transactional at the host boundary: each component receives mutation-isolated inputs, and only a successful result is committed to the canonical registry.
Current mismatch
internal/engine/engine.go:Match and Analyze pass requests containing mutable graph, registry, and target pointers directly to embedded components. A component can mutate those pointers and then return an error or observe cancellation; the engine continues with changed host state.
Managed plugins behave differently because requests cross the JSON/gRPC boundary. In internal/plugin/registry.go, returned PackageUpdates are applied only after a successful RPC. A failing managed plugin cannot mutate the caller's registry through request pointers.
The same SDK module should not have different rollback behavior solely because it runs embedded versus managed.
Dependency
Blocked on the deep snapshot primitives in bomly-dev/bomly-sdk#27. The final CLI change must pin a released SDK version; do not commit a module replace.
Required behavior
For every matcher and analyzer:
- Build component-visible request data from an SDK deep snapshot, without a JSON round trip.
- Do not expose canonical host pointers to
Ready, Applicable, Match, or Analyze.
- On a nil error, commit exactly one result form:
- a non-nil full
Registry wins; otherwise
- merge
PackageUpdates through sdk.ApplyPackageUpdates.
- On error or cancellation, discard all changes made to request snapshots and result-local state.
- Preserve successful changes from earlier components if a later component fails; rollback is per invocation, not whole-pipeline.
- Keep managed and embedded behavior observably equivalent.
Fresh snapshots for separate Ready, Applicable, and action calls are preferred because managed RPC serialization already prevents mutation from leaking between those calls. If a different scope is chosen, document and test the parity reasoning in the ADR.
Acceptance criteria
Architecture and compatibility
Add or update an ADR under dev-docs/adr/ because this tightens component execution semantics and may affect components that relied on mutating request inputs instead of returning results. The wire contract remains unchanged: full registries stay the v1 baseline and PackageUpdates remains optional/additive.
Shared copying and merge rules belong in the SDK; orchestration and commit timing belong here.
Out of scope
- Parallel matcher/analyzer execution.
- Changing merge precedence.
- Removing the full-registry response.
- Making every SDK model immutable.
Verification
Run:
make test
make build-lite
If the SDK pin changes generated documentation, run make generate and commit the drift.
Summary
Make matcher and analyzer execution transactional at the host boundary: each component receives mutation-isolated inputs, and only a successful result is committed to the canonical registry.
Current mismatch
internal/engine/engine.go:MatchandAnalyzepass requests containing mutable graph, registry, and target pointers directly to embedded components. A component can mutate those pointers and then return an error or observe cancellation; the engine continues with changed host state.Managed plugins behave differently because requests cross the JSON/gRPC boundary. In
internal/plugin/registry.go, returnedPackageUpdatesare applied only after a successful RPC. A failing managed plugin cannot mutate the caller's registry through request pointers.The same SDK module should not have different rollback behavior solely because it runs embedded versus managed.
Dependency
Blocked on the deep snapshot primitives in bomly-dev/bomly-sdk#27. The final CLI change must pin a released SDK version; do not commit a module
replace.Required behavior
For every matcher and analyzer:
Ready,Applicable,Match, orAnalyze.Registrywins; otherwisePackageUpdatesthroughsdk.ApplyPackageUpdates.Fresh snapshots for separate
Ready,Applicable, and action calls are preferred because managed RPC serialization already prevents mutation from leaking between those calls. If a different scope is chosen, document and test the parity reasoning in the ADR.Acceptance criteria
Architecture and compatibility
Add or update an ADR under
dev-docs/adr/because this tightens component execution semantics and may affect components that relied on mutating request inputs instead of returning results. The wire contract remains unchanged: full registries stay the v1 baseline andPackageUpdatesremains optional/additive.Shared copying and merge rules belong in the SDK; orchestration and commit timing belong here.
Out of scope
Verification
Run:
make test make build-liteIf the SDK pin changes generated documentation, run
make generateand commit the drift.