diff --git a/packages/effect-e2e/src/cache/__tests__/admit-entry-write.workflow.property.test.ts b/packages/effect-e2e/src/cache/__tests__/admit-entry-write.workflow.property.test.ts index ed391a3..b90a3b0 100644 --- a/packages/effect-e2e/src/cache/__tests__/admit-entry-write.workflow.property.test.ts +++ b/packages/effect-e2e/src/cache/__tests__/admit-entry-write.workflow.property.test.ts @@ -36,18 +36,6 @@ it.prop( labelOf(admissionOf(subject, new AdmitEntryWrite({ mode: 'read-write', entry }))) === `admitted ${wireOf(entry)}`, ) -it.prop( - '∀e_AdmitEntryWrite_→OversizedPastCap', - { of: [TraceEntry], subject: admitEntryWrite, arbitrary: { seed: 500_012 } }, - (subject, [entry]) => { - const exact = paddedTo(entry, WIRE_BYTES_CAP) - const over = paddedTo(entry, WIRE_BYTES_CAP + 1) - return labelOf(admissionOf(subject, new AdmitEntryWrite({ mode: 'read-write', entry: exact }))) === - `admitted ${wireOf(exact)}` && - labelOf(admissionOf(subject, new AdmitEntryWrite({ mode: 'read-write', entry: over }))) === 'refused Oversized' - }, -) - it.prop( '∀e_AdmitEntryWrite_→ReadOnlyRefusedBeforeSize', { of: [TraceEntry, Schema.Boolean], subject: admitEntryWrite, arbitrary: { seed: 500_013 } }, diff --git a/packages/effect-e2e/src/cache/__tests__/classify-stored-entry.workflow.property.test.ts b/packages/effect-e2e/src/cache/__tests__/classify-stored-entry.workflow.property.test.ts index bd074d8..d513001 100644 --- a/packages/effect-e2e/src/cache/__tests__/classify-stored-entry.workflow.property.test.ts +++ b/packages/effect-e2e/src/cache/__tests__/classify-stored-entry.workflow.property.test.ts @@ -1,5 +1,5 @@ import { it } from '@systemfsoftware/vitest' -import * as Arr from 'effect/Array' +import * as Effect from 'effect/Effect' import * as Match from 'effect/Match' import * as Option from 'effect/Option' import * as Result from 'effect/Result' @@ -11,8 +11,6 @@ import { entryWireBytesOf, entryWireTextOf, TraceEntry } from '../entry.schema.j type Classify = typeof classifyStoredEntry -const WIRE_BYTES_CAP = 1_048_576 - const utf8 = new TextEncoder() const readOf = (classify: Classify, command: ClassifyStoredEntry): CacheReadResult => @@ -27,45 +25,81 @@ const labelOf = (read: CacheReadResult): string => Invalid: ({ reason }) => `invalid ${reason}`, }) -const isHit = (read: CacheReadResult): boolean => - Match.valueTags(read, { Hit: () => true, Miss: () => false, Invalid: () => false }) - const wireOf = (entry: TraceEntry): string => Option.getOrElse(entryWireTextOf(entry), () => '') -const withAlerts = (entry: TraceEntry, alerts: ReadonlyArray): TraceEntry => ({ ...entry, alerts }) +const WIRE_BYTES_CAP = 1_048_576 -type AlertSlot = readonly [head: string, tail: string] +const withAlerts = (entry: TraceEntry, alerts: ReadonlyArray): TraceEntry => ({ ...entry, alerts }) -const alertSlotOf = (entry: TraceEntry): AlertSlot => { - const empty = wireOf(withAlerts(entry, [''])) - const filled = wireOf(withAlerts(entry, ['a'])) - const at = Option.getOrElse( - Arr.findFirstIndex(empty.split(''), (char, index) => char !== filled[index]), - () => empty.length, - ) - return [empty.slice(0, at), empty.slice(at)] +const paddedTo = (entry: TraceEntry, bytes: number): TraceEntry => { + const slim = withAlerts(entry, []) + return withAlerts(slim, ['a'.repeat(bytes - entryWireBytesOf(slim) - 2)]) } -const paddedWireOf = ([head, tail]: AlertSlot, count: number): string => `${head}${'a'.repeat(count)}${tail}` - it.prop( '∀e_ClassifyStoredEntry_≡HitRoundTripsWireText', { of: [TraceEntry], subject: classifyStoredEntry, arbitrary: { seed: 500_001 } }, (subject, [entry]) => labelOf(readOf(subject, foundOf(utf8.encode(wireOf(entry))))) === `hit ${wireOf(entry)}`, ) +const SWEEP_BOUND = 16 + +/** Folds a drawn integer onto a size offset in -SWEEP_BOUND..SWEEP_BOUND, so every draw lands near the cap. */ +const offsetOf = (n: number): number => { + const span = 2 * SWEEP_BOUND + 1 + return (((n % span) + span) % span) - SWEEP_BOUND +} + +const templateEntry = Effect.runSync( + Schema.decodeEffect(TraceEntry)({ + schema: 'trace-1', + keyedBy: { + step: 'act', + project: 'shop', + test: 'checkout pays', + target: 'staging', + engine: { adapter: '@systemfsoftware/effect-e2e-web', kind: 'web' }, + app: 'shop', + instruction: 'open /cart', + params: {}, + slots: [], + agent: { name: 'default', contextDigest: '0'.repeat(64) }, + }, + start: { origin: 'https://shop.example', path: '/', query: [] }, + actions: [], + end: { origin: 'https://shop.example', path: '/cart', query: [] }, + anchors: { appeared: [], gone: [] }, + checks: [], + alerts: [], + provenance: { + engine: { adapter: '@systemfsoftware/effect-e2e-web', kind: 'web' }, + engineVersion: '1.0.0', + model: { provider: 'openrouter', model: 'deepseek/deepseek-flash-latest' }, + agent: 'default', + version: '1.0.0', + }, + }), +) + it.prop( - '∀e_ClassifyStoredEntry_→OversizedPastCap', - { of: [TraceEntry], subject: classifyStoredEntry, arbitrary: { seed: 500_002 } }, - (subject, [entry]) => { - const slot = alertSlotOf(entry) - const base = entryWireBytesOf(withAlerts(entry, [''])) - const exact = utf8.encode(paddedWireOf(slot, WIRE_BYTES_CAP - base)) - const over = utf8.encode(paddedWireOf(slot, WIRE_BYTES_CAP + 1 - base)) - return exact.length === WIRE_BYTES_CAP && - over.length === WIRE_BYTES_CAP + 1 && - isHit(readOf(subject, foundOf(exact))) && - labelOf(readOf(subject, foundOf(over))) === 'invalid oversized' + '∀n_ClassifyStoredEntry_≤WireCap', + { + of: [Schema.Int], + subject: classifyStoredEntry, + cover: { + BelowCap: [(n: number) => offsetOf(n) < 0, 0.3], + AtCap: [(n: number) => offsetOf(n) === 0, 0.02], + AboveCap: [(n: number) => offsetOf(n) > 0, 0.3], + }, + arbitrary: { seed: 500_002 }, + }, + (subject, [n]) => { + const bytes = utf8.encode(wireOf(paddedTo(templateEntry, WIRE_BYTES_CAP + offsetOf(Number(n))))) + return Match.valueTags(readOf(subject, foundOf(bytes)), { + Hit: () => bytes.length <= WIRE_BYTES_CAP, + Invalid: () => bytes.length > WIRE_BYTES_CAP, + Miss: () => false, + }) }, ) diff --git a/packages/effect-e2e/tests/cache-store.cap.integration.test.ts b/packages/effect-e2e/tests/cache-store.cap.integration.test.ts new file mode 100644 index 0000000..0f3599d --- /dev/null +++ b/packages/effect-e2e/tests/cache-store.cap.integration.test.ts @@ -0,0 +1,174 @@ +import { + CacheReadResult, + CacheStorageFailed, + CacheWriteResult, + KeyDigest, + MemoryCacheStore, + TraceEntry, +} from '@systemfsoftware/effect-e2e/cache' +import { Gherkin, Given, it, makeFeature, Then, When } from '@systemfsoftware/effect-gherkin-spec' +import * as Effect from 'effect/Effect' +import * as Layer from 'effect/Layer' +import * as Match from 'effect/Match' +import * as Schema from 'effect/Schema' + +const Feature = makeFeature({ it }) + +/** The spec constant the store's own cap must agree with, kept here so the tests never import it. */ +const WIRE_BYTES_CAP = 1_048_576 + +const utf8 = new TextEncoder() + +const D1 = KeyDigest.make('1'.repeat(64)) + +const route = (path: string): Schema.JsonObject => ({ origin: 'https://shop.example', path, query: [] }) + +const entryJson = (path: string, alerts: ReadonlyArray): Schema.JsonObject => ({ + schema: 'trace-1', + keyedBy: { + step: 'act', + project: 'shop', + test: 'checkout pays', + target: 'staging', + engine: { adapter: '@systemfsoftware/effect-e2e-web', kind: 'web' }, + app: 'shop', + instruction: `open ${path}`, + params: {}, + slots: [], + agent: { name: 'default', contextDigest: '0'.repeat(64) }, + }, + start: route('/'), + actions: [{ action: { _tag: 'Navigate', url: `https://shop.example${path}` }, controls: [] }], + end: route(path), + anchors: { appeared: [], gone: [] }, + checks: [], + alerts, + provenance: { + engine: { adapter: '@systemfsoftware/effect-e2e-web', kind: 'web' }, + engineVersion: '1.0.0', + model: { provider: 'openrouter', model: 'deepseek/deepseek-flash-latest' }, + agent: 'default', + version: '1.0.0', + }, +}) + +const isJsonArray = (value: Schema.Json): value is Schema.JsonArray => Array.isArray(value) + +const isJsonObject = (value: Schema.Json): value is Schema.JsonObject => + typeof value === 'object' && value !== null && !isJsonArray(value) + +/** Canonical wire text built here, never through the production encoder. */ +const canonicalOf = (value: Schema.Json): string => + isJsonArray(value) + ? `[${value.map(canonicalOf).join(',')}]` + : isJsonObject(value) + ? `{${ + Object.entries(value) + .sort(([left], [right]) => (left < right ? -1 : 1)) + .map(([key, field]) => `${JSON.stringify(key)}:${canonicalOf(field)}`) + .join(',') + }}` + : JSON.stringify(value) + +const entryAtBytes = (bytes: number): Schema.JsonObject => { + const base = utf8.encode(canonicalOf(entryJson('/padded', ['']))).length + return entryJson('/padded', ['a'.repeat(bytes - base)]) +} + +const EXACT_CAP = entryAtBytes(WIRE_BYTES_CAP) +const OVER_CAP = entryAtBytes(WIRE_BYTES_CAP + 1) + +const entryOf = (json: Schema.JsonObject): Effect.Effect => + Effect.orDie(Schema.decodeUnknownEffect(TraceEntry)(json)) + +const readLabel = (read: CacheReadResult): string => + Match.valueTags(read, { + Hit: () => 'Hit', + Miss: () => 'Miss', + Invalid: ({ reason }) => `Invalid(${reason})`, + }) + +const writeLabel = (write: Effect.Effect): Effect.Effect => + Effect.match(write, { + onFailure: (failure) => `CacheStorageFailed(${failure.message})`, + onSuccess: CacheWriteResult.match({ + Written: () => 'Written', + ReadOnly: () => 'ReadOnly', + Oversized: () => 'Oversized', + }), + }) + +Feature('The wire byte cap at the cache store boundary') + .withLayer(Layer.empty) + .live('the scenarios run the real admit and classify workflows over an in-memory store') + .body(({ scenario }) => { + scenario( + 'An entry of exactly the wire byte cap is admitted, and its bytes are exactly the cap', + Gherkin.Do.pipe( + Given('a read-write memory store')('store', () => MemoryCacheStore.make({ mode: 'read-write' })), + When('the exact-cap entry is written under a fresh digest')( + 'write', + ({ store }) => Effect.flatMap(entryOf(EXACT_CAP), (entry) => writeLabel(store.write(D1, entry))), + ), + Then('the store admits it, and the entry is exactly 1048576 bytes')((state, expect) => + expect([state.write, utf8.encode(canonicalOf(EXACT_CAP)).length]).toEqual(['Written', WIRE_BYTES_CAP]) + ), + ), + ) + + scenario( + 'An entry one byte past the wire byte cap is refused as oversized', + Gherkin.Do.pipe( + Given('a read-write memory store')('store', () => MemoryCacheStore.make({ mode: 'read-write' })), + When('the over-cap entry is written under a fresh digest')( + 'write', + ({ store }) => Effect.flatMap(entryOf(OVER_CAP), (entry) => writeLabel(store.write(D1, entry))), + ), + Then('the store refuses it, and the entry is exactly 1048577 bytes')((state, expect) => + expect([state.write, utf8.encode(canonicalOf(OVER_CAP)).length]).toEqual(['Oversized', WIRE_BYTES_CAP + 1]) + ), + ), + ) + + scenario( + 'A stored entry of exactly the wire byte cap is a hit', + Gherkin.Do.pipe( + Given('a read-write memory store seeded with the exact-cap bytes')( + 'store', + () => + MemoryCacheStore.make({ + mode: 'read-write', + seed: new Map([[D1, utf8.encode(canonicalOf(EXACT_CAP))]]), + }), + ), + When('the store reads that digest')('read', ({ store }) => Effect.map(store.read(D1), readLabel)), + Then('the entry replays as a hit, and the stored bytes are exactly the cap')((state, expect) => + expect([state.read, utf8.encode(canonicalOf(EXACT_CAP)).length]).toEqual(['Hit', WIRE_BYTES_CAP]) + ), + ), + ) + + scenario( + 'A stored entry one byte past the wire byte cap is invalid oversized', + Gherkin.Do.pipe( + Given('a read-write memory store seeded with the over-cap bytes')( + 'store', + () => + MemoryCacheStore.make({ + mode: 'read-write', + seed: new Map([[D1, utf8.encode(canonicalOf(OVER_CAP))]]), + }), + ), + When('the store reads that digest')('read', ({ store }) => Effect.map(store.read(D1), readLabel)), + Then('the entry reads back as invalid oversized, and the stored bytes are exactly cap plus one')(( + state, + expect, + ) => + expect([state.read, utf8.encode(canonicalOf(OVER_CAP)).length]).toEqual([ + 'Invalid(oversized)', + WIRE_BYTES_CAP + 1, + ]) + ), + ), + ) + })