harn-stdlib 0.10.23

Embedded Harn standard library source catalog
Documentation
// @harn-entrypoint-category personas.compiler
//
// std/personas/compiler — closed prompt-persona blueprint validation and lowering.
//
// The model-facing blueprint intentionally cannot carry TOML, Harn source,
// capabilities, budgets, model policy, filters, destinations, or authority.
// The CLI materializer owns those package bytes and atomically publishes only a
// successfully validated lowering.
import "std/calendar"
import "std/schema"
import { list_providers } from "std/triggers"

const PERSONA_BLUEPRINT_SCHEMA_VERSION: string = "1"

const __PERSONA_TEMPLATE_IDS: list<string> = ["deterministic-sweeper", "hybrid-classify-then-act", "frontier-judgment-loop"]

type PersonaBlueprintCron = {cron: string, timezone: string}

type PersonaBlueprintExternal = {provider: string, event: string}

type PersonaBlueprint = {
  schema_version: "1",
  name: string,
  description: string,
  goal: string,
  template: string,
  cron?: PersonaBlueprintCron,
  external?: PersonaBlueprintExternal,
}

type PersonaBlueprintDiagnostic = {code: string, path: string, message: string}

type PersonaBlueprintValidationReport = {
  valid: bool,
  schema_version?: string,
  source_kind?: string,
  errors: list<PersonaBlueprintDiagnostic>,
  warnings: list<PersonaBlueprintDiagnostic>,
}

type PersonaBlueprintTrigger = {
  id: string,
  kind: string,
  provider: string,
  events: list<string>,
  secrets: dict,
  schedule?: string,
  timezone?: string,
  handler: string,
}

type PersonaBlueprintLowering = {
  profile: "prompt_compiled_v1",
  template: string,
  persona: {name: string, description: string, goal: string},
  policy: {autonomy_tier: "suggest", receipt_policy: "required"},
  triggers: list<PersonaBlueprintTrigger>,
}

type PersonaBlueprintCompileResult = Result<PersonaBlueprintLowering, PersonaBlueprintValidationReport>

/**
 * persona_blueprint_schema.
 *
 * Returns the closed model-output schema. A blueprint has exactly one source
 * after semantic validation: either `cron` or `external`, never both.
 *
 * @effects: []
 * @errors: []
 * @api_stability: experimental
 */
pub fn persona_blueprint_schema() -> dict {
  return schema_closed_object(
    {
      schema_version: schema_literal(PERSONA_BLUEPRINT_SCHEMA_VERSION),
      name: schema_string(),
      description: schema_string(),
      goal: schema_string(),
      template: schema_enum(__PERSONA_TEMPLATE_IDS),
      cron: schema_field(schema_closed_object({cron: schema_string(), timezone: schema_string()}), false),
      external: schema_field(schema_closed_object({provider: schema_string(), event: schema_string()}), false),
    },
  )
}

fn __persona_blueprint_blank_report(blueprint) -> PersonaBlueprintValidationReport {
  return {valid: true, schema_version: blueprint?.schema_version, source_kind: nil, errors: [], warnings: []}
}

fn __persona_blueprint_error(
  report: PersonaBlueprintValidationReport,
  code: string,
  path: string,
  message: string,
) -> PersonaBlueprintValidationReport {
  return report + {errors: report.errors + [{code: code, path: path, message: message}]}
}

fn __persona_blueprint_finalize(report: PersonaBlueprintValidationReport) -> PersonaBlueprintValidationReport {
  return report + {valid: len(report.errors) == 0}
}

fn __persona_blueprint_identifier(value) -> bool {
  return type_of(value) == "string" && regex_match("^[A-Za-z_][A-Za-z0-9_]*$", value) != nil
}

fn __persona_blueprint_nonempty(value) -> bool {
  return type_of(value) == "string" && trim(value) != ""
}

fn __persona_blueprint_catalog_entry(providers, provider) {
  return providers.find({ entry -> entry?.provider == provider })
}

fn __persona_blueprint_record(blueprint) -> PersonaBlueprint {
  return {
    schema_version: "1",
    name: blueprint.name,
    description: blueprint.description,
    goal: blueprint.goal,
    template: blueprint.template,
    cron: blueprint.cron,
    external: blueprint.external,
  }
}

fn __persona_blueprint_cron_fields(cron) -> list<string> {
  return regex_split(trim(cron ?? ""), "\\s+").filter({ field -> field != "" }).to_list()
}

fn __persona_blueprint_validate_cron(blueprint, providers, report: PersonaBlueprintValidationReport) -> PersonaBlueprintValidationReport {
  let out = report
  const source = blueprint.cron
  if len(__persona_blueprint_cron_fields(source.cron)) != 5 || !is_valid_cron(source.cron) {
    out = __persona_blueprint_error(
      out,
      "invalid_cron",
      "cron.cron",
      "cron source must be a valid five-field cron expression",
    )
  }
  try {
    parts(0, source.timezone)
  } catch (e) {
    out = __persona_blueprint_error(
      out,
      "invalid_timezone",
      "cron.timezone",
      "cron timezone is not a supported IANA timezone: " + to_string(e),
    )
  }
  const cron_provider = __persona_blueprint_catalog_entry(providers, "cron")
  if cron_provider == nil || !contains(cron_provider.kinds, "cron") {
    out = __persona_blueprint_error(
      out,
      "cron_unavailable",
      "cron",
      "live trigger catalog does not expose the cron transport",
    )
  }
  return out
}

fn __persona_blueprint_validate_external(
  blueprint,
  providers,
  report: PersonaBlueprintValidationReport,
) -> PersonaBlueprintValidationReport {
  let out = report
  const source = blueprint.external
  const provider = __persona_blueprint_catalog_entry(providers, source.provider)
  if provider == nil {
    return __persona_blueprint_error(
      out,
      "unknown_provider",
      "external.provider",
      "provider `" + source.provider + "` is not in the live trigger catalog",
    )
  }
  if len(provider.kinds) != 1 {
    out = __persona_blueprint_error(
      out,
      "ambiguous_provider_transport",
      "external.provider",
      "provider `" + source.provider + "` must expose exactly one transport kind",
    )
  }
  if !starts_with(source.event, source.provider + ".") {
    out = __persona_blueprint_error(
      out,
      "event_namespace_mismatch",
      "external.event",
      "event must begin with the selected provider namespace `" + source.provider + ".`",
    )
  }
  return out
}

/**
 * persona_blueprint_validate.
 *
 * Validates a closed `PersonaBlueprint` without performing an LLM call or a
 * filesystem mutation. `providers` exists for deterministic tests; production
 * callers leave it nil to use `std/triggers::list_providers()`.
 *
 * @effects: []
 * @errors: []
 * @api_stability: experimental
 */
pub fn persona_blueprint_validate(blueprint, providers = nil) -> PersonaBlueprintValidationReport {
  if type_of(blueprint) != "dict" {
    return {
      valid: false,
      schema_version: nil,
      source_kind: nil,
      errors: [{code: "not_a_dict", path: "", message: "persona blueprint must be a dict"}],
      warnings: [],
    }
  }
  const shape = schema_check(blueprint, persona_blueprint_schema())
  let report = __persona_blueprint_blank_report(blueprint)
  if is_err(shape) {
    const err = unwrap_err(shape)
    for entry in err.errors ?? [] {
      report = __persona_blueprint_error(report, "shape", entry?.path ?? "", entry?.message ?? to_string(entry))
    }
    return __persona_blueprint_finalize(report)
  }
  if !__persona_blueprint_identifier(blueprint.name) {
    report = __persona_blueprint_error(
      report,
      "invalid_name",
      "name",
      "persona name must be an identifier-like token",
    )
  }
  if !__persona_blueprint_nonempty(blueprint.description) {
    report = __persona_blueprint_error(
      report,
      "empty_description",
      "description",
      "description must not be blank",
    )
  }
  if !__persona_blueprint_nonempty(blueprint.goal) {
    report = __persona_blueprint_error(report, "empty_goal", "goal", "goal must not be blank")
  }
  const has_cron = blueprint.cron != nil
  const has_external = blueprint.external != nil
  if has_cron == has_external {
    return __persona_blueprint_finalize(
      __persona_blueprint_error(
        report,
        "source_count",
        "",
        "persona blueprint must select exactly one source: cron or external",
      ),
    )
  }
  const catalog = providers ?? list_providers()
  if has_cron {
    report = __persona_blueprint_validate_cron(blueprint, catalog, report) + {source_kind: "cron"}
  } else {
    report = __persona_blueprint_validate_external(blueprint, catalog, report) + {source_kind: "external"}
  }
  return __persona_blueprint_finalize(report)
}

fn __persona_blueprint_required_secret_refs(provider) -> dict {
  let secrets = {}
  for requirement in provider.secret_requirements {
    if requirement.required {
      // A lowering carries stable identifiers, never secret material. Package
      // validation owns the check that each provider-scoped reference is valid.
      secrets = secrets + {[requirement.name]: provider.provider + "/" + requirement.name}
    }
  }
  return secrets
}

fn __persona_blueprint_lower_trigger(blueprint, providers) -> PersonaBlueprintTrigger {
  const handler = "persona://" + blueprint.name
  if blueprint.cron != nil {
    return {
      id: blueprint.name + "-cron",
      kind: "cron",
      provider: "cron",
      events: ["cron.tick"],
      secrets: {},
      schedule: blueprint.cron.cron,
      timezone: blueprint.cron.timezone,
      handler: handler,
    }
  }
  const provider = __persona_blueprint_catalog_entry(providers, blueprint.external.provider)
  return {
    id: blueprint.name + "-" + provider.kinds[0],
    kind: provider.kinds[0],
    provider: blueprint.external.provider,
    events: [blueprint.external.event],
    secrets: __persona_blueprint_required_secret_refs(provider),
    schedule: nil,
    timezone: nil,
    handler: handler,
  }
}

/**
 * persona_blueprint_compile.
 *
 * Lowers a valid blueprint to the fixed prompt-compiled safety profile. This
 * never emits package bytes; the canonical scaffold transaction materializes
 * the resulting template, policy, and trigger plan atomically. Returns
 * `Ok(lowering)` on success or `Err(validation)` without side effects.
 *
 * @effects: []
 * @errors: []
 * @api_stability: experimental
 */
pub fn persona_blueprint_compile(blueprint, providers = nil) -> PersonaBlueprintCompileResult {
  const catalog = providers ?? list_providers()
  const validation = persona_blueprint_validate(blueprint, catalog)
  if !validation.valid {
    return Err(validation)
  }
  const typed = __persona_blueprint_record(blueprint)
  const trigger = __persona_blueprint_lower_trigger(typed, catalog)
  return Ok(
    {
      profile: "prompt_compiled_v1",
      template: typed.template,
      persona: {name: typed.name, description: typed.description, goal: typed.goal},
      policy: {autonomy_tier: "suggest", receipt_policy: "required"},
      triggers: [trigger],
    },
  )
}