fix: collapse the evidence-key gate and resolver into one decision
Three fix rounds each rebuilt the gate as a separate expression that merely agreed with the resolver on the inputs that round's tests used. Different expressions cannot agree in general, so the defect class stayed open while each reported instance closed. `findResolvableAsset(assets, key)` is now the single place that decides which Asset an evidence key resolves to. Every gate is `Boolean(findResolvableAsset(...)) || legacyKey(key)` via one shared composition, and every resolver returns what it returns: - validate-working-copy: the key gate and the decorative lookup (a last-wins Map against the resolver's first-wins find, so alt could be judged against a different Asset than the one rendered) - adapters/mock/project-public-render-model: gate and resolver - instant-preview: gate and descriptor resolver - createAssetCatalogResolver: the pixels, a fourth expression nobody had listed -- one Asset's caption could sit over another Asset's image Duplicate assetKeys are a contract violation but reachable through a paged list, so the choice is total and order-independent: newest updatedAt wins, tie-broken by id. InstantPreview's gate is no longer looser than the others. The un-loaded-asset case it was loosened for blanks either way; all the looseness bought was catalog-only keys rendering an empty gap with no message while validation said EVIDENCE_UNSUPPORTED. The test that pinned that divergence now asserts the consistent behaviour, and the false comment claiming a fix that did not exist is gone. idempotent() now maps a deterministic content failure to VALIDATION_STALE/409 instead of offering a retry that fails identically, and no longer caches uncharacterized internal failures -- reporting one as retryable while freezing it in the ledger meant the retry could never re-run. Adds tests/features/tech-log/evidence-key-agreement.test.ts: 144 adversarial (asset list, key) combinations asserting the agreement itself rather than examples. It reported 53 disagreements against the previous code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
783e9b2cf1
commit
073fda87eb
@@ -2,7 +2,8 @@ import type { components } from "../../../contracts/studio/generated.ts";
|
||||
import type { Asset, WorkingCopyInput } from "../../../contracts/studio/contract.ts";
|
||||
import {
|
||||
evidenceCatalogEntriesFromAssets,
|
||||
supportsEvidenceKeyIn,
|
||||
findResolvableAsset,
|
||||
supportsResolvableEvidenceKey,
|
||||
} from "../../../domain/content-format/asset-evidence-catalog.ts";
|
||||
import { ContentFormatError } from "../../../domain/content-format/parse-case-content.ts";
|
||||
import {
|
||||
@@ -27,19 +28,15 @@ function isLegacyStaticEvidenceKey(key: string): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* `resolveCaseEvidenceAssets` only needs *a* `ResolvedAsset` to turn the authoring
|
||||
* model into a genuine `PublicRenderModel` -- nothing downstream reads `block.asset`
|
||||
* for the pixels actually shown; that comes from `resolveEvidenceAsset` below
|
||||
* (`createAssetCatalogResolver`). So this must never throw: a key the Asset Picker
|
||||
* (Task 10) inserts, with no catalog match yet, still gets a placeholder descriptor
|
||||
* instead of blanking the whole preview behind an error panel.
|
||||
* Total -- never throws. Resolves through `findResolvableAsset`, the same
|
||||
* function this file's key gate and `createAssetCatalogResolver` (which
|
||||
* supplies the pixels) use, so the descriptor attached to a block and the
|
||||
* image rendered for it always come from one Asset.
|
||||
*/
|
||||
function resolveAssetDescriptor(assets: readonly Asset[]) {
|
||||
return (key: string): ResolvedAsset => {
|
||||
const asset = assets.find(
|
||||
(candidate) => candidate.assetKey === key && candidate.managementStatus === "READY",
|
||||
);
|
||||
if (asset?.publicPath) {
|
||||
const asset = findResolvableAsset(assets, key);
|
||||
if (asset) {
|
||||
return {
|
||||
assetId: asset.id,
|
||||
assetKey: asset.assetKey,
|
||||
@@ -94,27 +91,12 @@ export function InstantPreview({
|
||||
draft,
|
||||
effectiveCatalog,
|
||||
{ mode: "PREVIEW", publishedAt: null },
|
||||
// Gate 1 deliberately asks the SAME question gate 2 asks here
|
||||
// (`supportsEvidenceKeyIn` over the merged catalog) -- unlike
|
||||
// `validate-working-copy.ts`/`adapters/mock/project-public-render-model.ts`,
|
||||
// where reusing gate 2's question for gate 1 was the bug across fix
|
||||
// rounds 1-3 (see `asset-evidence-catalog.ts`'s module doc). The
|
||||
// difference: `resolveAssetDescriptor` below is total and cannot
|
||||
// throw, so a gate 1 that passes a key the resolver cannot fully
|
||||
// resolve only ever degrades that one figure to a placeholder --
|
||||
// never a crash, and there is no port-contract to violate here.
|
||||
// Fix round 2 narrowed this to an assets-only check (mirroring the
|
||||
// mock's gate at the time), which was itself a regression: a CASE
|
||||
// referencing an Asset the editor has not yet loaded -- outside the
|
||||
// Picker's 50-item page, or simply because `assets` is still `[]`
|
||||
// while `listAssets` is in flight on first mount -- failed gate 1
|
||||
// entirely and blanked the WHOLE preview, for every block in the
|
||||
// document, not just the one unresolved reference. Reverted to the
|
||||
// merged catalog (fix round 3): a key any part of the backend
|
||||
// already considers legitimate (document catalog OR a loaded Asset)
|
||||
// passes gate 1, and the resolver fills in real pixels when it has
|
||||
// them or a placeholder when it does not.
|
||||
supportsEvidenceKeyIn(effectiveCatalog),
|
||||
// The same expression `validate-working-copy.ts` and the mock's
|
||||
// preview projection use: a resolvable Asset, or the legacy static
|
||||
// key. A key this rejects fails the projection, and the error panel
|
||||
// below names it -- the same key `validateDocument` reports
|
||||
// `EVIDENCE_UNSUPPORTED` for.
|
||||
supportsResolvableEvidenceKey(assets, isLegacyStaticEvidenceKey),
|
||||
),
|
||||
resolveAssetDescriptor(assets),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user