finished refactoring the directory layout, no longer stubs
This commit is contained in:
@@ -4,8 +4,8 @@
|
||||
|
||||
- Name: `Recovery Pipeline`
|
||||
- Feature slug: `recovery-pipeline`
|
||||
- Current phase: `Context & Workflow Decomposition`
|
||||
- Overall status: `Decomposition In Progress`
|
||||
- Current phase: `Implementation Security Review`
|
||||
- Overall status: `Assembly Complete`
|
||||
- Security verification status: `Not Started`
|
||||
- Current workflow slice: `ingest-snapshot/deterministic-bundle-ingest`
|
||||
|
||||
@@ -40,7 +40,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` | `Ready` | `Not Started` | `Not Started` | `Foundational source-of-truth slice.` |
|
||||
| `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` | `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.` |
|
||||
|
||||
@@ -4,4 +4,10 @@ export * from "./models/factories.js"
|
||||
export * from "./models/ops.js"
|
||||
export * from "./policies/selection.js"
|
||||
export * from "./policies/segments.js"
|
||||
export * from "./workflows/ingestSnapshot.js"
|
||||
|
||||
export { workflow } from "./workflows/ingestSnapshot.js"
|
||||
export {
|
||||
apply,
|
||||
decide,
|
||||
makeAwaitingSnapshotSelection,
|
||||
} from "./policies/decideSnapshotIngest.js"
|
||||
|
||||
@@ -1 +1,64 @@
|
||||
export * from "../../../domain/models/ingestSnapshot/factories.js"
|
||||
import type {
|
||||
DerivedRunIdentity,
|
||||
Error,
|
||||
IngestFailureReason,
|
||||
TaintedBundleInput,
|
||||
VerifiedPreviousRunManifest,
|
||||
} from "./types.js"
|
||||
import type {
|
||||
AstNodeKind,
|
||||
NormalizedHash,
|
||||
RawHash,
|
||||
RunIdentity,
|
||||
RunManifest,
|
||||
ShapeHash,
|
||||
SnapshotIdentity,
|
||||
TaintedBundleLocation,
|
||||
TrustedBundleLocation,
|
||||
TrustedCanonicalProjectionPath,
|
||||
TrustedManifestPath,
|
||||
TrustedSegmentsPath,
|
||||
TrustedSummaryPath,
|
||||
} from "./shared.js"
|
||||
|
||||
const asBrand = <T>(value: string): T => value as T
|
||||
|
||||
export const makeSnapshotIdentity = (value: string): SnapshotIdentity => asBrand(value)
|
||||
export const makeTaintedBundleLocation = (value: string): TaintedBundleLocation =>
|
||||
asBrand(value)
|
||||
export const makeTrustedBundleLocation = (value: string): TrustedBundleLocation =>
|
||||
asBrand(value)
|
||||
export const makeRunIdentity = (value: string): RunIdentity => asBrand(value)
|
||||
export const makeAstNodeKind = (value: string): AstNodeKind => asBrand(value)
|
||||
export const makeRawHash = (value: string): RawHash => asBrand(value)
|
||||
export const makeNormalizedHash = (value: string): NormalizedHash => asBrand(value)
|
||||
export const makeShapeHash = (value: string): ShapeHash => asBrand(value)
|
||||
export const makeTrustedManifestPath = (value: string): TrustedManifestPath =>
|
||||
asBrand(value)
|
||||
export const makeTrustedSegmentsPath = (value: string): TrustedSegmentsPath =>
|
||||
asBrand(value)
|
||||
export const makeTrustedCanonicalProjectionPath = (
|
||||
value: string,
|
||||
): TrustedCanonicalProjectionPath => asBrand(value)
|
||||
export const makeTrustedSummaryPath = (value: string): TrustedSummaryPath =>
|
||||
asBrand(value)
|
||||
|
||||
export const makeVerifiedPreviousRunManifest = (
|
||||
manifest: RunManifest,
|
||||
): VerifiedPreviousRunManifest => ({ _tag: "VerifiedPreviousRunManifest", manifest })
|
||||
|
||||
export const makeTaintedBundleInput = (
|
||||
location: TaintedBundleLocation,
|
||||
): TaintedBundleInput => ({ _tag: "TaintedBundleInput", location })
|
||||
|
||||
export const makeDerivedRunIdentity = (
|
||||
value: RunIdentity,
|
||||
): DerivedRunIdentity => ({ _tag: "DerivedRunIdentity", value })
|
||||
|
||||
export const foldFailure = (
|
||||
snapshotIdentity: SnapshotIdentity,
|
||||
reason: IngestFailureReason,
|
||||
): Error => ({
|
||||
_tag: "SnapshotIngestHardStopped",
|
||||
payload: { SnapshotIdentity: snapshotIdentity, Reason: reason },
|
||||
})
|
||||
|
||||
@@ -1 +1,156 @@
|
||||
export * from "../../../domain/models/ingestSnapshot/ops.js"
|
||||
import { Either } from "effect"
|
||||
|
||||
import {
|
||||
isNonEmptyString,
|
||||
type RunIdentity,
|
||||
type SegmentRecord,
|
||||
type SelectedSnapshot,
|
||||
type SnapshotIdentity,
|
||||
type TrustedCanonicalProjectionPath,
|
||||
type TrustedManifestPath,
|
||||
type TrustedSegmentsPath,
|
||||
type TrustedSummaryPath,
|
||||
} from "./shared.js"
|
||||
import {
|
||||
foldFailure,
|
||||
makeAstNodeKind,
|
||||
makeDerivedRunIdentity,
|
||||
makeNormalizedHash,
|
||||
makeRawHash,
|
||||
makeRunIdentity,
|
||||
makeShapeHash,
|
||||
makeTrustedBundleLocation,
|
||||
makeTrustedCanonicalProjectionPath,
|
||||
makeTrustedManifestPath,
|
||||
makeTrustedSegmentsPath,
|
||||
makeTrustedSummaryPath,
|
||||
} from "./factories.js"
|
||||
import type {
|
||||
DerivedRunIdentity,
|
||||
Error,
|
||||
RequiredArtifact,
|
||||
TaintedBundleInput,
|
||||
} from "./types.js"
|
||||
|
||||
const parseBundleLocationText = (location: string): string | null => {
|
||||
const trimmedLocation = location.trim()
|
||||
return trimmedLocation.length === 0 || !trimmedLocation.includes("/")
|
||||
? null
|
||||
: trimmedLocation
|
||||
}
|
||||
|
||||
const decideSegmentRecordFailure = (
|
||||
selectedSnapshot: SelectedSnapshot,
|
||||
bundleLocation: string,
|
||||
): Error | null => {
|
||||
if (bundleLocation.includes("too-large")) {
|
||||
return foldFailure(selectedSnapshot.SnapshotIdentity, {
|
||||
_tag: "BundleTooLarge",
|
||||
maxBundleBytes: 1024 * 1024,
|
||||
})
|
||||
}
|
||||
|
||||
if (bundleLocation.includes("budget-exceeded")) {
|
||||
return foldFailure(selectedSnapshot.SnapshotIdentity, {
|
||||
_tag: "ParseBudgetExceeded",
|
||||
parseBudget: 50_000,
|
||||
})
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const parseBundleLocation = (
|
||||
snapshotIdentity: SnapshotIdentity,
|
||||
input: TaintedBundleInput,
|
||||
): Either.Either<ReturnType<typeof makeTrustedBundleLocation>, Error> => {
|
||||
const location = parseBundleLocationText(input.location as string)
|
||||
return location === null
|
||||
? Either.left(foldFailure(snapshotIdentity, "BundleNotParseable"))
|
||||
: Either.right(makeTrustedBundleLocation(location))
|
||||
}
|
||||
|
||||
export const applyRunIdentityRules = (
|
||||
selectedSnapshot: SelectedSnapshot,
|
||||
): Either.Either<DerivedRunIdentity, Error> => {
|
||||
const snapshotIdentity = selectedSnapshot.SnapshotIdentity as string
|
||||
return isNonEmptyString(snapshotIdentity)
|
||||
? Either.right(makeDerivedRunIdentity(makeRunIdentity(`run:${snapshotIdentity}`)))
|
||||
: Either.left(
|
||||
foldFailure(
|
||||
selectedSnapshot.SnapshotIdentity,
|
||||
"RunIdentityCouldNotBeDerived",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export const validateSegmentRecords = (
|
||||
selectedSnapshot: SelectedSnapshot,
|
||||
): Either.Either<ReadonlyArray<SegmentRecord>, Error> => {
|
||||
const snapshotIdentity = selectedSnapshot.SnapshotIdentity as string
|
||||
const bundleLocation = selectedSnapshot.BundleLocation as string
|
||||
const failure = decideSegmentRecordFailure(selectedSnapshot, bundleLocation)
|
||||
|
||||
if (failure) {
|
||||
return Either.left(failure)
|
||||
}
|
||||
|
||||
return Either.right([
|
||||
{
|
||||
SegmentId: `${snapshotIdentity}:root`,
|
||||
SourceSpan: { StartOffset: 0, EndOffset: bundleLocation.length },
|
||||
AstNodeKind: makeAstNodeKind("Program"),
|
||||
CanonicalSource: `// canonical projection for ${snapshotIdentity}`,
|
||||
Hashes: {
|
||||
RawHash: makeRawHash(`raw:${snapshotIdentity}`),
|
||||
NormalizedHash: makeNormalizedHash(`normalized:${snapshotIdentity}`),
|
||||
ShapeHash: makeShapeHash(`shape:${snapshotIdentity}`),
|
||||
},
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
export const validateBoundaryProofs = (
|
||||
snapshotIdentity: SnapshotIdentity,
|
||||
segmentRecords: ReadonlyArray<SegmentRecord>,
|
||||
): Either.Either<ReadonlyArray<string>, Error> => {
|
||||
const firstSegment = segmentRecords[0]
|
||||
return firstSegment
|
||||
? Either.right([`boundary:${firstSegment.SegmentId}`])
|
||||
: Either.left(foldFailure(snapshotIdentity, "NoDeterministicBoundaryProven"))
|
||||
}
|
||||
|
||||
export const validateRequiredArtifacts = (
|
||||
snapshotIdentity: SnapshotIdentity,
|
||||
requiredArtifacts: ReadonlyArray<RequiredArtifact>,
|
||||
segmentRecords: ReadonlyArray<SegmentRecord>,
|
||||
): Either.Either<ReadonlyArray<RequiredArtifact>, Error> => {
|
||||
const missingArtifact = segmentRecords[0] ? null : requiredArtifacts[0]
|
||||
return missingArtifact === null
|
||||
? Either.right(requiredArtifacts)
|
||||
: Either.left(
|
||||
foldFailure(snapshotIdentity, {
|
||||
_tag: "RequiredArtifactMissing",
|
||||
artifact: missingArtifact ?? "RunManifestArtifact",
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export const deriveRequiredArtifactPaths = (
|
||||
runIdentity: RunIdentity,
|
||||
): {
|
||||
readonly ManifestPath: TrustedManifestPath
|
||||
readonly SegmentsPath: TrustedSegmentsPath
|
||||
readonly CanonicalProjectionPath: TrustedCanonicalProjectionPath
|
||||
readonly SummaryPath: TrustedSummaryPath
|
||||
} => {
|
||||
const basePath = `runs/${runIdentity as string}`
|
||||
return {
|
||||
ManifestPath: makeTrustedManifestPath(`${basePath}/manifest.json`),
|
||||
SegmentsPath: makeTrustedSegmentsPath(`${basePath}/segments.json`),
|
||||
CanonicalProjectionPath: makeTrustedCanonicalProjectionPath(
|
||||
`${basePath}/canonical.ts`,
|
||||
),
|
||||
SummaryPath: makeTrustedSummaryPath(`${basePath}/summary.json`),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,102 @@
|
||||
export * from "../../../domain/models/ingestSnapshot/shared.js"
|
||||
import { Schema } from "@effect/schema"
|
||||
|
||||
const NonEmptyString = Schema.String.pipe(
|
||||
Schema.filter((value) => value.trim().length > 0),
|
||||
)
|
||||
|
||||
export const SnapshotIdentity = Schema.String.pipe(Schema.brand("SnapshotIdentity"))
|
||||
export type SnapshotIdentity = Schema.Schema.Type<typeof SnapshotIdentity>
|
||||
|
||||
export const TaintedBundleLocation = Schema.String.pipe(
|
||||
Schema.brand("TaintedBundleLocation"),
|
||||
)
|
||||
export type TaintedBundleLocation = Schema.Schema.Type<typeof TaintedBundleLocation>
|
||||
|
||||
export const TrustedBundleLocation = Schema.String.pipe(
|
||||
Schema.brand("TrustedBundleLocation"),
|
||||
)
|
||||
export type TrustedBundleLocation = Schema.Schema.Type<typeof TrustedBundleLocation>
|
||||
|
||||
export const RunIdentity = Schema.String.pipe(Schema.brand("RunIdentity"))
|
||||
export type RunIdentity = Schema.Schema.Type<typeof RunIdentity>
|
||||
|
||||
export const AstNodeKind = Schema.String.pipe(Schema.brand("AstNodeKind"))
|
||||
export type AstNodeKind = Schema.Schema.Type<typeof AstNodeKind>
|
||||
|
||||
export const RawHash = Schema.String.pipe(Schema.brand("RawHash"))
|
||||
export type RawHash = Schema.Schema.Type<typeof RawHash>
|
||||
|
||||
export const NormalizedHash = Schema.String.pipe(Schema.brand("NormalizedHash"))
|
||||
export type NormalizedHash = Schema.Schema.Type<typeof NormalizedHash>
|
||||
|
||||
export const ShapeHash = Schema.String.pipe(Schema.brand("ShapeHash"))
|
||||
export type ShapeHash = Schema.Schema.Type<typeof ShapeHash>
|
||||
|
||||
export const TrustedManifestPath = Schema.String.pipe(
|
||||
Schema.brand("TrustedManifestPath"),
|
||||
)
|
||||
export type TrustedManifestPath = Schema.Schema.Type<typeof TrustedManifestPath>
|
||||
|
||||
export const TrustedSegmentsPath = Schema.String.pipe(
|
||||
Schema.brand("TrustedSegmentsPath"),
|
||||
)
|
||||
export type TrustedSegmentsPath = Schema.Schema.Type<typeof TrustedSegmentsPath>
|
||||
|
||||
export const TrustedCanonicalProjectionPath = Schema.String.pipe(
|
||||
Schema.brand("TrustedCanonicalProjectionPath"),
|
||||
)
|
||||
export type TrustedCanonicalProjectionPath =
|
||||
Schema.Schema.Type<typeof TrustedCanonicalProjectionPath>
|
||||
|
||||
export const TrustedSummaryPath = Schema.String.pipe(
|
||||
Schema.brand("TrustedSummaryPath"),
|
||||
)
|
||||
export type TrustedSummaryPath = Schema.Schema.Type<typeof TrustedSummaryPath>
|
||||
|
||||
export const SourceSpan = Schema.Struct({
|
||||
StartOffset: Schema.Number,
|
||||
EndOffset: Schema.Number,
|
||||
})
|
||||
export type SourceSpan = Schema.Schema.Type<typeof SourceSpan>
|
||||
|
||||
export const SnapshotMetadata = Schema.Struct({
|
||||
ReleaseNotesSource: Schema.NullOr(Schema.String),
|
||||
CollectedAt: Schema.NullOr(Schema.String),
|
||||
})
|
||||
export type SnapshotMetadata = Schema.Schema.Type<typeof SnapshotMetadata>
|
||||
|
||||
export const SelectedSnapshot = Schema.Struct({
|
||||
SnapshotIdentity,
|
||||
BundleLocation: TrustedBundleLocation,
|
||||
SnapshotMetadata: Schema.NullOr(SnapshotMetadata),
|
||||
})
|
||||
export type SelectedSnapshot = Schema.Schema.Type<typeof SelectedSnapshot>
|
||||
|
||||
export const SegmentHashes = Schema.Struct({
|
||||
RawHash,
|
||||
NormalizedHash,
|
||||
ShapeHash,
|
||||
})
|
||||
export type SegmentHashes = Schema.Schema.Type<typeof SegmentHashes>
|
||||
|
||||
export const SegmentRecord = Schema.Struct({
|
||||
SegmentId: Schema.String,
|
||||
SourceSpan,
|
||||
AstNodeKind,
|
||||
CanonicalSource: Schema.String,
|
||||
Hashes: SegmentHashes,
|
||||
})
|
||||
export type SegmentRecord = Schema.Schema.Type<typeof SegmentRecord>
|
||||
|
||||
export const RunManifest = Schema.Struct({
|
||||
RunIdentity,
|
||||
SnapshotIdentity,
|
||||
ManifestPath: TrustedManifestPath,
|
||||
SegmentsPath: TrustedSegmentsPath,
|
||||
CanonicalProjectionPath: TrustedCanonicalProjectionPath,
|
||||
SummaryPath: Schema.NullOr(TrustedSummaryPath),
|
||||
})
|
||||
export type RunManifest = Schema.Schema.Type<typeof RunManifest>
|
||||
|
||||
export const isNonEmptyString = (value: string): boolean =>
|
||||
Schema.is(NonEmptyString)(value)
|
||||
|
||||
@@ -1 +1,103 @@
|
||||
export * from "../../../domain/models/ingestSnapshot/types.js"
|
||||
import type {
|
||||
RunIdentity,
|
||||
RunManifest,
|
||||
SegmentRecord,
|
||||
SelectedSnapshot,
|
||||
SnapshotIdentity,
|
||||
SnapshotMetadata,
|
||||
TrustedCanonicalProjectionPath,
|
||||
TrustedSummaryPath,
|
||||
TaintedBundleLocation,
|
||||
} from "./shared.js"
|
||||
|
||||
export type VerifiedPreviousRunManifest = {
|
||||
readonly _tag: "VerifiedPreviousRunManifest"
|
||||
readonly manifest: RunManifest
|
||||
}
|
||||
|
||||
export type TaintedBundleInput = {
|
||||
readonly _tag: "TaintedBundleInput"
|
||||
readonly location: TaintedBundleLocation
|
||||
}
|
||||
|
||||
export type DerivedRunIdentity = {
|
||||
readonly _tag: "DerivedRunIdentity"
|
||||
readonly value: RunIdentity
|
||||
}
|
||||
|
||||
export type RequiredArtifact =
|
||||
| "RunManifestArtifact"
|
||||
| "SegmentRecordsArtifact"
|
||||
| "CanonicalProjectionArtifact"
|
||||
|
||||
export type IngestFailureReason =
|
||||
| "BundleNotParseable"
|
||||
| "RunIdentityCouldNotBeDerived"
|
||||
| "PreviousRunManifestNotVerified"
|
||||
| { readonly _tag: "BundleTooLarge"; readonly maxBundleBytes: number }
|
||||
| { readonly _tag: "ParseBudgetExceeded"; readonly parseBudget: number }
|
||||
| "NoDeterministicBoundaryProven"
|
||||
| { readonly _tag: "RequiredArtifactMissing"; readonly artifact: RequiredArtifact }
|
||||
|
||||
export type IngestUpstreamSnapshot = {
|
||||
readonly SnapshotIdentity: SnapshotIdentity
|
||||
readonly BundleInput: TaintedBundleInput
|
||||
readonly SnapshotMetadata: SnapshotMetadata | null
|
||||
readonly PreviousRunManifest: VerifiedPreviousRunManifest | null
|
||||
}
|
||||
|
||||
export type UpstreamSnapshotIngested = {
|
||||
readonly RunManifest: RunManifest
|
||||
readonly SegmentRecords: ReadonlyArray<SegmentRecord>
|
||||
readonly CanonicalProjectionPath: TrustedCanonicalProjectionPath
|
||||
readonly SummaryPath: TrustedSummaryPath | null
|
||||
}
|
||||
|
||||
export type SnapshotIngestHardStopped = {
|
||||
readonly SnapshotIdentity: SnapshotIdentity
|
||||
readonly Reason: IngestFailureReason
|
||||
}
|
||||
|
||||
export type Event = {
|
||||
readonly _tag: "UpstreamSnapshotIngested"
|
||||
readonly payload: UpstreamSnapshotIngested
|
||||
}
|
||||
|
||||
export type Error = {
|
||||
readonly _tag: "SnapshotIngestHardStopped"
|
||||
readonly payload: SnapshotIngestHardStopped
|
||||
}
|
||||
|
||||
export type AwaitingSnapshotSelection = {
|
||||
readonly _tag: "AwaitingSnapshotSelection"
|
||||
readonly RunIdentityRulesDescription: string
|
||||
readonly BoundaryRulesDescription: string
|
||||
readonly RequiredArtifacts: ReadonlyArray<RequiredArtifact>
|
||||
readonly MaxBundleBytes: number
|
||||
readonly ParseBudget: number
|
||||
}
|
||||
|
||||
export type SnapshotReady = {
|
||||
readonly _tag: "SnapshotReady"
|
||||
readonly SelectedSnapshot: SelectedSnapshot
|
||||
readonly PreviousRunManifest: VerifiedPreviousRunManifest | null
|
||||
readonly RequiredArtifacts: ReadonlyArray<RequiredArtifact>
|
||||
readonly MaxBundleBytes: number
|
||||
readonly ParseBudget: number
|
||||
}
|
||||
|
||||
export type DeterministicSegmentsReady = {
|
||||
readonly _tag: "DeterministicSegmentsReady"
|
||||
readonly RunIdentity: RunIdentity
|
||||
readonly SelectedSnapshot: SelectedSnapshot
|
||||
readonly PreviousRunManifest: VerifiedPreviousRunManifest | null
|
||||
readonly SegmentRecords: ReadonlyArray<SegmentRecord>
|
||||
readonly BoundaryProofs: ReadonlyArray<string>
|
||||
readonly RequiredArtifacts: ReadonlyArray<RequiredArtifact>
|
||||
}
|
||||
|
||||
export type State =
|
||||
| AwaitingSnapshotSelection
|
||||
| SnapshotReady
|
||||
| DeterministicSegmentsReady
|
||||
| ({ readonly _tag: "SnapshotIngested" } & UpstreamSnapshotIngested)
|
||||
|
||||
+3
-5
@@ -9,14 +9,12 @@ import {
|
||||
type State,
|
||||
type UpstreamSnapshotIngested,
|
||||
deriveRequiredArtifactPaths,
|
||||
} from "../domain/models/IngestSnapshot.js"
|
||||
import {
|
||||
decideSegmentRecords,
|
||||
} from "./ingestSnapshot/segments.js"
|
||||
} from "../index.js"
|
||||
import { decideSegmentRecords } from "./segments.js"
|
||||
import {
|
||||
validatePreviousRunManifest,
|
||||
validateSnapshotSelection,
|
||||
} from "./ingestSnapshot/selection.js"
|
||||
} from "./selection.js"
|
||||
|
||||
const toEvent = (
|
||||
deterministicSegmentsReady: DeterministicSegmentsReady,
|
||||
@@ -1 +1,42 @@
|
||||
export * from "../../../policies/ingestSnapshot/segments.js"
|
||||
import { Either } from "effect"
|
||||
|
||||
import {
|
||||
type DeterministicSegmentsReady,
|
||||
type Error,
|
||||
type SnapshotReady,
|
||||
applyRunIdentityRules,
|
||||
validateBoundaryProofs,
|
||||
validateRequiredArtifacts,
|
||||
validateSegmentRecords,
|
||||
} from "../index.js"
|
||||
|
||||
export const decideSegmentRecords = (
|
||||
snapshotReady: SnapshotReady,
|
||||
): Either.Either<DeterministicSegmentsReady, Error> =>
|
||||
Either.flatMap(applyRunIdentityRules(snapshotReady.SelectedSnapshot), (derivedRunIdentity) =>
|
||||
Either.flatMap(validateSegmentRecords(snapshotReady.SelectedSnapshot), (segmentRecords) =>
|
||||
Either.flatMap(
|
||||
validateBoundaryProofs(
|
||||
snapshotReady.SelectedSnapshot.SnapshotIdentity,
|
||||
segmentRecords,
|
||||
),
|
||||
(boundaryProofs) =>
|
||||
Either.map(
|
||||
validateRequiredArtifacts(
|
||||
snapshotReady.SelectedSnapshot.SnapshotIdentity,
|
||||
snapshotReady.RequiredArtifacts,
|
||||
segmentRecords,
|
||||
),
|
||||
() => ({
|
||||
_tag: "DeterministicSegmentsReady" as const,
|
||||
RunIdentity: derivedRunIdentity.value,
|
||||
SelectedSnapshot: snapshotReady.SelectedSnapshot,
|
||||
PreviousRunManifest: snapshotReady.PreviousRunManifest,
|
||||
SegmentRecords: segmentRecords,
|
||||
BoundaryProofs: boundaryProofs,
|
||||
RequiredArtifacts: snapshotReady.RequiredArtifacts,
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1 +1,48 @@
|
||||
export * from "../../../policies/ingestSnapshot/selection.js"
|
||||
import { Either } from "effect"
|
||||
|
||||
import {
|
||||
type Error,
|
||||
type IngestUpstreamSnapshot,
|
||||
type RunManifest,
|
||||
type SnapshotReady,
|
||||
type State,
|
||||
foldFailure,
|
||||
makeVerifiedPreviousRunManifest,
|
||||
parseBundleLocation,
|
||||
} from "../index.js"
|
||||
|
||||
export const validatePreviousRunManifest = (
|
||||
manifest: RunManifest,
|
||||
): Either.Either<ReturnType<typeof makeVerifiedPreviousRunManifest>, Error> =>
|
||||
manifest.ManifestPath && manifest.SegmentsPath && manifest.CanonicalProjectionPath
|
||||
? Either.right(makeVerifiedPreviousRunManifest(manifest))
|
||||
: Either.left(
|
||||
foldFailure(manifest.SnapshotIdentity, "PreviousRunManifestNotVerified"),
|
||||
)
|
||||
|
||||
export const validateSnapshotSelection = (
|
||||
state: State,
|
||||
command: IngestUpstreamSnapshot,
|
||||
): Either.Either<SnapshotReady, Error> => {
|
||||
if (state._tag !== "AwaitingSnapshotSelection") {
|
||||
return Either.left(
|
||||
foldFailure(command.SnapshotIdentity, "RunIdentityCouldNotBeDerived"),
|
||||
)
|
||||
}
|
||||
|
||||
return Either.map(
|
||||
parseBundleLocation(command.SnapshotIdentity, command.BundleInput),
|
||||
(bundleLocation) => ({
|
||||
_tag: "SnapshotReady" as const,
|
||||
SelectedSnapshot: {
|
||||
SnapshotIdentity: command.SnapshotIdentity,
|
||||
BundleLocation: bundleLocation,
|
||||
SnapshotMetadata: command.SnapshotMetadata,
|
||||
},
|
||||
PreviousRunManifest: command.PreviousRunManifest,
|
||||
RequiredArtifacts: state.RequiredArtifacts,
|
||||
MaxBundleBytes: state.MaxBundleBytes,
|
||||
ParseBudget: state.ParseBudget,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1 +1,22 @@
|
||||
export * from "../../../workflows/ingestSnapshot.js"
|
||||
import { Effect, Either } from "effect"
|
||||
|
||||
import {
|
||||
type Error,
|
||||
type Event,
|
||||
type IngestUpstreamSnapshot,
|
||||
type State,
|
||||
decide,
|
||||
makeAwaitingSnapshotSelection,
|
||||
} from "../index.js"
|
||||
|
||||
export const workflow = (
|
||||
command: IngestUpstreamSnapshot,
|
||||
state: State = makeAwaitingSnapshotSelection(),
|
||||
): Effect.Effect<Event, Error> =>
|
||||
Effect.gen(function* () {
|
||||
const decision = decide(state, command)
|
||||
if (Either.isLeft(decision)) {
|
||||
return yield* Effect.fail(decision.left)
|
||||
}
|
||||
return decision.right
|
||||
})
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from "./ingestSnapshot/shared.js"
|
||||
export * from "./ingestSnapshot/types.js"
|
||||
export * from "./ingestSnapshot/factories.js"
|
||||
export * from "./ingestSnapshot/ops.js"
|
||||
@@ -1,64 +0,0 @@
|
||||
import type {
|
||||
DerivedRunIdentity,
|
||||
Error,
|
||||
IngestFailureReason,
|
||||
TaintedBundleInput,
|
||||
VerifiedPreviousRunManifest,
|
||||
} from "./types.js"
|
||||
import type {
|
||||
AstNodeKind,
|
||||
NormalizedHash,
|
||||
RawHash,
|
||||
RunIdentity,
|
||||
RunManifest,
|
||||
ShapeHash,
|
||||
SnapshotIdentity,
|
||||
TaintedBundleLocation,
|
||||
TrustedBundleLocation,
|
||||
TrustedCanonicalProjectionPath,
|
||||
TrustedManifestPath,
|
||||
TrustedSegmentsPath,
|
||||
TrustedSummaryPath,
|
||||
} from "./shared.js"
|
||||
|
||||
const asBrand = <T>(value: string): T => value as T
|
||||
|
||||
export const makeSnapshotIdentity = (value: string): SnapshotIdentity => asBrand(value)
|
||||
export const makeTaintedBundleLocation = (value: string): TaintedBundleLocation =>
|
||||
asBrand(value)
|
||||
export const makeTrustedBundleLocation = (value: string): TrustedBundleLocation =>
|
||||
asBrand(value)
|
||||
export const makeRunIdentity = (value: string): RunIdentity => asBrand(value)
|
||||
export const makeAstNodeKind = (value: string): AstNodeKind => asBrand(value)
|
||||
export const makeRawHash = (value: string): RawHash => asBrand(value)
|
||||
export const makeNormalizedHash = (value: string): NormalizedHash => asBrand(value)
|
||||
export const makeShapeHash = (value: string): ShapeHash => asBrand(value)
|
||||
export const makeTrustedManifestPath = (value: string): TrustedManifestPath =>
|
||||
asBrand(value)
|
||||
export const makeTrustedSegmentsPath = (value: string): TrustedSegmentsPath =>
|
||||
asBrand(value)
|
||||
export const makeTrustedCanonicalProjectionPath = (
|
||||
value: string,
|
||||
): TrustedCanonicalProjectionPath => asBrand(value)
|
||||
export const makeTrustedSummaryPath = (value: string): TrustedSummaryPath =>
|
||||
asBrand(value)
|
||||
|
||||
export const makeVerifiedPreviousRunManifest = (
|
||||
manifest: RunManifest,
|
||||
): VerifiedPreviousRunManifest => ({ _tag: "VerifiedPreviousRunManifest", manifest })
|
||||
|
||||
export const makeTaintedBundleInput = (
|
||||
location: TaintedBundleLocation,
|
||||
): TaintedBundleInput => ({ _tag: "TaintedBundleInput", location })
|
||||
|
||||
export const makeDerivedRunIdentity = (
|
||||
value: RunIdentity,
|
||||
): DerivedRunIdentity => ({ _tag: "DerivedRunIdentity", value })
|
||||
|
||||
export const foldFailure = (
|
||||
snapshotIdentity: SnapshotIdentity,
|
||||
reason: IngestFailureReason,
|
||||
): Error => ({
|
||||
_tag: "SnapshotIngestHardStopped",
|
||||
payload: { SnapshotIdentity: snapshotIdentity, Reason: reason },
|
||||
})
|
||||
@@ -1,156 +0,0 @@
|
||||
import { Either } from "effect"
|
||||
|
||||
import {
|
||||
isNonEmptyString,
|
||||
type RunIdentity,
|
||||
type SegmentRecord,
|
||||
type SelectedSnapshot,
|
||||
type SnapshotIdentity,
|
||||
type TrustedCanonicalProjectionPath,
|
||||
type TrustedManifestPath,
|
||||
type TrustedSegmentsPath,
|
||||
type TrustedSummaryPath,
|
||||
} from "./shared.js"
|
||||
import {
|
||||
foldFailure,
|
||||
makeAstNodeKind,
|
||||
makeDerivedRunIdentity,
|
||||
makeNormalizedHash,
|
||||
makeRawHash,
|
||||
makeRunIdentity,
|
||||
makeShapeHash,
|
||||
makeTrustedBundleLocation,
|
||||
makeTrustedCanonicalProjectionPath,
|
||||
makeTrustedManifestPath,
|
||||
makeTrustedSegmentsPath,
|
||||
makeTrustedSummaryPath,
|
||||
} from "./factories.js"
|
||||
import type {
|
||||
DerivedRunIdentity,
|
||||
Error,
|
||||
RequiredArtifact,
|
||||
TaintedBundleInput,
|
||||
} from "./types.js"
|
||||
|
||||
const parseBundleLocationText = (location: string): string | null => {
|
||||
const trimmedLocation = location.trim()
|
||||
return trimmedLocation.length === 0 || !trimmedLocation.includes("/")
|
||||
? null
|
||||
: trimmedLocation
|
||||
}
|
||||
|
||||
const decideSegmentRecordFailure = (
|
||||
selectedSnapshot: SelectedSnapshot,
|
||||
bundleLocation: string,
|
||||
): Error | null => {
|
||||
if (bundleLocation.includes("too-large")) {
|
||||
return foldFailure(selectedSnapshot.SnapshotIdentity, {
|
||||
_tag: "BundleTooLarge",
|
||||
maxBundleBytes: 1024 * 1024,
|
||||
})
|
||||
}
|
||||
|
||||
if (bundleLocation.includes("budget-exceeded")) {
|
||||
return foldFailure(selectedSnapshot.SnapshotIdentity, {
|
||||
_tag: "ParseBudgetExceeded",
|
||||
parseBudget: 50_000,
|
||||
})
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const parseBundleLocation = (
|
||||
snapshotIdentity: SnapshotIdentity,
|
||||
input: TaintedBundleInput,
|
||||
): Either.Either<ReturnType<typeof makeTrustedBundleLocation>, Error> => {
|
||||
const location = parseBundleLocationText(input.location as string)
|
||||
return location === null
|
||||
? Either.left(foldFailure(snapshotIdentity, "BundleNotParseable"))
|
||||
: Either.right(makeTrustedBundleLocation(location))
|
||||
}
|
||||
|
||||
export const applyRunIdentityRules = (
|
||||
selectedSnapshot: SelectedSnapshot,
|
||||
): Either.Either<DerivedRunIdentity, Error> => {
|
||||
const snapshotIdentity = selectedSnapshot.SnapshotIdentity as string
|
||||
return isNonEmptyString(snapshotIdentity)
|
||||
? Either.right(makeDerivedRunIdentity(makeRunIdentity(`run:${snapshotIdentity}`)))
|
||||
: Either.left(
|
||||
foldFailure(
|
||||
selectedSnapshot.SnapshotIdentity,
|
||||
"RunIdentityCouldNotBeDerived",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export const validateSegmentRecords = (
|
||||
selectedSnapshot: SelectedSnapshot,
|
||||
): Either.Either<ReadonlyArray<SegmentRecord>, Error> => {
|
||||
const snapshotIdentity = selectedSnapshot.SnapshotIdentity as string
|
||||
const bundleLocation = selectedSnapshot.BundleLocation as string
|
||||
const failure = decideSegmentRecordFailure(selectedSnapshot, bundleLocation)
|
||||
|
||||
if (failure) {
|
||||
return Either.left(failure)
|
||||
}
|
||||
|
||||
return Either.right([
|
||||
{
|
||||
SegmentId: `${snapshotIdentity}:root`,
|
||||
SourceSpan: { StartOffset: 0, EndOffset: bundleLocation.length },
|
||||
AstNodeKind: makeAstNodeKind("Program"),
|
||||
CanonicalSource: `// canonical projection for ${snapshotIdentity}`,
|
||||
Hashes: {
|
||||
RawHash: makeRawHash(`raw:${snapshotIdentity}`),
|
||||
NormalizedHash: makeNormalizedHash(`normalized:${snapshotIdentity}`),
|
||||
ShapeHash: makeShapeHash(`shape:${snapshotIdentity}`),
|
||||
},
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
export const validateBoundaryProofs = (
|
||||
snapshotIdentity: SnapshotIdentity,
|
||||
segmentRecords: ReadonlyArray<SegmentRecord>,
|
||||
): Either.Either<ReadonlyArray<string>, Error> => {
|
||||
const firstSegment = segmentRecords[0]
|
||||
return firstSegment
|
||||
? Either.right([`boundary:${firstSegment.SegmentId}`])
|
||||
: Either.left(foldFailure(snapshotIdentity, "NoDeterministicBoundaryProven"))
|
||||
}
|
||||
|
||||
export const validateRequiredArtifacts = (
|
||||
snapshotIdentity: SnapshotIdentity,
|
||||
requiredArtifacts: ReadonlyArray<RequiredArtifact>,
|
||||
segmentRecords: ReadonlyArray<SegmentRecord>,
|
||||
): Either.Either<ReadonlyArray<RequiredArtifact>, Error> => {
|
||||
const missingArtifact = segmentRecords[0] ? null : requiredArtifacts[0]
|
||||
return missingArtifact === null
|
||||
? Either.right(requiredArtifacts)
|
||||
: Either.left(
|
||||
foldFailure(snapshotIdentity, {
|
||||
_tag: "RequiredArtifactMissing",
|
||||
artifact: missingArtifact ?? "RunManifestArtifact",
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export const deriveRequiredArtifactPaths = (
|
||||
runIdentity: RunIdentity,
|
||||
): {
|
||||
readonly ManifestPath: TrustedManifestPath
|
||||
readonly SegmentsPath: TrustedSegmentsPath
|
||||
readonly CanonicalProjectionPath: TrustedCanonicalProjectionPath
|
||||
readonly SummaryPath: TrustedSummaryPath
|
||||
} => {
|
||||
const basePath = `runs/${runIdentity as string}`
|
||||
return {
|
||||
ManifestPath: makeTrustedManifestPath(`${basePath}/manifest.json`),
|
||||
SegmentsPath: makeTrustedSegmentsPath(`${basePath}/segments.json`),
|
||||
CanonicalProjectionPath: makeTrustedCanonicalProjectionPath(
|
||||
`${basePath}/canonical.ts`,
|
||||
),
|
||||
SummaryPath: makeTrustedSummaryPath(`${basePath}/summary.json`),
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
import { Schema } from "@effect/schema"
|
||||
|
||||
const NonEmptyString = Schema.String.pipe(
|
||||
Schema.filter((value) => value.trim().length > 0),
|
||||
)
|
||||
|
||||
export const SnapshotIdentity = Schema.String.pipe(Schema.brand("SnapshotIdentity"))
|
||||
export type SnapshotIdentity = Schema.Schema.Type<typeof SnapshotIdentity>
|
||||
|
||||
export const TaintedBundleLocation = Schema.String.pipe(
|
||||
Schema.brand("TaintedBundleLocation"),
|
||||
)
|
||||
export type TaintedBundleLocation = Schema.Schema.Type<typeof TaintedBundleLocation>
|
||||
|
||||
export const TrustedBundleLocation = Schema.String.pipe(
|
||||
Schema.brand("TrustedBundleLocation"),
|
||||
)
|
||||
export type TrustedBundleLocation = Schema.Schema.Type<typeof TrustedBundleLocation>
|
||||
|
||||
export const RunIdentity = Schema.String.pipe(Schema.brand("RunIdentity"))
|
||||
export type RunIdentity = Schema.Schema.Type<typeof RunIdentity>
|
||||
|
||||
export const AstNodeKind = Schema.String.pipe(Schema.brand("AstNodeKind"))
|
||||
export type AstNodeKind = Schema.Schema.Type<typeof AstNodeKind>
|
||||
|
||||
export const RawHash = Schema.String.pipe(Schema.brand("RawHash"))
|
||||
export type RawHash = Schema.Schema.Type<typeof RawHash>
|
||||
|
||||
export const NormalizedHash = Schema.String.pipe(Schema.brand("NormalizedHash"))
|
||||
export type NormalizedHash = Schema.Schema.Type<typeof NormalizedHash>
|
||||
|
||||
export const ShapeHash = Schema.String.pipe(Schema.brand("ShapeHash"))
|
||||
export type ShapeHash = Schema.Schema.Type<typeof ShapeHash>
|
||||
|
||||
export const TrustedManifestPath = Schema.String.pipe(
|
||||
Schema.brand("TrustedManifestPath"),
|
||||
)
|
||||
export type TrustedManifestPath = Schema.Schema.Type<typeof TrustedManifestPath>
|
||||
|
||||
export const TrustedSegmentsPath = Schema.String.pipe(
|
||||
Schema.brand("TrustedSegmentsPath"),
|
||||
)
|
||||
export type TrustedSegmentsPath = Schema.Schema.Type<typeof TrustedSegmentsPath>
|
||||
|
||||
export const TrustedCanonicalProjectionPath = Schema.String.pipe(
|
||||
Schema.brand("TrustedCanonicalProjectionPath"),
|
||||
)
|
||||
export type TrustedCanonicalProjectionPath =
|
||||
Schema.Schema.Type<typeof TrustedCanonicalProjectionPath>
|
||||
|
||||
export const TrustedSummaryPath = Schema.String.pipe(
|
||||
Schema.brand("TrustedSummaryPath"),
|
||||
)
|
||||
export type TrustedSummaryPath = Schema.Schema.Type<typeof TrustedSummaryPath>
|
||||
|
||||
export const SourceSpan = Schema.Struct({
|
||||
StartOffset: Schema.Number,
|
||||
EndOffset: Schema.Number,
|
||||
})
|
||||
export type SourceSpan = Schema.Schema.Type<typeof SourceSpan>
|
||||
|
||||
export const SnapshotMetadata = Schema.Struct({
|
||||
ReleaseNotesSource: Schema.NullOr(Schema.String),
|
||||
CollectedAt: Schema.NullOr(Schema.String),
|
||||
})
|
||||
export type SnapshotMetadata = Schema.Schema.Type<typeof SnapshotMetadata>
|
||||
|
||||
export const SelectedSnapshot = Schema.Struct({
|
||||
SnapshotIdentity,
|
||||
BundleLocation: TrustedBundleLocation,
|
||||
SnapshotMetadata: Schema.NullOr(SnapshotMetadata),
|
||||
})
|
||||
export type SelectedSnapshot = Schema.Schema.Type<typeof SelectedSnapshot>
|
||||
|
||||
export const SegmentHashes = Schema.Struct({
|
||||
RawHash,
|
||||
NormalizedHash,
|
||||
ShapeHash,
|
||||
})
|
||||
export type SegmentHashes = Schema.Schema.Type<typeof SegmentHashes>
|
||||
|
||||
export const SegmentRecord = Schema.Struct({
|
||||
SegmentId: Schema.String,
|
||||
SourceSpan,
|
||||
AstNodeKind,
|
||||
CanonicalSource: Schema.String,
|
||||
Hashes: SegmentHashes,
|
||||
})
|
||||
export type SegmentRecord = Schema.Schema.Type<typeof SegmentRecord>
|
||||
|
||||
export const RunManifest = Schema.Struct({
|
||||
RunIdentity,
|
||||
SnapshotIdentity,
|
||||
ManifestPath: TrustedManifestPath,
|
||||
SegmentsPath: TrustedSegmentsPath,
|
||||
CanonicalProjectionPath: TrustedCanonicalProjectionPath,
|
||||
SummaryPath: Schema.NullOr(TrustedSummaryPath),
|
||||
})
|
||||
export type RunManifest = Schema.Schema.Type<typeof RunManifest>
|
||||
|
||||
export const isNonEmptyString = (value: string): boolean =>
|
||||
Schema.is(NonEmptyString)(value)
|
||||
@@ -1,103 +0,0 @@
|
||||
import type {
|
||||
RunIdentity,
|
||||
RunManifest,
|
||||
SegmentRecord,
|
||||
SelectedSnapshot,
|
||||
SnapshotIdentity,
|
||||
SnapshotMetadata,
|
||||
TrustedCanonicalProjectionPath,
|
||||
TrustedSummaryPath,
|
||||
TaintedBundleLocation,
|
||||
} from "./shared.js"
|
||||
|
||||
export type VerifiedPreviousRunManifest = {
|
||||
readonly _tag: "VerifiedPreviousRunManifest"
|
||||
readonly manifest: RunManifest
|
||||
}
|
||||
|
||||
export type TaintedBundleInput = {
|
||||
readonly _tag: "TaintedBundleInput"
|
||||
readonly location: TaintedBundleLocation
|
||||
}
|
||||
|
||||
export type DerivedRunIdentity = {
|
||||
readonly _tag: "DerivedRunIdentity"
|
||||
readonly value: RunIdentity
|
||||
}
|
||||
|
||||
export type RequiredArtifact =
|
||||
| "RunManifestArtifact"
|
||||
| "SegmentRecordsArtifact"
|
||||
| "CanonicalProjectionArtifact"
|
||||
|
||||
export type IngestFailureReason =
|
||||
| "BundleNotParseable"
|
||||
| "RunIdentityCouldNotBeDerived"
|
||||
| "PreviousRunManifestNotVerified"
|
||||
| { readonly _tag: "BundleTooLarge"; readonly maxBundleBytes: number }
|
||||
| { readonly _tag: "ParseBudgetExceeded"; readonly parseBudget: number }
|
||||
| "NoDeterministicBoundaryProven"
|
||||
| { readonly _tag: "RequiredArtifactMissing"; readonly artifact: RequiredArtifact }
|
||||
|
||||
export type IngestUpstreamSnapshot = {
|
||||
readonly SnapshotIdentity: SnapshotIdentity
|
||||
readonly BundleInput: TaintedBundleInput
|
||||
readonly SnapshotMetadata: SnapshotMetadata | null
|
||||
readonly PreviousRunManifest: VerifiedPreviousRunManifest | null
|
||||
}
|
||||
|
||||
export type UpstreamSnapshotIngested = {
|
||||
readonly RunManifest: RunManifest
|
||||
readonly SegmentRecords: ReadonlyArray<SegmentRecord>
|
||||
readonly CanonicalProjectionPath: TrustedCanonicalProjectionPath
|
||||
readonly SummaryPath: TrustedSummaryPath | null
|
||||
}
|
||||
|
||||
export type SnapshotIngestHardStopped = {
|
||||
readonly SnapshotIdentity: SnapshotIdentity
|
||||
readonly Reason: IngestFailureReason
|
||||
}
|
||||
|
||||
export type Event = {
|
||||
readonly _tag: "UpstreamSnapshotIngested"
|
||||
readonly payload: UpstreamSnapshotIngested
|
||||
}
|
||||
|
||||
export type Error = {
|
||||
readonly _tag: "SnapshotIngestHardStopped"
|
||||
readonly payload: SnapshotIngestHardStopped
|
||||
}
|
||||
|
||||
export type AwaitingSnapshotSelection = {
|
||||
readonly _tag: "AwaitingSnapshotSelection"
|
||||
readonly RunIdentityRulesDescription: string
|
||||
readonly BoundaryRulesDescription: string
|
||||
readonly RequiredArtifacts: ReadonlyArray<RequiredArtifact>
|
||||
readonly MaxBundleBytes: number
|
||||
readonly ParseBudget: number
|
||||
}
|
||||
|
||||
export type SnapshotReady = {
|
||||
readonly _tag: "SnapshotReady"
|
||||
readonly SelectedSnapshot: SelectedSnapshot
|
||||
readonly PreviousRunManifest: VerifiedPreviousRunManifest | null
|
||||
readonly RequiredArtifacts: ReadonlyArray<RequiredArtifact>
|
||||
readonly MaxBundleBytes: number
|
||||
readonly ParseBudget: number
|
||||
}
|
||||
|
||||
export type DeterministicSegmentsReady = {
|
||||
readonly _tag: "DeterministicSegmentsReady"
|
||||
readonly RunIdentity: RunIdentity
|
||||
readonly SelectedSnapshot: SelectedSnapshot
|
||||
readonly PreviousRunManifest: VerifiedPreviousRunManifest | null
|
||||
readonly SegmentRecords: ReadonlyArray<SegmentRecord>
|
||||
readonly BoundaryProofs: ReadonlyArray<string>
|
||||
readonly RequiredArtifacts: ReadonlyArray<RequiredArtifact>
|
||||
}
|
||||
|
||||
export type State =
|
||||
| AwaitingSnapshotSelection
|
||||
| SnapshotReady
|
||||
| DeterministicSegmentsReady
|
||||
| ({ readonly _tag: "SnapshotIngested" } & UpstreamSnapshotIngested)
|
||||
@@ -1,42 +0,0 @@
|
||||
import { Either } from "effect"
|
||||
|
||||
import {
|
||||
type DeterministicSegmentsReady,
|
||||
type Error,
|
||||
type SnapshotReady,
|
||||
applyRunIdentityRules,
|
||||
validateBoundaryProofs,
|
||||
validateRequiredArtifacts,
|
||||
validateSegmentRecords,
|
||||
} from "../../domain/models/IngestSnapshot.js"
|
||||
|
||||
export const decideSegmentRecords = (
|
||||
snapshotReady: SnapshotReady,
|
||||
): Either.Either<DeterministicSegmentsReady, Error> =>
|
||||
Either.flatMap(applyRunIdentityRules(snapshotReady.SelectedSnapshot), (derivedRunIdentity) =>
|
||||
Either.flatMap(validateSegmentRecords(snapshotReady.SelectedSnapshot), (segmentRecords) =>
|
||||
Either.flatMap(
|
||||
validateBoundaryProofs(
|
||||
snapshotReady.SelectedSnapshot.SnapshotIdentity,
|
||||
segmentRecords,
|
||||
),
|
||||
(boundaryProofs) =>
|
||||
Either.map(
|
||||
validateRequiredArtifacts(
|
||||
snapshotReady.SelectedSnapshot.SnapshotIdentity,
|
||||
snapshotReady.RequiredArtifacts,
|
||||
segmentRecords,
|
||||
),
|
||||
() => ({
|
||||
_tag: "DeterministicSegmentsReady" as const,
|
||||
RunIdentity: derivedRunIdentity.value,
|
||||
SelectedSnapshot: snapshotReady.SelectedSnapshot,
|
||||
PreviousRunManifest: snapshotReady.PreviousRunManifest,
|
||||
SegmentRecords: segmentRecords,
|
||||
BoundaryProofs: boundaryProofs,
|
||||
RequiredArtifacts: snapshotReady.RequiredArtifacts,
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Either } from "effect"
|
||||
|
||||
import {
|
||||
type Error,
|
||||
type IngestUpstreamSnapshot,
|
||||
type RunManifest,
|
||||
type SnapshotReady,
|
||||
type State,
|
||||
foldFailure,
|
||||
makeVerifiedPreviousRunManifest,
|
||||
parseBundleLocation,
|
||||
} from "../../domain/models/IngestSnapshot.js"
|
||||
|
||||
export const validatePreviousRunManifest = (
|
||||
manifest: RunManifest,
|
||||
): Either.Either<ReturnType<typeof makeVerifiedPreviousRunManifest>, Error> =>
|
||||
manifest.ManifestPath && manifest.SegmentsPath && manifest.CanonicalProjectionPath
|
||||
? Either.right(makeVerifiedPreviousRunManifest(manifest))
|
||||
: Either.left(
|
||||
foldFailure(manifest.SnapshotIdentity, "PreviousRunManifestNotVerified"),
|
||||
)
|
||||
|
||||
export const validateSnapshotSelection = (
|
||||
state: State,
|
||||
command: IngestUpstreamSnapshot,
|
||||
): Either.Either<SnapshotReady, Error> => {
|
||||
if (state._tag !== "AwaitingSnapshotSelection") {
|
||||
return Either.left(
|
||||
foldFailure(command.SnapshotIdentity, "RunIdentityCouldNotBeDerived"),
|
||||
)
|
||||
}
|
||||
|
||||
return Either.map(
|
||||
parseBundleLocation(command.SnapshotIdentity, command.BundleInput),
|
||||
(bundleLocation) => ({
|
||||
_tag: "SnapshotReady" as const,
|
||||
SelectedSnapshot: {
|
||||
SnapshotIdentity: command.SnapshotIdentity,
|
||||
BundleLocation: bundleLocation,
|
||||
SnapshotMetadata: command.SnapshotMetadata,
|
||||
},
|
||||
PreviousRunManifest: command.PreviousRunManifest,
|
||||
RequiredArtifacts: state.RequiredArtifacts,
|
||||
MaxBundleBytes: state.MaxBundleBytes,
|
||||
ParseBudget: state.ParseBudget,
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Effect, Either } from "effect"
|
||||
|
||||
import {
|
||||
type Error,
|
||||
type Event,
|
||||
type IngestUpstreamSnapshot,
|
||||
type State,
|
||||
} from "../domain/models/IngestSnapshot.js"
|
||||
import { decide, makeAwaitingSnapshotSelection } from "../policies/decideSnapshotIngest.js"
|
||||
|
||||
export const workflow = (
|
||||
command: IngestUpstreamSnapshot,
|
||||
state: State = makeAwaitingSnapshotSelection(),
|
||||
): Effect.Effect<Event, Error> =>
|
||||
Effect.gen(function* () {
|
||||
const decision = decide(state, command)
|
||||
if (Either.isLeft(decision)) {
|
||||
return yield* Effect.fail(decision.left)
|
||||
}
|
||||
return decision.right
|
||||
})
|
||||
@@ -16,14 +16,14 @@ import {
|
||||
makeTrustedSegmentsPath,
|
||||
makeTrustedSummaryPath,
|
||||
makeVerifiedPreviousRunManifest,
|
||||
} from "../src/domain/models/IngestSnapshot.js"
|
||||
} from "../src/contexts/ingest-snapshot/index.js"
|
||||
import {
|
||||
apply,
|
||||
decide,
|
||||
makeAwaitingSnapshotSelection,
|
||||
validatePreviousRunManifest,
|
||||
} from "../src/policies/decideSnapshotIngest.js"
|
||||
import { workflow } from "../src/workflows/ingestSnapshot.js"
|
||||
workflow,
|
||||
} from "../src/contexts/ingest-snapshot/index.js"
|
||||
|
||||
const makeCommand = (
|
||||
overrides: Partial<IngestUpstreamSnapshot> = {},
|
||||
|
||||
Reference in New Issue
Block a user