1+ import { createLogger } from '@sim/logger'
2+ import { getErrorMessage } from '@sim/utils/errors'
13import { decryptSecret } from '@/lib/core/security/encryption'
24import { isLargeArrayManifest } from '@/lib/execution/payloads/large-array-manifest-metadata'
35import { isLargeValueRef } from '@/lib/execution/payloads/large-value-ref'
@@ -10,6 +12,30 @@ import {
1012} from '@/executor/utils/resolved-secret-matcher'
1113import { getResolvedSecretMatcherCapacityFailure } from '@/executor/utils/resolved-secret-matcher-capacity'
1214
15+ const logger = createLogger ( 'ResolvedSecretTraceRegistry' )
16+
17+ /**
18+ * Why a registry stopped being able to vouch for what it projects.
19+ *
20+ * Incompleteness is one-way and fails every later model projection in the run, surfacing to the
21+ * user as a single opaque sentence. Recording which guard tripped is the only way to tell a
22+ * genuine containment from a matcher that merely could not decide — the reasons are static
23+ * literals and the logged path is block/field names, never a resolved value.
24+ */
25+ type ResolvedSecretIncompletenessReason =
26+ | 'untrusted-provenance'
27+ | 'source-provenance-incomplete'
28+ | 'entry-decrypt-failed'
29+ | 'unverified-resolved-entry'
30+ | 'projection-mismatch'
31+ | 'unresolved-placeholder'
32+ | 'filter-capacity-exceeded'
33+ | 'restored-checkpoint-unavailable'
34+ | 'value-provenance-untrusted'
35+ | 'value-provenance-import-failed'
36+ | 'value-provenance-filter-incomplete'
37+ | 'unspecified'
38+
1339export const ANONYMOUS_SECRET_TRACE_REPLACEMENT = OPAQUE_RESOLVED_SECRET_REPLACEMENT
1440export const RESOLVED_SECRET_TRACE_CHECKPOINT_VERSION = 1
1541
@@ -669,7 +695,7 @@ export class ResolvedSecretTraceRegistry {
669695
670696 const entry = this . getVerifiedResolvedEntry ( name , resolvedValue )
671697 if ( ! entry ) {
672- this . markInputPathIncomplete ( path )
698+ this . markInputPathIncomplete ( path , 'unverified-resolved-entry' )
673699 return false
674700 }
675701
@@ -790,7 +816,7 @@ export class ResolvedSecretTraceRegistry {
790816 typeof projectedValue === 'string' &&
791817 state . projectedValue !== projectedValue
792818 ) {
793- this . markInputPathIncomplete ( path )
819+ this . markInputPathIncomplete ( path , 'projection-mismatch' )
794820 return
795821 }
796822 for ( const entryKey of entryKeys ) state . entryKeys . add ( entryKey )
@@ -846,7 +872,7 @@ export class ResolvedSecretTraceRegistry {
846872 if ( current . raw !== null && typeof current . raw === 'object' ) {
847873 const standaloneName = canonicalPlaceholderName ( current . projected as string )
848874 if ( ! standaloneName || ! entryKeysByName . has ( standaloneName ) ) {
849- this . markInputPathIncomplete ( current . path )
875+ this . markInputPathIncomplete ( current . path , 'unresolved-placeholder' )
850876 return
851877 }
852878 recordProjectedMarkerAcrossRawLeaves (
@@ -994,12 +1020,12 @@ export class ResolvedSecretTraceRegistry {
9941020 options : ImportResolvedSecretTraceProvenanceOptions
9951021 ) : Promise < boolean > {
9961022 if ( ! options . trusted || ! isResolvedSecretTraceProvenanceV1 ( provenance ) ) {
997- this . markIncomplete ( )
1023+ this . markIncomplete ( 'untrusted-provenance' )
9981024 return false
9991025 }
10001026
10011027 if ( ! provenance . complete ) {
1002- this . markIncomplete ( )
1028+ this . markIncomplete ( 'source-provenance-incomplete' )
10031029 }
10041030
10051031 const sameScope = scopesMatch ( provenance . scope , this . scope )
@@ -1016,9 +1042,14 @@ export class ResolvedSecretTraceRegistry {
10161042 } ,
10171043 { propagated : true }
10181044 )
1019- } catch {
1045+ } catch ( error ) {
10201046 importedAll = false
1021- this . markIncomplete ( )
1047+ logger . warn ( 'Provenance entry could not be decrypted' , {
1048+ error : getErrorMessage ( error , 'Unknown error' ) ,
1049+ named : entry . name !== undefined ,
1050+ scopeWorkspaceId : this . scope ?. workspaceId ,
1051+ } )
1052+ this . markIncomplete ( 'entry-decrypt-failed' )
10221053 }
10231054 }
10241055
@@ -1058,19 +1089,19 @@ export class ResolvedSecretTraceRegistry {
10581089 options : { trusted : boolean ; inputPath ?: ResolvedSecretInputPath }
10591090 ) : Promise < ImportResolvedSecretTraceProvenanceForValueResult > {
10601091 if ( ! options . trusted || ! isResolvedSecretTraceProvenanceV1 ( provenance ) ) {
1061- this . markInputPathIncomplete ( options . inputPath )
1092+ this . markInputPathIncomplete ( options . inputPath , 'value-provenance-untrusted' )
10621093 return { success : false , matched : false }
10631094 }
10641095
10651096 const sourceRegistry = new ResolvedSecretTraceRegistry ( [ ] , provenance . scope )
10661097 const sourceImported = await sourceRegistry . importProvenance ( provenance , { trusted : true } )
10671098 const filteredProvenance = sourceRegistry . exportProvenanceForValue ( value )
10681099 if ( ! sourceImported ) {
1069- this . markInputPathIncomplete ( options . inputPath )
1100+ this . markInputPathIncomplete ( options . inputPath , 'value-provenance-import-failed' )
10701101 return { success : false , matched : false }
10711102 }
10721103 if ( ! filteredProvenance . complete ) {
1073- this . markInputPathIncomplete ( options . inputPath )
1104+ this . markInputPathIncomplete ( options . inputPath , 'value-provenance-filter-incomplete' )
10741105 return { success : true , matched : false }
10751106 }
10761107 const filteredImported = await this . importProvenance ( filteredProvenance , { trusted : true } )
@@ -1214,10 +1245,16 @@ export class ResolvedSecretTraceRegistry {
12141245 return ! this . complete || this . incompleteInputPaths . size > 0
12151246 }
12161247
1217- markIncomplete ( ) : void {
1248+ markIncomplete ( reason : ResolvedSecretIncompletenessReason = 'unspecified' ) : void {
12181249 if ( ! this . complete ) return
12191250 this . complete = false
12201251 this . modelEgressRevision += 1
1252+ logger . warn ( 'Resolved secret registry marked incomplete' , {
1253+ reason,
1254+ scopeWorkspaceId : this . scope ?. workspaceId ,
1255+ activeEntryCount : this . activeEntries . size ,
1256+ incompleteInputPathCount : this . incompleteInputPaths . size ,
1257+ } )
12211258 }
12221259
12231260 /**
@@ -1388,7 +1425,11 @@ export class ResolvedSecretTraceRegistry {
13881425 matcher = createResolvedSecretMatcher (
13891426 [ ...candidatesByScanLiteral . keys ( ) ] . map ( ( plaintext ) => ( { plaintext, replacement : '' } ) )
13901427 )
1391- } catch {
1428+ } catch ( error ) {
1429+ logger . warn ( 'Provenance filter matcher could not be built' , {
1430+ error : getErrorMessage ( error , 'Unknown error' ) ,
1431+ candidateCount : candidatesByScanLiteral . size ,
1432+ } )
13921433 return { complete : false }
13931434 }
13941435
@@ -1623,15 +1664,24 @@ export class ResolvedSecretTraceRegistry {
16231664 )
16241665 }
16251666
1626- private markInputPathIncomplete ( path : ResolvedSecretInputPath | undefined ) : void {
1667+ private markInputPathIncomplete (
1668+ path : ResolvedSecretInputPath | undefined ,
1669+ reason : ResolvedSecretIncompletenessReason = 'unspecified'
1670+ ) : void {
16271671 if ( ! path || path . length === 0 ) {
1628- this . markIncomplete ( )
1672+ this . markIncomplete ( reason )
16291673 return
16301674 }
16311675 const key = inputPathKey ( path )
16321676 if ( this . incompleteInputPaths . has ( key ) ) return
16331677 this . incompleteInputPaths . set ( key , [ ...path ] )
16341678 this . modelEgressRevision += 1
1679+ logger . warn ( 'Resolved secret input path marked incomplete' , {
1680+ reason,
1681+ inputPath : path . join ( '.' ) ,
1682+ scopeWorkspaceId : this . scope ?. workspaceId ,
1683+ activeEntryCount : this . activeEntries . size ,
1684+ } )
16351685 }
16361686
16371687 private copyIncompleteInputPathsTo (
0 commit comments