Compare commits

..

5 Commits

9 changed files with 521 additions and 8 deletions
+2 -2
View File
@@ -16,7 +16,7 @@
| Feature Step | Bounded Context | Workflow Slice | Notes |
| :----------- | :-------------- | :------------- | :---- |
| Ingest upstream bundle snapshot into deterministic recovery artifacts | `ingest-snapshot` | `deterministic-bundle-ingest` | Produces the canonical per-run source of truth used by all later slices. |
| Identify vendored package boundaries and confidence decisions | `dependency-recovery` | `identify-vendored-packages` | Consumes ingest artifacts and records accepted, rejected, and unresolved dependency decisions. |
| Identify the next vendored package decision from one source | `dependency-recovery` | `identify-next-vendored-package-decision-from-source` | Consumes ingest artifacts, emits one dependency decision at a time, and signals when no more plausible candidates remain. |
| Replace accepted vendored packages with external dependencies while keeping fallbacks | `dependency-recovery` | `externalize-accepted-dependencies` | Depends on identified package decisions; unresolved packages stay bundled. |
| Extract deterministic context packets for each segment | `static-context-evidence` | `extract-segment-context` | Consumes ingest output after dependency treatment to emit machine-readable evidence. |
| Compare adjacent runs and classify lineage-aware changes | `snapshot-lineage` | `diff-adjacent-runs` | Consumes current and previous run manifests plus Phase 3 context. |
@@ -41,7 +41,7 @@
## Recommended Slice Order
1. `ingest-snapshot/deterministic-bundle-ingest` — all later slices depend on deterministic ingest artifacts and canonical segment boundaries.
2. `dependency-recovery/identify-vendored-packages` — shrinks the app-authored surface before later evidence and naming work.
2. `dependency-recovery/identify-next-vendored-package-decision-from-source` — shrinks the app-authored surface one source decision at a time before later evidence and naming work.
3. `dependency-recovery/externalize-accepted-dependencies` — completes dependency treatment before downstream evidence extraction.
4. `static-context-evidence/extract-segment-context` — provides deterministic evidence used by diffing, summaries, and transform anchoring.
5. `snapshot-lineage/diff-adjacent-runs` — identifies changed/new material and durable lineage needed for iterative naming.
@@ -72,7 +72,7 @@
## 6. Candidate Workflow Slices
- ingest-snapshot/deterministic-bundle-ingest: turn an upstream bundle into deterministic segment records and canonical source projection.
- dependency-recovery/identify-vendored-packages: score dependency candidates and recover package boundaries.
- dependency-recovery/identify-next-vendored-package-decision-from-source: recover the next plausible vendored candidate from one source and record one dependency decision or exhaustion.
- dependency-recovery/externalize-accepted-dependencies: replace accepted vendored code with npm imports while preserving fallbacks.
- static-context-evidence/extract-segment-context: emit canonical context packets and binding/link evidence.
- snapshot-lineage/diff-adjacent-runs: classify changes, mint lineage, and produce relabel queues plus upstream summaries.
+1 -1
View File
@@ -41,7 +41,7 @@
| Bounded Context | Workflow Slice | Slice Discovery | Core Sketch | Blueprint | Design Security | Assembly | Impl Security | Refactor | Notes |
| :-------------- | :------------- | :-------------- | :---------- | :-------- | :-------------- | :------- | :------------ | :------- | :---- |
| `ingest-snapshot` | `deterministic-bundle-ingest` | `Complete` | `Complete` | `Complete` | `Complete` | `Complete` | `Not Started` | `Not Started` | `Foundational source-of-truth slice.` |
| `dependency-recovery` | `identify-vendored-packages` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Shrinks app-authored surface before later phases.` |
| `dependency-recovery` | `identify-next-vendored-package-decision-from-source` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Shrinks app-authored surface one decision at a time.` |
| `dependency-recovery` | `externalize-accepted-dependencies` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Depends on package identification decisions.` |
| `static-context-evidence` | `extract-segment-context` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Produces deterministic evidence for downstream consumers.` |
| `snapshot-lineage` | `diff-adjacent-runs` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Owns lineage and changed/new segment routing.` |
@@ -0,0 +1,38 @@
# Dependency Recovery Context
**Vendored Package**: third-party code embedded inside the upstream bundle and considered for recovery as an external dependency.
_Avoid_: library blob, bundled package blob
**Dependency Decision**: the context-owned determination that a vendored candidate is accepted, rejected, or unresolved with recorded evidence and rationale.
_Avoid_: match result, package guess
**Acceptance Threshold**: the configurable confidence score boundary at or above which a vendored candidate is accepted automatically.
_Avoid_: hard-coded cutoff, fixed confidence bar
**Rejected Candidate**: a vendored candidate whose evidence deterministically supports that it is not a package match worth externalizing.
_Avoid_: low-confidence maybe, unresolved miss
**Unresolved Candidate**: a vendored candidate that remains plausible but is below the acceptance threshold, colliding, ambiguous, or otherwise unsafe to accept.
_Avoid_: rejected maybe, ignored candidate
**Fallback Preservation**: keeping bundled code available when externalization is unsafe or unresolved so later validation can compare behaviors safely.
_Avoid_: leave old code around, dead backup code
**Externalization**: replacing accepted vendored code with an external dependency reference without deleting the original bundled implementation.
_Avoid_: strip dependency, remove vendor code
## Example dialogue
> **Developer:** "If a candidate scores below the configured threshold, do we reject it?"
> **Domain Expert:** "No. If it still looks plausible, it stays unresolved until stronger evidence appears or a reviewer decides otherwise."
>
> **Developer:** "When do we mark a candidate rejected?"
> **Domain Expert:** "Only when the evidence deterministically says it is not a vendored package match worth externalizing."
>
> **Developer:** "Does accepting a vendored package delete the bundled code?"
> **Domain Expert:** "No. Externalization still preserves the bundled implementation as fallback evidence."
## Flagged ambiguities
- "Low confidence" alone does not mean `rejected`; low-confidence but still plausible candidates are `Unresolved Candidates`.
- "Match result" was too scoring-shaped; the preferred term is `Dependency Decision` because this context owns a reviewer-facing decision with rationale.
@@ -1,9 +1,9 @@
# Workflow Decomposition: Identify Vendored Packages
# Workflow Decomposition: Identify Next Vendored Package Decision From Source
- Bounded context: `dependency-recovery`
- Workflow slug: `identify-vendored-packages`
- Trigger: `Identify vendored packages from ingest artifacts`
- Success outcome: Vendored candidates receive accepted, rejected, or unresolved dependency decisions with evidence and rationale.
- Workflow slug: `identify-next-vendored-package-decision-from-source`
- Trigger: `Identify next vendored package decision from source`
- Success outcome: The next plausible vendored candidate from one source yields either a single accepted, rejected, or unresolved dependency decision, or an explicit no-more-candidates outcome.
## Inputs Owned by This Context
@@ -0,0 +1,59 @@
# Slice Discovery: Identify Next Vendored Package Decision From Source
- Bounded context: `dependency-recovery`
- Workflow slug: `identify-next-vendored-package-decision-from-source`
## Happy Path
- The workflow receives deterministic ingest artifacts from `ingest-snapshot`, including the run manifest, segment records, and canonical source projection.
- The workflow inspects the source snapshot and deterministically recovers the next plausible vendored candidate boundary that has not already been decided.
- The workflow computes a deterministic confidence ranking for package matches on that next candidate by combining the configured evidence signals.
- The workflow compares the strongest candidate match against the configurable acceptance threshold.
- If the strongest candidate is at or above the acceptance threshold, the workflow records one `accepted` dependency decision.
- If the strongest candidate remains plausible but is below threshold, colliding, or ambiguous, the workflow records one `unresolved` dependency decision.
- If the candidate evidence deterministically supports that it is not a vendored package match, the workflow records one `rejected` dependency decision.
- The workflow emits one decision record with evidence, rationale, boundary notes, and replacement planning hints.
- When no plausible undecided vendored candidates remain for the source under the current rules, the workflow emits an explicit no-more-candidates outcome.
- Downstream `externalize-accepted-dependencies` consumes the accumulated accepted decisions for externalization while preserving unresolved and rejected records for review and summaries.
## Edge Cases
- A package is split across multiple obfuscated wrappers -> recover one vendored candidate boundary spanning the related segments and record the grouping rationale.
- Multiple package matches compete for the same segment group -> keep the stronger deterministic ranking, but if the competition remains plausible and unsafe to settle automatically, emit `unresolved` rather than forcing rejection.
- A candidate has some evidence but stays below the configured acceptance threshold -> emit `unresolved`.
- A candidate has strong counter-evidence that it is app-authored or otherwise not a vendored package match -> emit `rejected`.
- Runtime traces are unavailable -> continue with static evidence only.
- Runtime traces conflict with static evidence -> record the mixed provenance and prefer `unresolved` unless the conflict is resolved deterministically.
- A candidate package boundary is only partially recoverable -> record the partial boundary notes and keep the decision unresolved unless the recoverable portion still supports a deterministic accepted or rejected decision.
- Two runs use different threshold settings -> preserve the same confidence scores and evidence, but allow the configurable threshold to change which candidates become accepted.
## Business Rules & Invariants
- Rule: Confidence scoring is deterministic for identical ingest artifacts, evidence sources, and configuration.
- Rule: Acceptance uses a configurable threshold rather than a hard-coded cutoff.
- Rule: `Accepted` means the candidate score is at or above the configured acceptance threshold and the match is safe enough to externalize later.
- Rule: `Unresolved` is preferred over `Rejected` when a candidate remains plausible but is below threshold, colliding, ambiguous, or otherwise unsafe to settle automatically.
- Rule: `Rejected` is reserved for candidates whose evidence deterministically supports that they are not vendored package matches worth externalizing.
- Rule: The workflow records auditable evidence and rationale for every dependency decision.
- Rule: One vendored package candidate may map to multiple segments when the boundary recovery rationale supports it.
- Invariant: This slice identifies and records dependency decisions only; it does not externalize code.
- Invariant: Accepted, rejected, and unresolved candidates all remain visible in emitted manifests.
- Invariant: Changing the acceptance threshold must not require redesigning the manifest format.
## Required Decisions Owned by This Context
- Which evidence signals are combined into deterministic vendored-package confidence scoring.
- Which related segments belong to one vendored package candidate boundary.
- Whether a candidate becomes accepted, rejected, or unresolved.
- What evidence, rationale, ambiguity notes, and replacement planning hints must be persisted for downstream use.
## Handoff Assumptions
- `ingest-snapshot` provides deterministic run manifest, segment records, and canonical projection as the source of truth for candidate discovery.
- `externalize-accepted-dependencies` consumes only accepted dependency decisions for replacement work.
- Later review and release-summary seams consume accepted, rejected, and unresolved manifests without reopening this slice's decision logic.
- Optional runtime traces act only as additional evidence inside this context and do not override deterministic decision recording requirements.
## Open Questions
- None currently inside this slice; threshold tuning and evidence-weight configuration remain implementation concerns within this context.
@@ -0,0 +1,105 @@
# Core Sketch: Identify Next Vendored Package Decision From Source
- Bounded context: `dependency-recovery`
- Workflow slug: `identify-next-vendored-package-decision-from-source`
## Command
- `IdentifyNextVendoredPackageDecisionFromSource`
- Meaning: inspect one source snapshot, deterministically recover the next plausible vendored candidate if one remains, and record exactly one `Dependency Decision` or an explicit no-more-candidates outcome for later `Externalization`.
## Required State
State owned by `dependency-recovery` and required to decide this workflow:
- `VendoredCandidateDiscoveryRules`
- allowed evidence signals for vendored-package discovery
- grouping rules for segment and segment-group candidates
- rationale rules for recovered package boundaries
- `ConfidenceScoringRules`
- deterministic scoring weights or combination rules across evidence types
- ranking rules for competing package matches on the same candidate boundary
- tie-break rules when scores or evidence patterns compete
- `AcceptanceThresholdPolicy`
- configurable `Acceptance Threshold`
- rules for when plausible but below-threshold or colliding candidates remain `Unresolved Candidates`
- rules for when counter-evidence is strong enough to produce a `Rejected Candidate`
- `DependencyDecisionRequirements`
- required manifest fields for evidence summary, raw evidence references, confidence score, provenance, recovered boundary notes, ambiguity notes, replacement plan, and fallback reference
- required auditability rules for later review and downstream handoffs
## Observed Inputs
Snapshots or handoffs read but not owned by this context:
- one source snapshot from `ingest-snapshot`, plus the current decision cursor for that source
- the relevant `Run Manifest` facts and canonical source projection from `ingest-snapshot`
- optional runtime traces from that source used only as additional or tie-break evidence
- optional registry, tarball, or CDN package evidence used to compare matches for the next recovered candidate
## Policy Signature (Pseudo)
```text
recoverNextCandidateBoundary :
SourceSnapshot
-> SourceDecisionCursor
-> VendoredCandidateDiscoveryRules
-> Result<NextCandidateBoundary, NoMoreVendoredCandidates>
scoreVendoredCandidate :
NextCandidateBoundary
-> CandidateEvidenceSources
-> ConfidenceScoringRules
-> RankedCandidateMatches
decideDependencyDecision :
NextCandidateBoundary
-> RankedCandidateMatches
-> AcceptanceThresholdPolicy
-> DependencyDecision
validateDecisionRecord :
DependencyDecision
-> DependencyDecisionRequirements
-> Result<DependencyDecisionRecorded, DependencyDecisionRejected>
performNextVendoredPackageDecisionFromSource :
IdentifyNextVendoredPackageDecisionFromSource
-> DependencyRecoveryState
-> Result<NextVendoredPackageDecisionFromSourceCompleted, VendoredPackageIdentificationHardStopped>
```
## Events
### Success Event
- `NextVendoredPackageDecisionFromSourceCompleted`
- run identity reference
- source reference
- next decision cursor
- emitted single dependency decision
- emitted decision record reference
- evidence artifact references for that candidate
### Exhaustion Event
- `NoMoreVendoredCandidatesRemain`
- run identity reference
- source reference
- final decision cursor
### Failure Event
- `VendoredPackageIdentificationHardStopped`
- run identity when available
- failed stage such as missing ingest artifacts, invalid candidate boundary inputs, or invalid decision-manifest requirements
- failure reason
## Boundary Notes
- The `dependency-recovery` context decides the next vendored-package decision for one source per workflow invocation.
- Outer orchestration may repeat the workflow for the same source using the returned decision cursor until the slice emits `NoMoreVendoredCandidatesRemain`.
- This slice does not externalize accepted packages; that belongs to `dependency-recovery/externalize-accepted-dependencies`.
- `ingest-snapshot` remains the source of truth for run manifest, segment boundaries, and canonical projection; this slice must not reopen ingest decisions.
- Optional runtime traces and package-source comparisons act only as evidence inputs here and must not turn this slice into cross-context orchestration.
- Feature-level orchestration decides whether unresolved or review-needed outcomes slow later phases; this slice only records one audit-ready dependency decision at a time.
@@ -0,0 +1,242 @@
module DependencyRecovery.IdentifyNextVendoredPackageDecisionFromSource
open DependencyRecovery.SharedModel
// 1. Primitives
type TaintedRunManifestReference = TaintedRunManifestReference of string
type TaintedSegmentRecordReference = TaintedSegmentRecordReference of string
type TaintedCanonicalProjectionReference = TaintedCanonicalProjectionReference of string
type TaintedRuntimeTraceReference = TaintedRuntimeTraceReference of string
type TrustedRunManifestReference = TrustedRunManifestReference of string
type TrustedSegmentRecordReference = TrustedSegmentRecordReference of string
type TrustedCanonicalProjectionReference = TrustedCanonicalProjectionReference of string
type TrustedRuntimeTraceReference = TrustedRuntimeTraceReference of string
type CandidateGroupingRule =
| GroupAdjacentSegments
| GroupStructurallyLinkedSegments
| GroupSharedLiteralClusters
| GroupSharedExportSurface
type BoundaryRationaleRule =
| RecordAdjacentGroupingRationale
| RecordStructuralLinkRationale
| RecordSharedLiteralRationale
| RecordExportSurfaceRationale
type ScoringRule =
| WeightLicenseBanner
| WeightPreservedPackageName
| WeightSourceMapHint
| WeightPreservedRequireString
| WeightCharacteristicLiteralSet
| WeightHelperSignature
| WeightAstShapeFingerprint
| WeightExportSurfaceSimilarity
| WeightDependencyGraphPosition
| WeightByteSimilarity
| WeightRuntimeExecutionTrace
type RankingRule =
| RankByTotalEvidenceWeight
| RankByEvidenceDiversity
| RankByBoundaryCoverage
| RankByRuntimeSupport
type TieBreakRule =
| PreferMoreSpecificPackageMatch
| PreferBroaderBoundaryCoverage
| PreferStaticEvidenceAgreement
| PreferStablePackageNameOrder
type UnresolvedRule =
| KeepBelowThresholdCandidatesUnresolved
| KeepCollidingCandidatesUnresolved
| KeepAmbiguousCandidatesUnresolved
| KeepConflictingEvidenceCandidatesUnresolved
type RejectedRule =
| RejectDeterministicallyAppAuthoredCandidates
| RejectDeterministicallyNonPackageCandidates
| RejectDeterministicallyContradictedCandidates
type RequiredManifestField =
| CandidatePackageNameField
| DecisionStateField
| ConfidenceScoreField
| EvidenceSummaryField
| RawEvidenceReferencesField
| MatchedSegmentIdsField
| RecoveredBoundaryNotesField
| ReplacementPlanField
| FallbackReferenceField
| EvidenceProvenanceField
| AmbiguityNotesField
type AuditabilityRule =
| RecordDecisionRationale
| RecordThresholdUsed
| RecordScoringInputs
| RecordCompetingMatches
| RecordDecisionTimestampOrder
// 2. Commands (Inputs)
type SourceDecisionCursor = SourceDecisionCursor of string
type TaintedSourceReference = TaintedSourceReference of string
type TrustedSourceReference = TrustedSourceReference of string
type TaintedCandidateBoundaryReference = TaintedCandidateBoundaryReference of string
type TrustedCandidateBoundaryReference = TrustedCandidateBoundaryReference of string
type IdentifyNextVendoredPackageDecisionFromSource = {
runManifest: TaintedRunManifestReference
canonicalProjection: TaintedCanonicalProjectionReference
source: TaintedSourceReference
decisionCursor: SourceDecisionCursor
runtimeTraces: TaintedRuntimeTraceReference option
}
// 3. Observed inputs and owned state
type TrustedSourceInput = {
runIdentity: RunIdentity
runManifest: TrustedRunManifestReference
canonicalProjection: TrustedCanonicalProjectionReference
source: TrustedSourceReference
decisionCursor: SourceDecisionCursor
runtimeTraces: TrustedRuntimeTraceReference option
}
type VendoredCandidateDiscoveryRules = {
allowedSignals: EvidenceSignal list
groupingRules: CandidateGroupingRule list
boundaryRationaleRules: BoundaryRationaleRule list
}
type ConfidenceScoringRules = {
scoringRules: ScoringRule list
rankingRules: RankingRule list
tieBreakRules: TieBreakRule list
}
type AcceptanceThresholdPolicy = {
acceptanceThreshold: ConfidenceScore
unresolvedRules: UnresolvedRule list
rejectedRules: RejectedRule list
}
type DependencyDecisionRequirements = {
requiredManifestFields: RequiredManifestField list
auditabilityRules: AuditabilityRule list
}
type DependencyRecoveryState = {
candidateDiscoveryRules: VendoredCandidateDiscoveryRules
confidenceScoringRules: ConfidenceScoringRules
acceptanceThresholdPolicy: AcceptanceThresholdPolicy
dependencyDecisionRequirements: DependencyDecisionRequirements
}
// 4. Events (Facts)
type NextCandidateBoundary = {
source: TrustedSourceReference
candidateBoundary: TrustedCandidateBoundaryReference
nextCursor: SourceDecisionCursor
}
type DecisionRecordReference = DecisionRecordReference of string
type NextVendoredPackageDecisionFromSourceCompleted = {
runIdentity: RunIdentity
source: TrustedSourceReference
nextCursor: SourceDecisionCursor
dependencyDecision: DependencyDecision
decisionRecord: DecisionRecordReference
evidenceArtifacts: EvidenceReference list
}
type NoMoreVendoredCandidatesRemain = {
runIdentity: RunIdentity
source: TrustedSourceReference
finalCursor: SourceDecisionCursor
}
type VendoredPackageIdentificationStage =
| CandidateInputParsingStage
| CandidateScoringStage
| DependencyDecisionStage
| DecisionRecordValidationStage
type VendoredPackageIdentificationFailureReason =
| MissingIngestArtifacts
| InvalidIngestArtifactReference
| InvalidCandidateBoundaryReference
| InvalidDecisionRecordRequirements
type VendoredPackageIdentificationHardStopped = {
runIdentity: RunIdentity option
failedStage: VendoredPackageIdentificationStage
reason: VendoredPackageIdentificationFailureReason
}
// 5. State (Aggregate)
type DependencyIdentificationState =
| AwaitingVendoredPackageIdentification of DependencyRecoveryState
| NextVendoredPackageDecisionRecorded of NextVendoredPackageDecisionFromSourceCompleted
| VendoredCandidateDiscoveryExhausted of NoMoreVendoredCandidatesRemain
// 6. Parse and decision contracts
val parseSourceInput :
IdentifyNextVendoredPackageDecisionFromSource
-> Result<TrustedSourceInput, VendoredPackageIdentificationHardStopped>
val recoverNextCandidateBoundary :
TrustedSourceInput
-> VendoredCandidateDiscoveryRules
-> Result<NextCandidateBoundary, NoMoreVendoredCandidatesRemain>
val scoreCandidateMatches :
NextCandidateBoundary
-> TrustedSourceInput
-> ConfidenceScoringRules
-> Result<CandidateMatch list, VendoredPackageIdentificationHardStopped>
val decideDependencyDecision :
AcceptanceThresholdPolicy
-> NextCandidateBoundary
-> CandidateMatch list
-> Result<DependencyDecision, VendoredPackageIdentificationHardStopped>
val validateDecisionRecord :
DependencyDecisionRequirements
-> DependencyDecision
-> Result<DecisionRecordReference, VendoredPackageIdentificationHardStopped>
val decide :
DependencyIdentificationState
-> IdentifyNextVendoredPackageDecisionFromSource
-> Result<NextVendoredPackageDecisionFromSourceCompleted, VendoredPackageIdentificationHardStopped>
val apply :
DependencyIdentificationState
-> NextVendoredPackageDecisionFromSourceCompleted
-> DependencyIdentificationState
val workflow :
IdentifyNextVendoredPackageDecisionFromSource
-> Effect.Effect<Result<NextVendoredPackageDecisionFromSourceCompleted, VendoredPackageIdentificationHardStopped>>
@@ -0,0 +1,69 @@
module DependencyRecovery.SharedModel
type RunIdentity = RunIdentity of string
type SegmentId = SegmentId of string
type CandidateBoundaryId = CandidateBoundaryId of string
type PackageName = PackageName of string
type ConfidenceScore = private ConfidenceScore of int
type EvidenceReference = EvidenceReference of string
type BoundaryNote = BoundaryNote of string
type AmbiguityNote = AmbiguityNote of string
type ReplacementPlan = ReplacementPlan of string
type FallbackReference = FallbackReference of string
type Rationale = Rationale of string
type EvidenceProvenance =
| Registry
| Tarball
| Cdn
| Runtime
| Static
| Mixed
type EvidenceSignal =
| LicenseBanner
| PreservedPackageName
| SourceMapHint
| PreservedRequireString
| CharacteristicLiteralSet
| HelperSignature
| AstShapeFingerprint
| ExportSurfaceSimilarity
| DependencyGraphPosition
| ByteSimilarity
| RuntimeExecutionTrace
type CandidateBoundary = {
boundaryId: CandidateBoundaryId
segmentIds: SegmentId list
boundaryNotes: BoundaryNote list
}
type EvidenceSummary = {
signals: EvidenceSignal list
rawEvidence: EvidenceReference list
provenance: EvidenceProvenance
rationale: Rationale
}
type CandidateMatch = {
packageName: PackageName
confidenceScore: ConfidenceScore
evidence: EvidenceSummary
ambiguityNotes: AmbiguityNote list
}
type DependencyDecision =
| AcceptedDecision of CandidateBoundary * CandidateMatch * ReplacementPlan * FallbackReference
| RejectedDecision of CandidateBoundary * CandidateMatch
| UnresolvedDecision of CandidateBoundary * CandidateMatch list