{"version":3,"file":"W3cV2DataIntegrityContextValidator.mjs","names":["jsonld"],"sources":["../../../../src/modules/vc/data-integrity/W3cV2DataIntegrityContextValidator.ts"],"sourcesContent":["import type { AgentContext } from '../../../agent/context'\nimport { injectable } from '../../../plugins'\nimport {\n  createProofVerificationIssue,\n  type W3cDataIntegrityProcessingIssue as DataIntegrityProcessingIssue,\n  type W3cDataIntegrityUnsecuredDocument as DataIntegrityUnsecuredDocument,\n} from '../../w3c-di/internal'\nimport { omitUndefinedFields } from '../../w3c-di/proof-processing/normalization'\nimport { CREDENTIALS_CONTEXT_V2_URL } from '../constants'\nimport { DEFAULT_CONTEXTS, DI_SPEC_CONTEXT_HASHES } from '../jsonld/contexts'\nimport { JsonLdModuleConfig } from '../jsonld/JsonLdModuleConfig'\nimport jsonld from '../jsonld/jsonld'\nimport { W3cV2CredentialsModuleConfig } from '../W3cV2CredentialsModuleConfig'\n\n/**\n * Output of the VC Data Integrity §4.6 Context Validation algorithm.\n */\nexport interface W3cV2DataIntegrityContextValidationResult {\n  validated: boolean\n  validatedDocument: DataIntegrityUnsecuredDocument | null\n  warnings: DataIntegrityProcessingIssue[]\n  errors: DataIntegrityProcessingIssue[]\n}\n\n/**\n * Implements the VC Data Integrity §4.6 Context Validation algorithm.\n *\n * Algorithm inputs:\n *   - inputDocument: the secured document (after proof verification)\n *   - recompactInvalidContexts: whether to run JSON-LD compaction when §4.6 step 3 trigger conditions are detected\n *\n * Recompaction delegates context resolution to the shared\n * {@link JsonLdModuleConfig}, so a custom `documentLoader` configured on\n * `JsonLdModule`/`W3cCredentialsModule` is honoured consistently with VC 1.1\n * JSON-LD processing. Context hash verification (§2.4) only trusts Credo's\n * bundled context copies; it does not fetch remote content over the network.\n */\n@injectable()\nexport class W3cV2DataIntegrityContextValidator {\n  public constructor(\n    private readonly jsonLdModuleConfig: JsonLdModuleConfig,\n    private readonly w3cV2CredentialsModuleConfig: W3cV2CredentialsModuleConfig\n  ) {}\n\n  public async validate(\n    agentContext: AgentContext,\n    inputDocument: DataIntegrityUnsecuredDocument\n  ): Promise<W3cV2DataIntegrityContextValidationResult> {\n    const normalisedInputDocument = omitUndefinedFields(inputDocument)\n    const validatedDocument = { ...normalisedInputDocument }\n\n    // §4.6, step 1: initialise result\n    const result: W3cV2DataIntegrityContextValidationResult = {\n      validated: false,\n      validatedDocument,\n      warnings: [],\n      errors: [],\n    }\n\n    // §4.6, step 2: get contextValue\n    const contextValue = normaliseContext(validatedDocument['@context'])\n\n    // §2.4.2 Context Injection: context injection is only for securing, not verification.\n    // A conforming verifier MUST NOT accept a document without top-level @context.\n    if (validatedDocument['@context'] === undefined || validatedDocument['@context'] === null) {\n      result.errors.push(\n        createProofVerificationIssue(\n          'Missing top-level @context in secured document',\n          'Verification must not perform context injection (§2.4.2). Document must have explicit @context.'\n        )\n      )\n      result.validated = false\n      result.validatedDocument = null\n      return result\n    }\n\n    // §4.6, step 3: detect trigger conditions\n    const triggerErrors: DataIntegrityProcessingIssue[] = []\n\n    // 3a: the mandatory VC 2.0 context must be first; additional contexts are permitted.\n    if (contextValue[0] !== CREDENTIALS_CONTEXT_V2_URL) {\n      triggerErrors.push(\n        createProofVerificationIssue(\n          '@context does not start with the required VC 2.0 context',\n          `Document @context must start with '${CREDENTIALS_CONTEXT_V2_URL}'`\n        )\n      )\n    }\n\n    // 3b: any subtree contains @context (no exemptions per spec §4.6)\n    const nestedContextPaths = collectAllNestedContextPaths(validatedDocument)\n    if (nestedContextPaths.length > 0) {\n      triggerErrors.push(\n        createProofVerificationIssue(\n          'Nested @context detected in document',\n          `Nested @context found at path(s): ${nestedContextPaths.join(', ')}`\n        )\n      )\n    }\n\n    // 3c: flag spec-pinned contexts that are not available as bundled, trusted contexts (§2.4)\n    for (const contextEntry of contextValue) {\n      if (typeof contextEntry !== 'string') continue\n\n      if (contextEntry === CREDENTIALS_CONTEXT_V2_URL) continue\n      if (!(contextEntry in DI_SPEC_CONTEXT_HASHES)) continue\n\n      const bundledContextIssue = this.verifyContextIsBundled(contextEntry)\n      if (bundledContextIssue) {\n        triggerErrors.push(bundledContextIssue)\n        break\n      }\n    }\n\n    if (triggerErrors.length > 0) {\n      if (this.w3cV2CredentialsModuleConfig.recompactInvalidContexts) {\n        try {\n          result.validatedDocument = (await jsonld.compact(normalisedInputDocument, [CREDENTIALS_CONTEXT_V2_URL], {\n            documentLoader: this.jsonLdModuleConfig.documentLoader(agentContext),\n            compactToRelative: false,\n          })) as DataIntegrityUnsecuredDocument\n\n          // Preserve trigger-condition visibility when recompaction succeeds.\n          result.warnings.push(...triggerErrors)\n        } catch (error) {\n          result.errors.push(\n            createProofVerificationIssue(\n              'Context recompaction failed (§4.6 step 3.1)',\n              error instanceof Error ? error.message : 'JSON-LD compaction failed'\n            )\n          )\n        }\n      } else {\n        result.errors.push(...triggerErrors)\n      }\n    }\n\n    // §4.6, step 4: finalise\n    if (result.errors.length === 0) {\n      result.validated = true\n    } else {\n      result.validated = false\n      result.validatedDocument = null\n    }\n\n    // §4.6, step 5: return\n    return result\n  }\n\n  /**\n   * Verifies a spec-pinned context URI (§2.4). The §2.4 hash-pinning mechanism exists for\n   * processors that don't vendor the canonical contexts and must instead fetch and hash-verify\n   * them over the network; Credo vendors all spec-required contexts, so a bundled copy is\n   * trusted as canonical and no network fetch or hash comparison is performed.\n   */\n  private verifyContextIsBundled(contextUri: string): DataIntegrityProcessingIssue | undefined {\n    if (hasBundledContext(contextUri)) {\n      return undefined\n    }\n\n    return createProofVerificationIssue(\n      'Unable to verify context hash (§2.4)',\n      `Context '${contextUri}' is not available as a bundled context and cannot be hash-verified`\n    )\n  }\n}\n\nfunction normaliseContext(context: unknown): unknown[] {\n  if (context === undefined || context === null) return []\n  return Array.isArray(context) ? context : [context]\n}\n\nfunction collectAllNestedContextPaths(value: unknown, path: string[] = []): string[] {\n  if (value === null || typeof value !== 'object') return []\n\n  if (Array.isArray(value)) {\n    return value.flatMap((item, i) => collectAllNestedContextPaths(item, [...path, String(i)]))\n  }\n\n  const obj = value as Record<string, unknown>\n  const paths: string[] = []\n\n  for (const [key, entry] of Object.entries(obj)) {\n    const nextPath = [...path, key]\n    const isTopLevelContext = path.length === 0 && key === '@context'\n\n    if (key === '@context' && !isTopLevelContext) {\n      paths.push(nextPath.join('.'))\n    }\n\n    paths.push(...collectAllNestedContextPaths(entry, nextPath))\n  }\n\n  return paths\n}\n\nfunction hasBundledContext(contextUrl: string): boolean {\n  if (contextUrl in DEFAULT_CONTEXTS) return true\n  const withoutFragment = contextUrl.split('#')[0]\n  return withoutFragment in DEFAULT_CONTEXTS\n}\n"],"mappings":"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"}