CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/speaker-toolkit

Six-skill presentation system: ingest talks into a rhetoric vault, run interactive clarification, generate a speaker profile, create presentations that match your documented patterns, produce the deck illustrations + thumbnail visual layer, and publish talk pages to a Jekyll shownotes site. Includes a 111-entry Presentation Patterns taxonomy (81 observable: 62 patterns + 19 antipatterns; 30 unobservable: 21 patterns + 9 antipatterns) for scoring, brainstorming, and go-live preparation.

Quality

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

validate-profile.pyskills/vault-profile/scripts/

#!/usr/bin/env python3
"""Validate a current speaker-profile.json before the owner writes it.

Schema version 5 binds every Presentation Pattern aggregate to one exact,
current scoring generation, exact per-pattern opportunity denominators, and a
versioned classification policy. The reusable strict nested contract lives in
``profile_pattern_provenance.py`` so non-owner readers make the same pattern-
history availability decision as this writer.

Contract
--------
Input:
    A profile path (positional) or JSON on stdin, plus required
    ``--vault-root <path>`` for schema-v5 owner validation. The live vault is
    reloaded with the candidate baseline's ``as_of`` value before acceptance;
    no talk is reparsed.

Stdout (JSON):
    {
      "valid":          true|false,
      "schema_version": <int|null>,
      "missing_keys":   [ ... ],
      "errors":         [ ... ]
    }

Exit codes:
    0   profile valid
    1   profile invalid (missing keys, wrong schema version, malformed input,
        or stale/inconsistent pattern provenance)
"""

from __future__ import annotations

import json
import pathlib
import sys
from collections.abc import Mapping
from typing import Any


_PROFILE_SCRIPTS = pathlib.Path(__file__).resolve().parent
if str(_PROFILE_SCRIPTS) not in sys.path:
    sys.path.insert(0, str(_PROFILE_SCRIPTS))
_INGRESS_SCRIPTS = (
    pathlib.Path(__file__).resolve().parents[2] / "vault-ingress" / "scripts"
)
if str(_INGRESS_SCRIPTS) not in sys.path:
    sys.path.insert(0, str(_INGRESS_SCRIPTS))

from profile_pattern_provenance import (  # noqa: E402
    active_pattern_generation_identity as _active_pattern_generation_identity,
    assess_pattern_profile,
)
from pattern_cohort_snapshot import (  # noqa: E402
    PatternCohortSnapshotError,
    build_current_pattern_snapshot,
    configured_evidence_freshness_assessor,
)
from pattern_classification_runtime import (  # noqa: E402
    classify_pattern_profile,
    resolve_classification_policy,
)

# Pyright cannot resolve this sibling script module added to sys.path at runtime.
from tracking_database import (  # noqa: E402  # pyright: ignore[reportMissingImports]
    TrackingDatabaseError,
    assess_tracking_database,
)

# Pyright cannot resolve this sibling script module added to sys.path at runtime.
from tracking_database_io import (  # noqa: E402  # pyright: ignore[reportMissingImports]
    DATABASE_READ_DIAGNOSTICS,
    DATABASE_READ_FALLBACK,
    TrackingDatabaseIOError,
    decode_json_object,
    snapshot_tracking_database,
)

# Pyright cannot resolve this sibling script module added to sys.path at runtime.
from vault_root_authority import (  # noqa: E402  # pyright: ignore[reportMissingImports]
    materialize_native_authority,
    resolve_vault_root_authority,
)


CURRENT_SCHEMA_VERSION = 5

REQUIRED_KEYS = [
    "schema_version",
    "generated_date",
    "talks_analyzed",
    "speaker",
    "infrastructure",
    "presentation_modes",
    "instrument_catalog",
    "rhetoric_defaults",
    "confirmed_intents",
    "guardrail_sources",
    "pacing",
    "pattern_profile",
    "visual_style_history",
    "publishing_process",
    "design_rules",
    "badges",
]

_PATTERN_HISTORY_KEYS = frozenset(
    {
        "pattern_baseline",
        "baseline_talk_filenames",
        "eligible_talk_count",
        "talks_scored",
        "average_pattern_score",
        "score_trend",
        "pattern_breadth",
        "underused_patterns",
        "score_drivers",
        "by_mode",
        "strengths",
        "pattern_usage",
        "antipattern_frequency",
        "never_used_patterns",
        "signature_combinations",
        "mastery_levels",
        "classification_availability",
        "classification_schema_version",
        "classification_policy",
        "pattern_classifications",
        "antipattern_classifications",
        "trend_analysis",
    }
)
_FORBIDDEN_NON_PATTERN_ENTRY_FIELDS = frozenset(
    {
        "pattern_id",
        "pattern_ids",
        "pattern_score",
        "mastery_level",
        "pattern_catalog_fingerprint",
        "pattern_scoring_schema_version",
        "times_used",
        "times_detected",
        "usage_rate",
        "frequency_rate",
        "out_of",
        "detected_count",
        "evaluable_count",
        "unevaluable_count",
        "not_applicable_count",
        "eligible_cohort_count",
        "coverage",
        "classification",
        "observation_status",
        "applicable_coverage",
        "lower",
        "upper",
        "semantic_sha256",
    }
)


def active_pattern_generation_identity() -> tuple[str, int]:
    """Expose the shared active identity for profile construction/tests."""
    return _active_pattern_generation_identity()


def _parse_args(argv: list[str]) -> tuple[pathlib.Path | None, pathlib.Path | None]:
    profile_path: pathlib.Path | None = None
    vault_root: pathlib.Path | None = None
    index = 1
    while index < len(argv):
        arg = argv[index]
        if arg == "--vault-root":
            if vault_root is not None:
                raise ValueError("--vault-root may be supplied only once")
            index += 1
            if index >= len(argv):
                raise ValueError("--vault-root requires a path")
            vault_root = materialize_native_authority(
                argv[index],
                authority="cli_root",
            )
        elif arg.startswith("-"):
            raise ValueError(f"unknown option {arg!r}")
        elif profile_path is None:
            profile_path = pathlib.Path(arg)
        else:
            raise ValueError(f"unexpected extra argument {arg!r}")
        index += 1
    return profile_path, vault_root


def _load_input(profile_path: pathlib.Path | None) -> dict[str, Any]:
    if profile_path is not None:
        return json.loads(profile_path.read_text())
    return json.loads(sys.stdin.read())


def _load_live_pattern_snapshot(
    vault_root: pathlib.Path,
    profile: Mapping[str, object],
) -> dict[str, object]:
    """Recompute the source-exact payload used by ``load-vault.py``."""
    database_path = vault_root / "tracking-database.json"
    try:
        database_snapshot = snapshot_tracking_database(database_path)
        database = decode_json_object(database_snapshot)
    except TrackingDatabaseIOError as exc:
        # This message is printed and emitted in the result object, and decoder
        # messages name the rejected key or value verbatim. The typed reason
        # code routes to the shared closed vocabulary instead.
        _code, message = DATABASE_READ_DIAGNOSTICS.get(
            exc.reason_code, DATABASE_READ_FALLBACK
        )
        raise ValueError(f"tracking-database.json is invalid: {message}") from exc
    try:
        assessment = assess_tracking_database(database)
    except TrackingDatabaseError as exc:
        raise ValueError(f"tracking-database.json schema is invalid: {exc}") from exc
    if not assessment.usable:
        raise ValueError(
            "tracking-database.json has no usable prior state for this reader: "
            + ", ".join(assessment.reason_codes)
        )
    vault_root = resolve_vault_root_authority(
        database_path=database_path,
        config=database.get("config"),
        cli_vault_root=vault_root,
    )
    talks = database.get("talks")
    if not isinstance(talks, list) or any(
        not isinstance(talk, Mapping) for talk in talks
    ):
        raise ValueError("tracking-database.json `talks` must be an array of objects")
    pattern_profile = profile.get("pattern_profile")
    baseline = (
        pattern_profile.get("pattern_baseline")
        if isinstance(pattern_profile, Mapping)
        else None
    )
    as_of = baseline.get("as_of") if isinstance(baseline, Mapping) else None
    snapshot = build_current_pattern_snapshot(
        talks,
        as_of=as_of,
        evidence_freshness_assessor=configured_evidence_freshness_assessor(
            vault_root,
            database.get("config"),
        ),
    )
    try:
        classification = classify_pattern_profile(
            snapshot["baseline_talks"],
            resolve_classification_policy(vault_root),
        )
    except RuntimeError as exc:
        raise ValueError(
            f"pattern classification runtime is unavailable: {exc}"
        ) from exc
    return {**snapshot, "pattern_classification": classification}


def _validate_live_pattern_source(
    profile: Mapping[object, object],
    snapshot: object,
) -> list[str]:
    """Require source fields to equal one freshly recomputed canonical snapshot."""
    if not isinstance(snapshot, Mapping):
        return ["live pattern snapshot must be an object"]
    pattern_profile = profile.get("pattern_profile")
    if not isinstance(pattern_profile, Mapping):
        return ["pattern_profile must be an object before live source validation"]
    opportunities = snapshot.get("pattern_opportunities")
    if not isinstance(opportunities, Mapping):
        return ["live pattern snapshot lacks pattern_opportunities"]
    classification = snapshot.get("pattern_classification")
    if not isinstance(classification, Mapping):
        return ["live pattern snapshot lacks pattern_classification"]

    comparisons = (
        (
            "pattern_baseline",
            snapshot.get("pattern_baseline"),
            "live canonical pattern_baseline",
        ),
        (
            "baseline_talk_filenames",
            snapshot.get("baseline_talk_filenames"),
            "live fresh scoring-v5 cohort filenames",
        ),
        (
            "eligible_talk_count",
            opportunities.get("eligible_cohort_count"),
            "live fresh scoring-v5 eligible cohort count",
        ),
        (
            "pattern_usage",
            opportunities.get("pattern_usage"),
            "live canonical positive opportunity rows",
        ),
        (
            "antipattern_frequency",
            opportunities.get("antipattern_frequency"),
            "live canonical negative opportunity rows",
        ),
    )
    errors: list[str] = []
    for field, expected, description in comparisons:
        if pattern_profile.get(field) != expected:
            errors.append(
                f"pattern_profile.{field} does not equal the {description}; "
                "regenerate it from the current load-vault.py payload"
            )
    for field, expected in classification.items():
        if pattern_profile.get(field) != expected:
            errors.append(
                f"pattern_profile.{field} does not equal the live deterministic "
                "classification output; regenerate it from the current "
                "load-vault.py payload"
            )
    return errors


def _validate_non_pattern_entries(
    value: object,
    *,
    path: str,
) -> list[str]:
    if not isinstance(value, list):
        return [f"{path} must be an array"]

    errors: list[str] = []
    for index, entry in enumerate(value):
        entry_path = f"{path}[{index}]"
        if not isinstance(entry, Mapping):
            errors.append(f"{entry_path} must be an object")
            continue
        if entry.get("source_lane") != "non_pattern":
            errors.append(
                f"{entry_path}.source_lane must be exactly 'non_pattern'; "
                "catalog-derived history belongs only in pattern_profile"
            )
        forbidden = sorted(_FORBIDDEN_NON_PATTERN_ENTRY_FIELDS.intersection(entry))
        if forbidden:
            errors.append(
                f"{entry_path} contains catalog-history fields prohibited outside "
                f"pattern_profile: {', '.join(forbidden)}"
            )
    return errors


def _validate_catalog_history_storage(profile: Mapping[object, object]) -> list[str]:
    """Keep all Presentation Pattern history inside ``pattern_profile``."""
    errors: list[str] = []
    rhetoric_defaults = profile.get("rhetoric_defaults")
    if not isinstance(rhetoric_defaults, Mapping):
        errors.append("rhetoric_defaults must be an object")
    else:
        duplicates = sorted(_PATTERN_HISTORY_KEYS.intersection(rhetoric_defaults))
        if duplicates:
            errors.append(
                "rhetoric_defaults duplicates catalog history owned by "
                f"pattern_profile: {', '.join(duplicates)}"
            )

    guardrail_sources = profile.get("guardrail_sources")
    if not isinstance(guardrail_sources, Mapping):
        errors.append("guardrail_sources must be an object")
    elif "recurring_issues" not in guardrail_sources:
        errors.append("guardrail_sources.recurring_issues is required in schema v5")
    else:
        errors.extend(
            _validate_non_pattern_entries(
                guardrail_sources["recurring_issues"],
                path="guardrail_sources.recurring_issues",
            )
        )

    errors.extend(_validate_non_pattern_entries(profile.get("badges"), path="badges"))
    return errors


def validate_profile(
    profile: object,
    *,
    live_pattern_snapshot: object | None = None,
    require_live_source: bool = False,
    live_source_recompute_failed: bool = False,
) -> tuple[list[str], list[str], object]:
    """Return ``(missing_top_level_keys, errors, schema_version)``.

    ``live_source_recompute_failed`` marks a supplied ``--vault-root`` whose
    live snapshot could not be rebuilt. The caller reports that failure with its
    own specific error, so the missing-flag message is suppressed rather than
    stacked on top of it.
    """
    if not isinstance(profile, Mapping):
        return (
            [],
            [f"profile must be a JSON object, got {type(profile).__name__}"],
            None,
        )

    missing = [key for key in REQUIRED_KEYS if key not in profile]
    schema_version = profile.get("schema_version")
    errors: list[str] = []
    schema_is_current = (
        isinstance(schema_version, int)
        and not isinstance(schema_version, bool)
        and schema_version == CURRENT_SCHEMA_VERSION
    )
    if not schema_is_current:
        errors.append(
            f"schema_version is {schema_version!r} (expected {CURRENT_SCHEMA_VERSION})"
        )
    if not missing and schema_is_current:
        assessment = assess_pattern_profile(
            profile["pattern_profile"], expected_contract_version=5
        )
        if not assessment.current_contract:
            errors.extend(assessment.errors)
        errors.extend(_validate_catalog_history_storage(profile))
        if live_pattern_snapshot is not None:
            errors.extend(_validate_live_pattern_source(profile, live_pattern_snapshot))
        elif live_source_recompute_failed:
            # The root was supplied; the caller appends the specific
            # recomputation failure. Saying the flag is required here would
            # contradict it.
            pass
        elif require_live_source:
            errors.append(
                "schema-v5 owner validation requires --vault-root so occurrence "
                "rows and classifications can be recomputed from the live "
                "tracking database"
            )
    return missing, errors, schema_version


def _emit_result(
    *,
    valid: bool,
    schema_version: object,
    missing_keys: list[str],
    errors: list[str],
) -> None:
    print(
        json.dumps(
            {
                "valid": valid,
                "schema_version": schema_version,
                "missing_keys": missing_keys,
                "errors": errors,
            },
            indent=2,
        )
    )


def main(argv: list[str]) -> int:
    try:
        profile_path, vault_root = _parse_args(argv)
        profile = _load_input(profile_path)
    except (
        json.JSONDecodeError,
        UnicodeError,
        FileNotFoundError,
        OSError,
        ValueError,
    ) as exc:
        message = f"Could not load profile: {exc}"
        print(f"ERROR: could not load profile input: {exc}", file=sys.stderr)
        _emit_result(
            valid=False,
            schema_version=None,
            missing_keys=[],
            errors=[message],
        )
        return 1

    live_snapshot: object | None = None
    live_error: str | None = None
    if vault_root is not None and isinstance(profile, Mapping):
        try:
            live_snapshot = _load_live_pattern_snapshot(vault_root, profile)
        except PatternCohortSnapshotError as exc:
            live_error = (
                "schema-v5 owner validation could not recompute occurrence rows "
                f"and classifications from the --vault-root given: {exc}"
            )
        except (json.JSONDecodeError, OSError, ValueError) as exc:
            live_error = (
                "schema-v5 owner validation could not recompute occurrence rows "
                f"and classifications from the --vault-root given: {exc}"
            )

    missing, errors, schema_version = validate_profile(
        profile,
        live_pattern_snapshot=live_snapshot,
        require_live_source=True,
        live_source_recompute_failed=live_error is not None,
    )
    if live_error is not None:
        errors.append(live_error)
    if missing:
        errors.insert(0, f"missing keys: {', '.join(missing)}")
    valid = not missing and not errors

    if not valid:
        print(f"ERROR: profile invalid — {'; '.join(errors)}", file=sys.stderr)
    _emit_result(
        valid=valid,
        schema_version=schema_version,
        missing_keys=missing,
        errors=errors,
    )
    return 0 if valid else 1


if __name__ == "__main__":
    sys.exit(main(sys.argv))

skills

README.md

tile.json