Skip to content
中文

Completeness and evidence projection rules ​

The policy implementation follows.

ts
export function defineEvidenceChecklist<Observation, Requirement extends EvidenceRequirement>(
  definitions: readonly EvidenceProvider<Observation, Requirement>[],
) {
  const providers = new Map<string, EvidenceProvider<Observation, Requirement>>();
  for (const definition of definitions) {
    const predicate = definition.isSatisfied;
    breakWhile([
      {
        occur: () => providers.has(definition.kind),
        error: () => `Duplicate evidence kind: ${definition.kind}`,
      },
      {
        occur: () => typeof predicate !== 'function',
        error: () => `Missing evidence provider: ${definition.kind}`,
      },
    ]);
    providers.set(
      definition.kind,
      Object.freeze({
        kind: definition.kind,
        description: definition.description,
        isSatisfied: predicate.bind(definition),
      }),
    );
  }
  const satisfied = (observations: readonly Observation[], requirement: Requirement): boolean => {
    const provider = providers.get(requirement.kind);
    breakWhile(!provider, () => `Unknown evidence kind: ${requirement.kind}`);
    return observations.some((observation) =>
      booleanResult(
        provider!.isSatisfied(observation, requirement),
        'Evidence predicates must be synchronous',
        'Evidence predicates must return boolean',
      ),
    );
  };
  return Object.freeze({
    isSatisfied: (request: { observations: readonly Observation[]; requirement: Requirement }) =>
      satisfied(request.observations, request.requirement),
    isComplete: (request: {
      observations: readonly Observation[];
      requirements: readonly Requirement[];
    }) => request.requirements.every((requirement) => satisfied(request.observations, requirement)),
    missing({
      observations,
      requirements,
    }: {
      observations: readonly Observation[];
      requirements: readonly Requirement[];
    }) {
      return requirements.filter((requirement) => !satisfied(observations, requirement));
    },
  });
}

export function defineEvidenceProjection<Observation, Source>(
  definition: EvidenceProjectionDefinition<Observation, Source>,
) {
  const sourceId = definition.sourceId.bind(definition);
  const toolId =
    definition.toolId?.bind(definition) ??
    ((observation: Observation) => {
      // Fallback tool selector: accept either the current `toolId` field or the legacy `tool`.
      const value = observation as { toolId?: string; tool?: string };
      return value.toolId ?? value.tool!;
    });
  const providers = new Map<string, EvidenceProjectionProvider<Observation, Source>>();
  for (const [tool, provider] of Object.entries(definition.providers)) {
    breakWhile(
      ![provider.sources, provider.withSources].every((handler) => typeof handler === 'function'),
      `Invalid evidence projection: ${tool}`,
    );
    providers.set(
      tool,
      Object.freeze({
        sources: provider.sources.bind(provider),
        withSources: provider.withSources.bind(provider),
      }),
    );
  }
  return Object.freeze({
    project({
      observations,
      sourceIds = [],
    }: {
      observations: Observation[];
      sourceIds?: readonly string[];
    }): Observation[] {
      // Unknown/empty claims still need review against every available candidate.
      if (!sourceIds.length) return observations;
      return observations.map((observation) => {
        const provider = providers.get(
          synchronous(toolId(observation), 'Evidence tool selectors must be synchronous'),
        );
        if (!provider) return observation;
        const sources = synchronous(
          provider.sources(observation),
          'Evidence source projection must be synchronous',
        ).filter((source) =>
          sourceIds.includes(
            synchronous(sourceId(source), 'Evidence source selectors must be synchronous'),
          ),
        );
        return synchronous(
          provider.withSources(observation, sources),
          'Evidence observations must be synchronous',
        );
      });
    },
  });
}

API reference, usage examples and developer guides.