CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/speaker-toolkit

Seven-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, create and publish talk-content Agent Skills with talk pages to a Jekyll shownotes site, and verify a recorded screencast against its storyboard. Includes a 113-entry Presentation Patterns taxonomy (83 observable: 64 patterns + 19 antipatterns; 30 unobservable: 21 patterns + 9 antipatterns) for scoring, brainstorming, and go-live preparation.

75

Quality

94%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

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",
        "absence_provability",
    }
)
_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