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

schemas-db.mdskills/vault-ingress/references/

Vault DB & Subagent Schemas

Tracking Database Schema

The tracking database (tracking-database.json) is the single source of truth. Canonical path: ~/.claude/rhetoric-knowledge-vault/tracking-database.json.

Owner: vault-ingress owns the artifact shape, every independent record shape, and all migrations. vault-clarification may write current config, confirmed-intent, and improvement-goal records. presentation-creator may write current QR records. vault-profile and the remaining presentation consumers are read-only. Non-owner readers accept legacy database schema 0 and current schema 1 during rollout. They never rewrite legacy state. An unsupported future database or record version is no usable prior state.

The independently versioned records are the database root, config, each talks[], pptx_catalog[], qr_codes[], resources[], thumbnails[], confirmed_intents[], improvement_goals[], and talks[].source_rejections[]. Queue claims, source identities, adherence baselines, and evidence ledgers retain their existing domain schema fields. Objects embedded inside a versioned record are part of that containing record unless this reference declares a separate schema.

Current constants live in skills/vault-ingress/scripts/tracking_database.py. Database schema 0 is the implicit unversioned corpus. Within that root, a missing talk version is the historical talk schema v1, not a request to synthesize v5 evidence; missing config, PPTX, QR, resource, thumbnail, confirmed-intent, source-rejection, and goal versions likewise map only to their validated historical v1 shapes. Config v2 adds the owner-controlled pptx_directory_exclusions discovery boundary. Run the owner migration in vault-ingress Step 1. Dry-run emits the exact input SHA-256. Apply requires that digest, refuses active queue claims, stores the complete original bytes under .backups/, and replaces the verified generation atomically. Backup directory and file opens do not follow symbolic links. The target input comparison remains exact across bytes and every FileGeneration field, including mtime_ns and ctime_ns, after staging and immediately before replacement. The unique staged candidate keeps its original descriptor/name device and inode, regular-file type, single link, size, exact bytes, and SHA-256 throughout verification. Staged mtime_ns and ctime_ns may rebaseline after fsync only when the descriptor view and name view are each stable across one byte-read window within the writer-owned bounded attempts. Exhaustion and every hard staged mismatch raise StagedCandidateConflictError carrying the failed invariant. The class is defined in skills/vault-ingress/scripts/tracking_database_io.py.

pptx_catalog v1 -> v2

Writer: the record_pptx mutation in skills/vault-ingress/scripts/mutate-tracking-database.py. Readers dual-accept v1 and v2.

  • v1 records a bare visual_extracted boolean and nothing about which extractor produced it, so a stored true may refer to extractor schema v0, v1, v2, v3, or current v4. A v1 record with a visual_evidence key is rejected as an unknown field.
  • v2 requires visual_evidence: either null for a deck no extraction has been attempted on, or a receipt binding outcome, extractor_schema_version, pipeline_version, the exact source_fingerprint of the PPTX bytes, and the artifact identity/digest the run produced.
  • artifact is required when outcome is succeeded and must be null when it is failed. A success naming no artifact cannot be proven to still exist, which is the ambiguity this schema removes.
  • The receipt's shape is fatal at the writer and at the classifier, never at the database assessment. A malformed receipt is per-record evidence trouble: record_pptx refuses to persist it and the classifier refuses to trust it, but the database stays usable, so preflight reports one warning instead of refusing the whole vault.
  • visual_extracted mirrors whether visual_evidence records a success, so schema-v1 readers keep resolving one boolean.
  • Migration stamps an unversioned record at v1, not the current constant: a legacy record has no binding and cannot satisfy the v2 shape. Migration never invents a generation for it.
  • artifact.path is vault-root-relative; pptx_path is relative to config.pptx_source_dir.
  • Selection is derived from this record, never stored in it. The classes, the regeneration predicate, and the live observations a caller must supply are owned by classify_pptx_visual_evidence in skills/vault-ingress/scripts/tracking_database.py — see the constants above that function and its docstring. Running the selection is bootstrap-and-preflight.md's Step; this reference defines the persisted shape only.

qr_codes v1 -> v2

Writer: skills/presentation-creator/scripts/generate-qr.py. Readers dual-accept v1 and v2 for the rollout window.

  • v1 records qr_png_rel_path alone and carries no artifacts key. A v1 record with an artifacts key is rejected as an unknown field.
  • v2 requires a non-empty artifacts array — one entry per generated PNG, so a multi-colour deck run records every variant rather than only the first.
  • path is the exact path written, never a default filename. path_root states what path is relative to: deck_dir, cwd, or absolute. A reader never infers the root.
  • sha256 is the artifact's digest, so catalog validation distinguishes the intended PNG from a stale replacement. bg_hex names the colour variant, or is null for a single-variant run.
  • qr_png_rel_path mirrors artifacts[0].path so schema-v1 readers keep resolving one artifact.
  • target_url is the canonical redirect target in every mode, including MCP-preresolved. The short URL never stands in for it.
  • Migration stamps an unversioned record at v1, not the current constant: a legacy record has no artifacts and cannot satisfy the v2 shape. Only the QR writer produces v2 records, and it writes them complete.

A schema-v1 database with config v2 is an idempotent no-op. A schema-v1 database with config v1 receives only the config-v2 migration; the root generation and every other record remain unchanged. Before migration, queue inspect may read schema 0 and queue recover may close an active schema-0 lease in place. Recovery changes only queue lease/status state and never stamps database or talk schema fields; the established queue transition may advance a recovered claim receipt from v1 to v2 while adding its release fields.

The owner migration is a preservation migration. Its only allowed semantic changes are adding root schema v1, adding the validated historical version to an unversioned owner record, creating absent owned arrays as empty arrays, and upgrading config v1 to v2. A missing exclusion list receives the canonical defaults; a valid owner-supplied list is preserved exactly. It preserves every other JSON value and missing-vs-present distinction, including legacy-v1 pattern_observations objects, arrays, or nulls and every historical citation/source-inspection field. Explicit version 0 sentinels, future owner versions, ambiguous or malformed historical records, and active claims fail before backup or write. An exact current database is a byte-and-inode-preserving no-op.

Independent recordCurrent schema
database root1
config2 (schema 1 remains readable owner-migration input)
talk5
PPTX catalog1
QR code2 (schema 1 remains readable legacy state)
resource summary1
thumbnail1
confirmed intent1
source rejection1
improvement goal2 (schema 1 remains readable historical state)
ComponentAccessContract
vault-ingress migrationowner read/writeAccept root schema 0/1 and config schema 1/2; migrate to root v1/config v2; never downgrade future state
vault-ingress queue inspection/recoveryowner compatibility transitionInspect schema 0/1; recover active leases in schema 0/1; never stamp artifact or talk schema versions
vault-ingress queue normalization/claim, persistence, shownotes apply, source repaircurrent read/writeRequire database schema 1, config schema 2, and supported explicit owner-record versions; targeted writers emit their current record generation and never migrate the root implicitly
vault-ingress preflight, source audit, analysis rendering, shownotes dry-rundual readerParse schemas 0 and 1; gate through existing finding/error channels; never rewrite
vault-clarificationcurrent read/writeRoute schema migration to vault-ingress; preserve config v2 and stamp confirmed intent v1/improvement goal v2
presentation-creator QR writerdual reader/current writerRead schemas 0 and 1; require schema 1 before URL creation or QR metadata persistence; stamp QR v2
presentation-creator publishing/post-eventauthorized current writerRequire schema 1 before tracking writes; stamp resource v1 and preserve talk v5
illustrations thumbnail workflowauthorized current writerRequire schema 1 before tracking writes; stamp thumbnail v1 and preserve talk v5
vault-profiledual readerParse schemas 0 and 1; treat unsupported generations as unavailable; never migrate

Current database schema 1 with config schema 2 requires all eight top-level state fields shown below. Missing legacy arrays become empty during owner migration. Current writers do not create them opportunistically. A schema-v1 improvement goal remains valid historical state; migration never fabricates the schema-v2 baseline provenance needed for current pattern-goal verification. Talks v1-v5 remain readable under the current root and are promoted only when the talk-domain writer legitimately persists that exact talk.

config.vault_storage_path is a root assertion, not a redirect. When present and non-null it must be a native absolute locator lexically equal to the parent of tracking-database.json; absent/null uses that database parent. Readers do not expand ~, rebase relative values, translate foreign path flavors, resolve symlinks to establish equivalence, or silently repair a mismatch. Empty/blank, drive-relative forms such as C:vault, current-drive-rooted forms such as \vault, dot-segment roots, device namespaces, and every other non-native or non-absolute value fail closed. Repair an invalid stored assertion with the expectation-bound set_config dry-run/apply/re-read/preflight sequence in source-identity-preflight.md.

The exclusion value in the structural example below is one illustrative valid customization, not the owner default. See the config field semantics.

{
  "schema_version": 1,
  "config": {
    "schema_version": 2,
    "vault_root": "~/.claude/rhetoric-knowledge-vault",
    "vault_storage_path": "/native/absolute/vault/root (optional; must match the tracking-database parent; null/absent uses that parent)",
    "pptx_source_dir": "/native/absolute/path/to/Presentations (optional; null/absent falls back to the vault root)",
    "python_path": "/path/to/python3",
    "template_skip_patterns": ["template"],
    "pptx_directory_exclusions": ["example-tool-cache"],
    "shownotes": {
      "enabled": true,
      "source": {
        "type": "local_jekyll|local_hugo|local_eleventy|local_astro|remote_url|none",
        "path_or_url": "/path/to/shownotes-site-root (or a remote https URL for remote_url)",
        "talks_subdir": "_talks"
      },
      "url": {"base": "https://speaking.example.com", "template": "/{slug}/"},
      "thumbnail_path_template": "assets/images/thumbnails/{slug}-thumbnail.png",
      "slug_convention": {"template": "{venue-compact}{yy}-{short-id}", "examples": []},
      "ssg_template_pointer": "{source.path_or_url}/_layouts/default.html"
    },
    "clarification_sessions_completed": 0
  },
  "talks": [{
    "filename": "2024-04-10-talk-slug.md",
    "title": "Talk Title", "conference": "Name", "date": "2024-04-10",
    "slides_url": "Google Drive file URL (optional — slides extracted from video if absent)",
    "video_url": "YouTube watch URL (optional when a usable transcript or slide source exists)",
    "youtube_id": "dQw4w9WgXcQ", "google_drive_id": "1AbCdEfGhIjK",
    "source_identity": {
      "schema_version": 1, "provider": "youtube", "video_id": "dQw4w9WgXcQ",
      "title": "Title recorded at the source",
      "uploader": "Conference Channel", "uploader_id": "@conference",
      "speakers": ["Speaker Name"],
      "recorded_date": "2024-04-10", "upload_date": "2024-04-11",
      "duration_seconds": 2700,
      "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      "webpage_video_id": "dQw4w9WgXcQ",
      "captured_at": "2026-07-31T12:00:00Z"
    },
    "source_relation": {"type": "duplicate|borrowed_recording", "target_filename": "canonical-talk.md"},
    "source_rejections": [{
      "schema_version": 1,
      "source_type": "video|slides", "url": "known-bad upstream URL",
      "reason": "non_delivery_clip|wrong_delivery|unrelated_recording",
      "evidence": "how the rejection was verified",
      "verified_at": "timezone-aware ISO-8601 timestamp"
    }],
    "pptx_path": "Conference/Year/Talk Name.pptx  (optional — highest quality slide source when available)",
    "schema_version": 5,
    "transcript_source": "youtube_auto|whisper|manual|none  (how the transcript was obtained; MAY BE ABSENT — see below)",
    "transcript_path": "transcripts/{id}.txt  (optional vault-relative path; required for non-YouTube transcript evidence)",
    "slide_source": "pptx|pdf|both|video_extracted|none  (set in Step 2 per slide source hierarchy)",
    "slides_local_path": "slides/<artifact>.pdf  (optional explicit local PDF; legacy readers also accept slides_pdf_path/pdf_path)",
    "pptx_visual_status": "pending|extracted|no_pptx",
    "status": "pending|needs-reprocessing|reprocessing-inflight|processed|processed_partial|skipped_no_sources|skipped_download_failed|skipped_duplicate",
    "reprocess_reason": "machine-readable reason for needs-reprocessing, or null",
    "reprocess_generation": 1,
    "_queue_claim": {
      "schema_version": 5,
      "run_id": "reparse-2026-07",
      "batch_id": "25",
      "claimed_at": "2026-07-31T18:00:00+00:00",
      "previous_status": "needs-reprocessing",
      "reprocess_generation": 1,
      "required_return_schema_version": 5,
      "adherence_baseline": {
        "schema_version": 2,
        "as_of": "2026-07-31T18:00:00+00:00",
        "scope": "global",
        "active_batch_excluded": true,
        "excluded_filenames": ["2024-04-10-talk-slug.md"],
        "eligible_statuses": ["processed", "processed_partial"],
        "pattern_scoring_generation_status": "current",
        "pattern_scoring_generation_reasons": [],
        "pattern_catalog_fingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "pattern_scoring_schema_version": 5,
        "eligible_talk_count": 25,
        "opportunity_coverage_identity": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
        "raw_score_comparison_status": "available",
        "raw_score_comparison_reason": null,
        "scored_talk_count": 25,
        "pattern_score_sum": 170,
        "average_pattern_score": 6.8
      },
      "state": "claimed"
    },
    "_queue_claim_history": [],
    "rhetoric_notes": "", "areas_for_improvement": "",
    "structured_data": {}, "verbatim_examples": {},
    "adherence_assessment": "", "processed_date": null,
    "_comment_queryable_scalars": "Promoted from the subagent return by scripts/persist-results.py (PROMOTE list) — do NOT hand-map in Step 4.",
    "co_presenter": false, "co_presenters": [], "delivery_language": "en",
    "slide_count": 0, "slide_design_style": null, "illustration_style": null,
    "opening_type": null, "closing_type": null, "narrative_arc_type": null,
    "audience_interaction_count": 0, "pattern_score": 0,
    "pattern_scoring_generation_status": "current",
    "pattern_scoring_generation_reasons": [],
    "pattern_scoring_schema_version": 5,
    "pattern_catalog_fingerprint": "sha256 of the exact catalog files used",
    "pattern_observations": {
      "evidence_schema_version": 2,
      "evidence_sources": ["transcript"],
      "source_inspection": [{
        "source": "transcript",
        "line_ranges": [[1, 240]],
        "line_count": 240,
        "coverage_complete": true,
        "artifact_root": "vault",
        "artifact_path": "transcripts/dQw4w9WgXcQ.txt",
        "artifact_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
      }],
      "pattern_ids": [],
      "antipattern_ids": [],
      "not_evaluable_ids": [],
      "pattern_score": 0,
      "patterns_detected": [],
      "antipatterns_detected": [],
      "applicability_assessments": [],
      "pattern_outcomes": [
        {"pattern_id": "another-catalog-id", "outcome": "not_evaluable"},
        {"pattern_id": "one-catalog-id", "outcome": "undetected"}
      ],
      "opportunity_coverage_identity": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
      "not_evaluable": []
    }
  }],
  "_comment_schema_version": "Database schema v1 is owner-migrated by vault-ingress. A missing talk record version is the historical implicit-v1 lineage. v2 makes transcript_source optional. Two incompatible v3 lineages were emitted; v4 is their source-located union and remains archival with evidence ledger v1. V5 adds applicability assessments, exhaustive outcomes, opportunity-coverage identity, evidence ledger v2, and current scoring schema v5. Root migration preserves all historical evidence and never synthesizes v5 outcomes.",
  "_comment_absent_transcript_source": "Absent transcript_source: the key may be MISSING on a talk, and missing is meaningful — it means provenance is unknown, not that no transcript exists (that is the explicit value `none`). It arises on one path: fetch-transcript.py returning method `existing`, where a valid transcript was already on disk and no fetch ran, so nothing was learned about where it came from. Writers MUST NOT backfill a guess; `manual` in particular asserts a human produced it. Readers gauging transcript reliability MUST treat absent as unknown and MUST NOT default it to any value.",
  "pptx_catalog": [{
    "schema_version": 2,
    "pptx_path": "Conference/Year/Talk Name.pptx",
    "talk_filename": "2024-04-10-talk-slug.md or null",
    "matched": true,
    "slide_count": 60,
    "visual_extracted": true,
    "visual_evidence": {
      "outcome": "succeeded",
      "extractor_schema_version": 4,
      "pipeline_version": "1.5.0",
      "source_fingerprint": {
        "algorithm": "sha256",
        "digest": "64 lowercase hex characters",
        "size_bytes": 123456
      },
      "artifact": {
        "path": "vault-root-relative extraction artifact path",
        "sha256": "64 lowercase hex characters"
      }
    }
  }],
  "_comment_pptx_catalog_failed": {
    "schema_version": 2,
    "pptx_path": "Conference/Year/Broken.pptx",
    "talk_filename": null,
    "matched": false,
    "slide_count": 0,
    "visual_extracted": false,
    "visual_evidence": {
      "outcome": "failed",
      "extractor_schema_version": 4,
      "pipeline_version": "1.5.0",
      "source_fingerprint": {
        "algorithm": "sha256",
        "digest": "64 lowercase hex characters",
        "size_bytes": 123456
      },
      "artifact": null
    }
  },
  "qr_codes": [{
    "schema_version": 2,
    "talk_slug": "arc-of-ai",
    "target_url": "canonical shownotes URL — the short link's redirect target",
    "shortener": "bitly|rebrandly|none|mcp_preresolved",
    "short_path": "shortener's back-half/slashtag; always equals talk_slug, null for none",
    "short_url": "shortened URL, equal to target_url when shortener=none",
    "shortener_link_id": "API-side ID needed for updates; null for none",
    "qr_png_rel_path": "mirrors artifacts[0].path for schema-v1 readers",
    "artifacts": [{
      "path": "arc-of-ai-qr-ffffff.png",
      "path_root": "deck_dir|cwd|absolute",
      "sha256": "64 lowercase hex characters",
      "bg_hex": "ffffff, or null for a single-variant run"
    }],
    "created_at": "2026-04-15",
    "updated_at": "2026-04-15"
  }],
  "resources": [{
    "schema_version": 1,
    "talk_slug": "arc-of-ai",
    "item_count": 12,
    "category_breakdown": {"urls": 7, "repos": 3, "books_papers": 2}
  }],
  "thumbnails": [{
    "schema_version": 1,
    "talk_slug": "arc-of-ai",
    "youtube_url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
    "source_slide_num": 15,
    "speaker_photo_used": "photos/speaker-headshot.png",
    "thumbnail_path": "illustrations/thumbnail.png",
    "shownotes_thumbnail_path": "assets/images/thumbnails/arc-of-ai-thumbnail.png",
    "dimensions": "1280x720",
    "file_size_kb": 185,
    "created_at": "2026-04-20",
    "approved": true
  }],
  "confirmed_intents": [{
    "schema_version": 1,
    "pattern": "delayed_self_introduction",
    "intent": "deliberate",
    "rule": "Use two-phase introduction",
    "note": "Speaker-confirmed intent"
  }],
  "improvement_goals": []
}

Owner Read and Mutation Contract

Agent workflows never open or rewrite the tracking database directly. Read it through the owner command:

"{python_path}" "{speaker_toolkit_root}/skills/vault-ingress/scripts/read-tracking-database.py" \
  "{vault_root}/tracking-database.json"

The command accepts only a no-follow regular file and strictly decodes one UTF-8 JSON object. Duplicate keys at any depth, non-finite numbers, finite numbers that cannot round-trip through the toolkit without changing mathematical value, nesting deeper than 200 JSON containers, unpaired UTF-16 surrogate escapes, invalid JSON, and non-object roots fail closed. After decoding, owner schema assessment is the first semantic operation: an unsupported root, record, queue-claim, or adherence-baseline generation returns no database payload and is never interpreted with an older identity or nested shape. Success returns report schema v1 with database_path, the exact-byte sha256, and database; failure exits 2 with ok: false and no database value. The host Python may run only this stdlib-only bootstrap read until config.python_path is discovered or supplied by the user. Immediately repeat the read against the same canonical path with that configured interpreter and require the same SHA-256; restart if the generation changed. Use the configured interpreter for initialization, migration, queue recovery, typed mutation, and every later toolkit command.

Agent-owned config and catalog changes use a typed plan:

{
  "schema_version": 1,
  "mutations": [{
    "kind": "set_config",
    "path": ["shownotes", "enabled"],
    "expect": {"$missing": true},
    "value": true
  }]
}

Delete a config field with delete: true; never pass the missing marker as a value:

{
  "schema_version": 1,
  "mutations": [{
    "kind": "set_config",
    "path": ["talks_source_dir"],
    "expect": "/prior/path",
    "delete": true
  }]
}

Every plan is strict JSON with exactly schema_version and a non-empty mutations array. Every expectation is either the exact decoded value/record seen by the owner reader or the exact missing marker {"$missing": true}. JSON null is a present value and is never interchangeable with that marker. The exact missing marker is reserved and invalid as a set_config value. A legacy writer may already have persisted that marker literally. A deletion with the missing-marker expectation is idempotent across both recoverable states: an absent field stays unchanged, while the exact present marker is removed. Any other present value fails the precondition. A recovery change receipt adds before_exists: true and after_exists: false to distinguish the literal old marker from the absent after marker. Run the usual dry-run, hash-bound apply, and owner re-read sequence. The plan decoder applies the same finite-number round-trip, 200-container depth, and Unicode-scalar gates as the database decoder, so a reviewed value cannot silently change or fail later during rendering. Equality is recursive JSON equality: object key order is irrelevant, array order is significant, and booleans, integers, and decimals are distinct types. Thus true, 1, and 1.0 never satisfy one another, and {"$missing": 1} is an ordinary object rather than the missing marker. Schema versions use the same exact-type rule. The supported mutation kinds are:

KindTyped purpose
initialize_databaseSole mutation for a missing database; carries initial config
set_configSet or delete one nested config path against its exact prior value
record_pptxReplace/add one complete schema-v2 PPTX catalog record, generation binding included, and, when matched, bind the talk's expected pptx_path
upsert_confirmed_intentReplace/add one complete schema-v1 record identified by pattern
upsert_improvement_goalReplace/add one complete record identified by id
patch_improvement_goal_verificationSet only verification fields, with expect covering exactly the same fields
retire_improvement_goalExpect one complete current goal record and change only its status to retired, preserving legacy fields
upsert_resourceReplace/add one complete schema-v1 record identified by talk_slug
upsert_thumbnailReplace/add one complete schema-v1 record identified by talk_slug
apply_reviewed_metadataInstall one human-reviewed shownotes catalog-conflict decision on one exact talk filename, over a closed identity field set, with expect covering exactly the same fields
update_talk_publishingSet supported publishing fields on one exact talk filename, with expect covering exactly the same fields
update_talk_clarificationSet complete object/array blind_spot_observations or humor_postmortem values on one exact talk, with matching field expectations

apply_reviewed_metadata exists because scan-shownotes.py --apply refuses review-required entries by design: an approved catalog correction otherwise had no owner writer at all. It stays narrow — the writable field set, the metadata-only versus analysis-invalidating classification, and the reprocessing transition it demands are named at the top of skills/vault-ingress/scripts/mutate-tracking-database.py. Deterministic scan updates and human-approved conflict decisions stay separate paths; source lanes stay with apply-source-repairs.py.

The command owns each operation's closed fields and record validation; do not reimplement those allowlists in skill prose. PPTX catalog records require exact integer schema_version: 2, since only v2 carries the visual-evidence generation binding; resource, thumbnail, and confirmed-intent records require exact integer schema_version: 1. A boolean or future version is not equivalent. Complete resource category counts must sum to item_count, and publishing scalar/identifier types are checked before patching. Run the plan without --apply, review its changes, then bind apply to that report's exact input hash:

"{python_path}" "{speaker_toolkit_root}/skills/vault-ingress/scripts/mutate-tracking-database.py" \
  "{vault_root}/tracking-database.json" mutation-plan.json

"{python_path}" "{speaker_toolkit_root}/skills/vault-ingress/scripts/mutate-tracking-database.py" \
  "{vault_root}/tracking-database.json" mutation-plan.json \
  --apply --expected-sha256 <input-sha256-from-dry-run>

Initialization uses a sole initialize_database mutation, stamps database schema version 1 and config schema version 2, supplies the canonical pptx_directory_exclusions when the plan omits that field, defaults to dry-run, and applies with the literal --expected-sha256 missing. All other applies require the dry-run SHA. The complete plan is one transaction: one failed type, record, semantic expectation, or file-generation precondition installs nothing. Re-read after apply rather than assuming the candidate is still current.

All toolkit writers share a persistent sibling lock, stage and fsync complete bytes through a retained no-follow descriptor, and retain the opened parent directory. Immediately before installation they recheck exact input bytes/file generation plus the staged descriptor's bytes, hash, generation, type, link count, and directory-relative visible identity. Replacement/link and cleanup use names anchored to that same directory descriptor. A substituted staged name fails closed and is deliberately left untouched rather than unlinking an attacker's path. Cooperative writers therefore serialize; a same-bytes inode replacement still conflicts.

Filesystem editors that ignore the sibling lock are outside that exclusion guarantee. The final pre-install check is defense in depth, and an immediate post-install inode/byte verification reports an observed last-moment edit as installed_verification_failed. There remains an irreducible last-instruction race: a non-cooperating process can change the path during the install instruction or immediately after verification. No userspace lock protocol can make that actor cooperate, so every caller must re-read the live database after an installed result. No-op transactions preserve the original bytes and inode. Reports distinguish durable, unchanged, installed_directory_fsync_failed, and installed_verification_failed. Once the install syscall succeeds, verification, directory-fsync, staged-name cleanup, directory close, and lock unlock/close failures return database_written: true with warnings; they never masquerade as a pre-install exception. Inspect the live database and reported output SHA before any retry. Backup-using writers bind a never-overwritten backup to the exact input SHA under the same transaction.

Shownotes Scan/Import Report

Run scan-shownotes.py against the canonical tracking database. The scanner reads config.shownotes.source for local sources and resolves path_or_url/talks_subdir inside the configured root. A null or absent config.shownotes may use the legacy absolute config.talks_source_dir during migration. remote_url, none, and disabled sources return a structured no-op without reading Markdown or writing the database.

On an exact-filename match, the title-agreement call receives the stored title, the proposed shownotes title, the stored conference, and the stored date. Its closed boolean decision contract lives in skills/vault-ingress/scripts/source_identity_matching.py::shownotes_titles_agree. Agreement is comparison-only and keeps the stored title unchanged. Disagreement emits existing_title_conflict and leaves the entry review_required. An exact title can still fill empty non-title metadata through the existing update contract.

The command emits report schema v3:

{
  "schema_version": 3,
  "ok": true,
  "mode": "dry-run|apply",
  "operation": "scan|skipped_disabled|skipped_nonlocal",
  "apply_requested": false,
  "database_written": false,
  "input_sha256": "64 lowercase hex characters",
  "output_sha256": "64 lowercase hex characters",
  "durability_state": "dry_run|unchanged|durable|installed_directory_fsync_failed|installed_verification_failed",
  "warnings": [],
  "mutation_count": 1,
  "scanned_file_count": 1,
  "existing_talk_count": 10,
  "counts": {
    "add": 1,
    "update": 0,
    "unchanged": 0,
    "review_required": 0
  },
  "shownotes": {
    "enabled": true,
    "source_type": "local_jekyll",
    "config_origin": "shownotes|talks_source_dir",
    "root": "/absolute/shownotes/root",
    "talks_subdir": "_talks",
    "talks_directory": "/absolute/shownotes/root/_talks"
  },
  "entries": [{
    "filename": "2026-08-01-talk.md",
    "disposition": "add|update|unchanged|review_required",
    "proposal": {"filename": "2026-08-01-talk.md"},
    "changes": {},
    "issues": [],
    "applied": false
  }]
}

A rejected_source_reappeared issue carries the exact ledger record that produced the match, so a reviewer decides from the report alone:

{
  "code": "rejected_source_reappeared",
  "field": "video_url",
  "message": "shownotes proposes a known-bad video_url; ...",
  "matched_rejection": {
    "source_type": "video|slides",
    "url": "the stored known-bad URL",
    "provider_id": "parsed provider ID, or null when the URL has none",
    "reason": "non_delivery_clip|wrong_delivery|unrelated_recording",
    "evidence": "how the rejection was verified",
    "verified_at": "timezone-aware ISO-8601 timestamp"
  },
  "match": {
    "method": "exact_url|provider_id",
    "candidate_url": "the URL the shownotes page proposes",
    "candidate_provider_id": "its parsed provider ID, or null"
  }
}

Only the matched record appears; unrelated source_rejections entries stay private to the talk. A malformed ledger record never reaches this shape — the scanner refuses to load a database whose source_rejections fail tracking_database._validate_source_rejection.

Dry-run is the default and never writes. --apply adds complete new records with current talk schema and status pending, or fills empty fields on an exact filename match. Established values are not overwritten. Conflicting, incomplete, and normalized-collision entries stay review_required and never mutate. mutation_count counts deterministic add and update candidates; database_written records whether an atomic replacement occurred. Input and output SHA-256 values bind the report to exact database generations. An installed_directory_fsync_failed or installed_verification_failed result means the install syscall succeeded; inspect the live database and reported output hash before retrying.

Supported Markdown metadata includes YAML, TOML, and JSON frontmatter plus a body H1 and labeled Conference, Event, Venue, Date, Video, Recording, Slides, or Deck links. YouTube and Google Drive identities use the shared ingress URL parsers. A proposed source matches a rejection when its URL is exact or its parsed provider ID equals the rejected URL's ID. Such a proposal remains inactive with rejected_source_reappeared until human review supplies a valid replacement.

The two persisted pattern_outcomes rows in the tracking-DB example are illustrative only; a real v5 persisted talk contains exactly one sorted row for every observable catalog entry. The raw v5 worker return below includes applicability_assessments but must omit engine-owned evidence_schema_version, pattern_outcomes, and opportunity_coverage_identity; persistence derives all three.

The copyable talk above is a current scoring generation. A replayable legacy return that cannot prove the current evidence contract instead stores this mutually exclusive shape and omits both pattern_scoring_schema_version and pattern_catalog_fingerprint:

{
  "pattern_scoring_generation_status": "legacy_unbaselineable",
  "pattern_scoring_generation_reasons": [
    "comparison_group_ambiguous:gradual-consistency"
  ]
}

A fresh v5 worker uses the exact empty adherence sentinel and does not author a raw-score comparison. Only an owner-side consumer that sees the canonical talk outcomes may compare against a baseline carrying the same opportunity_coverage_identity. Baseline schema v2 keeps all fresh-v5 talks in eligible_talk_count; scored_talk_count is only the exact-identity raw-score cohort. Mixed identities use zero/null score aggregates plus explicit raw_score_comparison_status: "unavailable" and reason mixed_opportunity_coverage rather than normalizing unlike denominators. A non-empty cohort whose exhaustive outcome matrix contains no detected or undetected row also uses zero/null aggregates with reason no_evaluable_pattern_opportunities; missing opportunities must never publish an available zero average.

source_identity and source_relation are optional. Their owned shape, offline comparison rules, duplicate semantics, and compatibility policy are in source-identity-preflight.md. Do not fetch live metadata during validation. Capture provider evidence separately with the read-only flow in source-identity-audit.md, review it, then run the preflight. Uploader/upload date never establish speaker/recorded date, and a captured webpage URL is never an automatic active-source repair.

Queue eligibility is not encoded by video_url alone. One shared resolver derives auditable source_capabilities for queueing, return provenance, and terminal status checks. A local capability requires an artifact that the source-specific quality checker/parser/probe can actually read under the vault or configured source root; a non-empty, escaped, symlinked, missing, or malformed local path is not a capability. Active remote video/slide acquisition paths remain separate eligible capabilities because processing performs that acquisition. transcript_source: manual is provenance only and does not prove an artifact exists. Legacy no-video/no-transcript statuses normalize to skipped_no_sources only when the shared verified-local plus remote-acquisition capability list is empty.

Every fresh queue claim is schema v5 and carries exactly the required_return_schema_version and adherence_baseline fields shown above. The queue owner builds one baseline before mutating any selected talk, copies it unchanged to every batch member, and requires adherence_baseline.as_of to equal the canonical claimed_at. excluded_filenames is the sorted exact batch; exclusion happens before generation identity or score inspection so a talk's prior result cannot compare with itself. Only eligible talks stamped current with empty reasons and the baseline's exact catalog fingerprint/scoring schema contribute to eligible_talk_count. Exact opportunity identity additionally controls the raw-score cohort. Promoted and nested pattern scores must agree. Count and sum are integers; an available average uses decimal ROUND_HALF_EVEN to two places.

A closed claim adds released_at and release_reason; a completed claim also adds terminal result_status and the canonical result_payload_sha256 receipt. Those suffix fields are forbidden while state is claimed.

Claim records are immutable generation evidence. Idempotent replay returns the stored claim and leaves DB bytes unchanged. Recovery closes but preserves the same v5 snapshot; a later claim increments reprocess_generation and captures a fresh snapshot. Historical retry epochs may span _queue_claim_history and current _queue_claim locations, but their combined members must still match the baseline's exact excluded filenames and share one snapshot.

The four version axes are deliberately explicit:

ClaimAuthorized returnPersisted talkPattern scoring
v1 or v2saved v1 or v2 onlymigrated legacy recordnever current v5
v3v3 onlymigrated union-safe recordnever current v5
v4v4 onlyarchival source-located v4never current v5
v5v5 onlyv5v5 when canonical evidence/outcomes are fresh

Claim/return compatibility authorizes replay; it does not grant current scoring status. Only a v5 return canonicalized from current source artifacts can produce talk schema v5 with pattern_scoring_schema_version: 5, evidence ledger v2, exhaustive outcomes, and pattern_scoring_generation_status: "current". V1–v3 detections retain the explicit empty-citation legacy sentinel. V4 keeps its source locations and evidence ledger v1 but migration never fabricates v5 applicability assessments, outcomes, or opportunity identity.

improvement_goals is the coaching-loop artifact — speaker-chosen focus areas that a later ingress run verifies. vault-ingress owns the record shape and migrations; vault-clarification creates and retires current records. Ingress verification writes only the verification fields. Record schema, lifecycle, and writer/reader contract: ../../vault-clarification/references/schemas-config.md Improvement Goals Schema. Verification rubric: processing-rules.md Improvement Goal Verification.

Per-Talk Subagent Return Schema

Each subagent returns this JSON after processing one talk:

{
  "filename": "the .md filename",
  "return_schema_version": 5,
  "queue_claim": {
    "run_id": "copied from talk._queue_claim.run_id",
    "batch_id": "copied from talk._queue_claim.batch_id",
    "reprocess_generation": 1
  },
  "status": "processed|processed_partial|skipped_no_sources|skipped_download_failed|skipped_duplicate",
  "slide_source": "pptx|pdf|both|video_extracted|none",
  "slides_local_path": "slides/<artifact>.pdf  (optional; required for processed video_extracted)",
  "clear_fields": [
    "analysis-owned dotted paths disproved by this re-analysis; omit when none"
  ],
  "rhetoric_notes": "500-1000 words: qualitative observations across dimensions 1-13",
  "areas_for_improvement": "100-300 words: honest critical reflection (Dimension 14); name the related antipattern ID + severity per issue where a Dimension 14 antipattern applies",
  "transcript_source": "youtube_auto|whisper|manual  (how the transcript was obtained; OMIT the key entirely when provenance is unknown — see Absent transcript_source in the DB schema above)",
  "transcript_path": "transcripts/{id}.txt  (optional exact repeat of a pre-registered non-YouTube path; cannot introduce citation authority)",
  "structured_data": {
    "delivery_language": "en|de|ru|etc  (primary language of the talk)",
    "co_presenter": false,
    "co_presenters": ["Full Name; required and non-empty when co_presenter is true"],
    "slide_count": 60,
    "talk_duration_estimate": "35 min (from transcript length/pacing clues)",
    "meme_count": 15,
    "image_only_slide_count": 25,
    "audience_interaction_count": 3,
    "opening_type": "provocative_image|failure_framing|audience_poll|story|bold_claim|demo_cold_open",
    "closing_type": "summary_cta|callback|open_question|demo_finale|resource_list",
    "narrative_arc_type": "problem_diagnosis_solution|discovery_demo|chronological|listicle",
    "slide_design_style": "comic_book|minimal_dark|demo_scaffolding|mixed",
    "illustration_style": "name of dominant illustration aesthetic, or 'none'",
    "illustration_coherence": "unified|mixed|none",
    "image_source_distribution": {"ai_generated": 0, "speaker_created": 7, "stock_photo": 0, "unknown": 28, "none": 12},
    "image_source_distribution_basis": "Unit: slide; classify each slide by its dominant image source using asset manifests; origins without provenance count as unknown.",
    "visual_continuity_devices": ["FIG_numbering", "progressive_form", "recurring_mascot"],
    "opening_sequence": ["title", "provocative_hook", "bio", "shownotes_url", "first_argument"],
    "closing_sequence": ["summary_bullets", "cta_with_qr", "thanks_with_humor"],
    "color_coded_backgrounds": {
      "purple_halftone": "slide numbers and semantic register"
    },
    "background_color_sequence": ["purple", "white", "red", "yellow", "...for every slide"],
    "per_slide_visual": [
      {
        "slide_number": 1,
        "background_color_name": "purple_halftone|red_halftone|yellow_halftone|etc",
        "content_type": "title|bio|shownotes|content_bullets|data_chart|quote|meme_only|meme_with_text|section_divider|progressive_reveal|comparison_table|hot_take|cta|thanks",
        "image_composition": "full_bleed|full_bleed_with_text|image_left_text_right|image_right_text_left|centered_image_with_title|inset_image|progressive_reveal|screenshot|meme_with_caption|none",
        "has_speech_bubble": false,
        "has_starburst": false,
        "has_footer": true
      }
    ],
    "typography_observations": {
      "title_font_description": "hand-lettered comic style, appears to be...",
      "body_font_description": "...",
      "bullet_character": "multiplication_sign|dash|circle|custom",
      "title_color_adapts_to_background": true
    },
    "footer_observations": {
      "element_count": 4,
      "separator_character": "|",
      "footer_color_adapts_to_background": false,
      "watermark_present": true,
      "watermark_description": "description of any corporate/sponsor logo or branding"
    },
    "shape_observations": {
      "speech_bubble_slides": [1, 15, 42],
      "starburst_slides": [8, 23, 55],
      "speech_bubble_description": "white fill, black outline, tail pointing down-left",
      "starburst_description": "red fill, white text, explosion/irregular star shape"
    },
    "key_data_points": {},
    "named_authorities": {},
    "time_bound_promotion": {},
    "native_deck_audit": {},
    "native_timing_audit": {},
    "source_comparison": {},
    "source_identity": {},
    "animation_observations": {},
    "pptx_pdf_reconciliation": {},
    "extensions": {
      "producer_namespace": {"additive extension data": true}
    }
  },
  "verbatim_examples": {
    "signature_phrases": ["actual phrases from transcript, e.g. 'is not a thing'"],
    "jokes": ["verbatim joke/humor lines from transcript"],
    "transitions": ["actual transition phrases, e.g. 'Next thing you know...'"],
    "audience_addresses": ["how speaker addresses audience, e.g. 'raise your hand if...'"],
    "opening_lines": ["first 2-3 sentences of the talk, verbatim"],
    "closing_lines": ["last 2-3 sentences of the talk, verbatim"]
  },
  "adherence_assessment": "",
  "new_patterns": "100-300 words on NEW patterns not in summary, or ''",
  "summary_updates": "50-200 words: additions for rhetoric-style-summary.md by section #, or ''",
  "pattern_observations": {
    "evidence_sources": [
      "every source actually inspected: static_slides|native_deck|delivery_video|transcript|source_comparison"
    ],
    "source_inspection": [
      {"source": "transcript", "line_ranges": [[1, 240]]},
      {"source": "static_slides", "page_ranges": [[1, 60]]},
      {"source": "native_deck", "page_ranges": [[1, 60]]},
      {"source": "delivery_video", "time_ranges": [[0, 1800.0]]},
      {
        "source": "source_comparison",
        "evidence_sources_used": ["static_slides", "native_deck"],
        "comparison_scope": "full"
      },
      {
        "source": "source_comparison",
        "evidence_sources_used": ["transcript", "delivery_video"],
        "comparison_scope": "partial"
      }
    ],
    "patterns_detected": [
      {
        "pattern_id": "progressive-reveal",
        "confidence": "strong|moderate|weak",
        "evidence_source": "static_slides|native_deck|delivery_video|transcript|source_comparison",
        "evidence": "Three consecutive slides add one element at a time.",
        "evidence_citations": [
          {"source": "native_deck", "channel": "slide_sequence", "slide_numbers": [21, 22, 23]}
        ]
      }
    ],
    "antipatterns_detected": [
      {
        "pattern_id": "shortchanged",
        "confidence": "strong|moderate|weak",
        "evidence_source": "static_slides|native_deck|delivery_video|transcript|source_comparison",
        "evidence": "The talk announces the close before beginning a new topic.",
        "evidence_citations": [
          {"source": "transcript", "channel": "timed_transcript", "quote": "Before I finish, there is one more architecture topic."}
        ]
      }
    ],
    "applicability_assessments": [
      {
        "pattern_id": "pattern-with-applicability-contract",
        "result": "not_applicable",
        "condition_id": "catalog-owned-condition-id",
        "evidence_source": "transcript",
        "evidence": "The complete transcript establishes the catalog-owned condition.",
        "evidence_citations": [
          {"source": "transcript", "channel": "transcript", "quote": "A unique source-language span of at least four words"}
        ]
      }
    ],
    "not_evaluable": [
      {
        "pattern_id": "composite-animation",
        "reason_code": "missing_required_source_coverage"
      },
      {
        "pattern_id": "catalog-entry-awaiting-owner-gate",
        "reason_code": "source_gate_pending_owner_review"
      },
      {
        "pattern_id": "positive-only-pattern",
        "reason_code": "absence_not_authorized_by_catalog"
      },
      {
        "pattern_id": "conditional-pattern-with-incomplete-coverage",
        "reason_code": "missing_applicability_source_coverage"
      }
    ],
    "pattern_score": {
      "patterns_used": 8,
      "antipatterns_detected": 2,
      "score": 6
    }
  },
  "catalog_feedback": {
    "unmatched_observations": [{
      "observation": "Observed move with no exact catalog fit",
      "why_no_pattern_fits": "Boundaries checked and why each fails",
      "proposed_name": "new-pattern-name",
      "proposed_polarity": "pattern|antipattern"
    }],
    "tensions": [{
      "pattern_ids": ["exact-pattern-id", "exact-antipattern-id"],
      "nature": "How the entries trade against one another",
      "evidence": "Talk-specific evidence"
    }],
    "definition_problems": [{
      "pattern_id": "exact-catalog-id",
      "problem": "ambiguous|undetectable|unfalsifiable|miscategorized|overlapping",
      "detail": "Why the documented boundary cannot be applied"
    }],
    "scoring_problems": [{
      "issue": "Model-level scoring defect",
      "detail": "Evidence and consequence"
    }],
    "confusable_pairs": [{
      "pattern_ids": ["first-exact-id", "second-exact-id"],
      "detail": "The missing discriminator"
    }]
  }
}

A source_comparison detection or applicability assessment adds "evidence_sources_used": ["static_slides", "native_deck"] (or another exact qualifying catalog group). Return v5 enforces this proof structurally. The field is forbidden on non-comparison records. Only replayed v1–v3 artifacts may omit it, and persistence infers the proof only when exactly one pair qualifies.

per_slide_visual, when present, is a closed, complete slide ledger. It requires a positive integer slide_count and exactly that many rows in ascending order, with slide_number covering every integer from 1 through slide_count once. Every row has exactly the seven keys shown above; aliases and extra keys are rejected. background_color_name is an open non-empty label so a newly observed palette can be named. content_type and image_composition use the closed vocabularies shown above, and the three has_* values are booleans. background_color_sequence, when supplied, must reproduce the row background labels in order. meme_count, when supplied with the ledger, must equal the number of meme_only plus meme_with_text rows. No equivalent row-derived check exists for image_only_slide_count: visible text baked into an image distinguishes that measure from image composition and meme classification.

image_source_distribution is strictly a count map: each non-empty string key names a source/provenance class and each value is a non-negative integer. Visual appearance does not establish authorship. Do not infer ai_generated, stock_photo, or another origin from style alone; count unverified origins as unknown. Content/format labels such as “meme” and “screenshot” are not authorship provenance, and free-form entries such as classification_note do not belong in this map. If observable visual categories need their own map, introduce a distinct schema field rather than mixing notes or category metadata into source counts. Whenever the map is present, its sibling image_source_distribution_basis is required and must be a non-empty string. The basis states the counting unit (slide, page, or asset), the classification rule including how a dominant class is selected, the provenance evidence used, and how unverified origins are counted as unknown. Both fields are authored-slide evidence and cannot be supplied from untrusted video context.

The worker matches the active claim contract. Every fresh claim is schema v5 with required_return_schema_version: 5, and only that exact claim authorizes a v5 return. Saved claim schemas v1/v2 authorize only return schemas v1/v2; schema v3 authorizes only v3; schema v4 authorizes only archival v4. Recover a live legacy lease and issue a new v5 generation; never mutate its claim to make a newer return appear compatible.

For newly emitted work, validate-returns.py must report the processed talk's scoring-generation status as current; a valid but legacy_unbaselineable result is replay-only and must be repaired.

Versions 2–5 share the complete-snapshot merge contract: supplied declared scalar and list fields replace prior values, including empties only where the field contract permits emptiness; complete structured maps and each verbatim lane replace their prior snapshots; omitted fields remain untouched. The image-source distribution and its basis form one dependent group. Unregistered incoming structured objects fail closed instead of acquiring accidental recursive-merge semantics. Historical returns with no version field, or with explicit version 1, retain the legacy additive merge contract so saved artifacts remain replayable. Unknown future versions are rejected.

The structured snapshot objects currently registered for atomic replacement are image_source_distribution, color_coded_backgrounds, typography_observations, footer_observations, shape_observations, video_extraction, key_data_points, named_authorities, time_bound_promotion, native_deck_audit, native_timing_audit, source_comparison, source_identity, animation_observations, and pptx_pdf_reconciliation; per_slide_visual is the corresponding atomic array. Their complete nested contents come from the current analysis, so no child from an older run survives. Experimental recursively additive data must live under the explicit structured_data.extensions object. A new top-level object needs a named policy here and in STRUCTURED_FIELD_POLICIES before a snapshot return may use it. The six documented verbatim_examples lanes are exact: a stale undeclared lane makes the effective snapshot candidate invalid until clear_fields removes it. A valid snapshot verbatim object may still repair a legacy non-object container atomically.

Every processed return carries the required top-level analysis blocks and the complete required pattern_observations fields. Individual structured_data fields and verbatim_examples lanes remain optional for partial-return and legacy compatibility: omission preserves the prior field, while a supplied empty value records that the current analysis found none. Required prose fields use an empty string only for adherence_assessment, new_patterns, and summary_updates. For adherence_assessment, the no-assessment sentinel is exactly ""; whitespace-only text is invalid. Fresh return v5 uses exact adherence_assessment: "" and omits adherence_comparison. The worker cannot know the engine-owned canonical talk opportunity_coverage_identity. An owner-side consumer may construct a numeric comparison only after persistence, only when the talk identity exactly equals a schema-v2 baseline identity, raw_score_comparison_status is available, and the baseline has at least ten exact-identity scored talks. Comparisons from return v1–v4 are archival only and never verified current numeric evidence. Versions 2–5 require rhetoric_notes and areas_for_improvement to contain substantive non-whitespace analysis. An unknown transcript_source is omitted; a present value must be one of the declared enums and must never be JSON null. Missing/version-1 returns retain their historical type-only and empty-value no-op behavior. A skipped terminal return may contain only filename, return_schema_version, queue_claim, and status. Both writers reject a missing/unknown status or a return whose queue generation does not match the talk's active claim. Returns should omit processed_date: the persistence writer's normalized batch --run-date (or generated UTC timestamp) owns that field. A legacy return-side value remains accepted for compatibility but cannot override persistence or rendered provenance. Date-only values are advisory; a full timezone-aware return timestamp is an explicit assertion and must normalize to the authoritative batch stamp or both writers reject it.

The return filenames must exactly equal every tracking-DB member carrying the same run_id and batch_id, with each member's own generation matching its claim. Partial, superset, mixed-identity, duplicate, or lifecycle-split batches fail before either artifact changes. persist-results.py requires the whole batch in claimed state and closes it as completed; write-analysis.py requires that same whole batch in completed state. A genuinely one-member batch is complete and remains supported. A partially closed or stranded batch must be recovered into a fresh queue generation rather than finished piecemeal. For claim v3–v5, every live batch member must also share one canonical claimed_at, one identical baseline, and an excluded_filenames array equal to the exact sorted batch. Persistence validates all of those conditions before the first candidate merge; one mismatch leaves both DB and analysis artifacts unchanged.

Queue-claim schema v2 adds result_payload_sha256 to completed claims; schema v3 adds the required-return version and immutable adherence snapshot. Schema v4 freezes those fields to the source-located return-v4/scoring-v4 contract. Schema v5 carries the v5 return/scoring contract and schema-v2 baseline. The receipt hashes the exact return payload after stable JSON key/whitespace canonicalization. persist-results.py closes v1 as v2 and closes v2–v5 at their own versions, storing the receipt for every completed v2–v5 claim. The analysis writer recomputes it and rejects a substituted payload. queue-state.py reads v1–v5 without mutating inspect or idempotent replay. An already completed v1 claim has no reconstructable receipt and therefore cannot authorize an analysis replacement until a fresh generation is processed. Unknown future claim versions fail closed.

Recovery never rewrites a claim snapshot. It marks the generation closed and restores its prior claimable status; reclaiming creates a new generation with a fresh pre-mutation baseline. A historical v3/v4/v5 batch may therefore be split across current and history storage locations, but the combined (run_id, batch_id, claimed_at) epoch must still have exact membership and one baseline.

Terminal skip reasons are state-bound too. skipped_no_sources requires an empty capability list. skipped_download_failed requires a remote video/slide acquisition path and no remaining verified local transcript, PPTX, PDF, or video artifact; a stale local declaration does not block that terminal result. skipped_duplicate requires source_relation.type: duplicate plus a non-empty target_filename.

Before rendering a processed result, write-analysis.py recomputes the scoring generation from the receipt-bound return and current catalog. A current result must carry pattern_scoring_generation_status: current, an empty reasons array, scoring schema 5, and the exact catalog fingerprint. A replayable v1–v4 result that cannot prove the current evidence contract carries legacy_unbaselineable plus exact sorted machine reasons and must not retain a current scoring version or fingerprint. Its Markdown visibly labels adherence prose legacy-unverified and states that it is excluded from current numeric baselines, Section 15 aggregates, and speaker profiles. A v2–v4 snapshot replay also clears any stale authenticated adherence_comparison from a prior generation. Skipped results are not_applicable in validator and persistence reports and do not render or restamp prior analysis-generation metadata.

After all members merge successfully, persist-results.py emits current_adherence_baseline on stdout. It uses baseline schema version 2 and is explicitly all-inclusive: active_batch_excluded: false and excluded_filenames: []. Its as_of is the authoritative completion stamp. eligible_talk_count describes every fresh-v5 candidate; score count/sum/average describe only one exact opportunity-identity cohort. Mixed identities make the raw-score comparison unavailable with zero/null score aggregates while retaining the full per-pattern opportunity cohort. A shared identity with no evaluable outcome uses the same zero/null sentinel with no_evaluable_pattern_opportunities rather than publishing an available 0.0. Section 15 and profile generation consume exact current-generation talk data and this post-batch aggregate; they must not recompute after member 1, use a processing-date cohort, or mutate a preclaim baseline.

The completed return receipt authorizes rendering, but snapshot analysis-owned content comes from the validated persisted effective talk, not the partial raw return. This is the single canonical merged payload: a structured field or verbatim lane omitted by the return and preserved by persistence remains present in Markdown. catalog_feedback is the sole receipt-bound rendering side channel read directly from the return because it is intentionally not stored on the talk.

Analysis replacement is batch-transactional. The writer preflights every target, including normalized/case-fold collisions with existing output-directory entries and exact directory/special-file targets, then stages every body before the first replacement. Existing targets move to same-directory recovery backups during commit; a later failure restores them in reverse order. Exact target symlinks are moved/replaced as directory entries, so their external targets are never followed.

slides_local_path is a top-level analysis provenance scalar. Returns use the portable canonical form slides/<artifact>.pdf; persistence copies it to the talk record and the analysis writer renders it in the provenance header. For slide_source: "video_extracted", the filename must be slides/{structured_data.video_extraction.source_video_id}.pdf. status: "processed" requires that path plus a complete schema-v3 manifest whose top-level crop provenance and slide_region artifact independently agree on a verified manual crop. The return's manifest identity is also matched against the claimed talk's youtube_id before either writer changes state.

Any video-extracted return without a promoted artifact must omit slides_local_path, include it in clear_fields, and cannot finish processed. A trusted but unpromoted verified slide_region may still supply static_slides evidence to a processed_partial return. An untrusted manifest is context-only: do not list static_slides and do not return authored-slide structured evidence. A full_frame_context artifact may still qualify as delivery_video evidence for room, speaker, PiP, and delivery/timing phenomena that it actually establishes; its scope can never be promoted into authored-slide evidence.

clear_fields explicitly deletes prior analysis before the return is applied. Allowed paths are top-level analysis prose/provenance scalars or leaves under structured_data, verbatim_examples, and pattern_observations. It cannot clear queue identity, source URLs, catalog metadata, or the talk record itself. Clearing a promoted structured scalar clears its top-level copy too. A supplied v2–v5 replacement wins after a clear; permitted empty values are real snapshots, not no-ops. Legacy v1 empty values retain their historical additive no-op behavior.

evidence_source uses the enum defined by the pattern index's Evidence-Source Contract. Detected entries must name a qualifying source. Strong detections use strong_evaluable_from (defaulting to evaluable_from); moderate/weak detections use the base gate. A source_comparison detection must name both sources in its evidence. Every v4/v5 return carries a duplicate-free evidence_sources_used array exactly equal to one qualifying underlying group. Saved v1–v3 replay may omit that array; persistence infers it only when exactly one pair qualifies. Zero or multiple qualifying groups remain replayable but are excluded from current baselines. The source_comparison marker does not count as an underlying source and is forbidden as a catalog gate member or singleton.

For an undetected entry, absence_evaluable_from defaults to the base gate. V4 absence remains archival and is never current. In v5, complete canonical inspection coverage is necessary but never sufficient to authorize absence: the persistence engine must also derive absence_capability_complete: true for the current source role, and the entry's absence_evaluable_from singleton gate must match that complete source. An unsatisfied gate requires exactly {"pattern_id": "...", "reason_code": "missing_required_source_coverage"}. An explicit null absence gate requires absence_not_authorized_by_catalog and keeps the entry positive-only. In v5, incomplete applicability coverage requires missing_applicability_source_coverage; complete applicability coverage requires exactly one source-located assessment for every nondetected conditional entry. An observable entry with no owner-approved gate requires source_gate_pending_owner_review; it cannot be detected or silently counted as absent. This is fail-closed catalog debt, not a model waiver. A valid positive detection takes precedence for a gated entry. Not-evaluable entries are excluded from pattern_ids, antipattern_ids, and every pattern_score count. Never put an unavailable entry in a detected array or treat it as an absent pattern.

catalog_feedback is mandatory on current processed returns and uses only the five lanes shown above (empty arrays are valid). Exact IDs and pattern/antipattern polarity are validated against catalog YAML; new suggested names occupy a separate namespace and carry proposed_polarity. The read-only aggregator also audits historical returns, reports legacy compatibility issues without silently repairing them, and preserves per-entry provenance. Its owned schema and aggregation contract are in catalog-feedback-intake.md.

Source Inspection Receipt Schema

Every return v4/v5 carries pattern_observations.source_inspection. Its source-name set exactly equals evidence_sources; comparison records may repeat the source_comparison name only for distinct underlying groups. Worker-authored records are closed objects:

{"source": "transcript", "line_ranges": [[1, 120], [121, 240]]}
{"source": "static_slides", "page_ranges": [[1, 20], [25, 60]]}
{"source": "native_deck", "page_ranges": [[1, 60]]}
{"source": "delivery_video", "time_ranges": [[0, 900.0], [905.0, 1800.0]]}
{"source": "source_comparison", "evidence_sources_used": ["static_slides", "native_deck"], "comparison_scope": "full"}
{"source": "source_comparison", "evidence_sources_used": ["transcript", "delivery_video"], "comparison_scope": "partial"}

Line/page ranges are inclusive positive integers. Time ranges are finite non-negative seconds with end > start. In all three lanes, ranges are ordered, non-overlapping, and may be adjacent. Persistence reads the exact artifacts to derive their line/page count or video duration. Coverage is complete only when the ranges start at 1 (or time 0), reach the verified final bound, and contain no gap. A comparison's range receipt is complete only when comparison_scope is full and every named underlying source has complete range coverage. That remains positive evidence; neither full nor partial comparison proves an undetected or applicability outcome until a future canonical receipt establishes aligned modality capture.

native_deck and static_slides are distinct evidence sources. Reading or extracting a .pptx establishes only native_deck; it never silently creates a rendered-page receipt or authorizes static-slide absence. A real PDF, a trusted video-extracted slide artifact, or a stable PDF exported from the exact PPTX may establish positive static_slides evidence only when the concrete artifact is actually inspected and identity-bound in canonical persistence. Video-extracted static pages, bare native_deck, and bare delivery_video are positive-only; their current receipts do not prove exhaustive modality capture. A genuine authored/rendered PDF may be absence-capable when its catalog gate permits it.

Canonical rows make the distinction auditable. coverage_complete is locator range completeness. absence_capability_complete is the independent engine-owned negative/applicability gate, and absence_capability_reason carries its stable reason (authorized_transcript, authorized_rendered_static, nonexhaustive_video_extraction, bare_native_deck, bare_delivery_video, comparison_alignment_unverified, or incomplete_range_coverage). Workers must not return either absence-capability field.

Workers never return canonical receipt enrichment. Persistence adds artifact_root, vault/root-relative artifact_path, artifact_sha256, optional timing-artifact identity, required quality-artifact identity for current v4/v5 transcript evidence, derived line_count/page_count/duration_seconds, and coverage_complete; comparison records add artifact_identities. Current cohort readers re-hash these identities and fail stale, missing, symlinked, relocated, or owner-path-drifted evidence closed. Transcript freshness also re-runs the hash-bound quality policy against the current owner/provider duration; a material identity-duration change yields transcript_quality_context_drift even when the transcript and sidecar bytes themselves did not change. Native-deck freshness likewise requires a current native_deck_audit, binds its PPTX digest, size, and slide count to the current bounded probe and canonical inspection, and binds any rendered-page receipt to the current bounded PDF plus the exact persisted static-slide inspection ranges. Missing, obsolete, wrong-lane, or artifact-disconnected audits requeue the talk.

Pattern Evidence Citation Schema

evidence remains the concise human explanation. evidence_citations is the auditable proof. Every newly returned detection requires one or more citations; persist-results.py rejects missing citations, unknown or duplicate pattern IDs, pattern/antipattern bucket swaps, observable: false patterns, and citation channels not permitted by that pattern's required evidence_channels frontmatter. An observable catalog entry without that field is itself invalid and stops persistence.

A permitted citation channel is necessary but not sufficient. Every citation's source names the underlying member it locates or supplements, and its channel must be compatible with that source. The detection's evidence_source must independently satisfy its effective source/outcome gate, and at least one citation must locate proof from that source: transcript evidence uses transcript or timed_transcript, static/native deck evidence uses slides or slide_sequence, and delivery evidence uses video. A source_comparison detection must cite every member named by evidence_sources_used. talk_metadata may supplement those citations but cannot replace the qualifying gate source.

Allowed citation shapes:

{"source": "transcript", "channel": "transcript", "quote": "A unique source-language span of at least four words", "translation": "Required English translation for non-English delivery; otherwise optional"}
{"source": "transcript", "channel": "timed_transcript", "quote": "A unique source-language span of at least four words", "translation": "Required English translation for non-English delivery; otherwise optional"}
{"source": "static_slides", "channel": "slides", "slide_numbers": [4, 17]}
{"source": "native_deck", "channel": "slide_sequence", "slide_numbers": [21, 22, 23]}
{"source": "delivery_video", "channel": "video", "start_seconds": 42.5, "end_seconds": 48.0}
{"source": "delivery_video", "channel": "talk_metadata", "field": "slide_count"}

Those are the complete worker-side shapes. A worker supplies the source/channel and the smallest source locator it can actually claim: quote, slide numbers, video interval, or metadata field. It must not copy line_start, line_end, transcript start_seconds/end_seconds, artifact root/path/hash fields, timing/quality-artifact fields, metadata value/owner_value_after_return, or any other canonical enrichment from an earlier analysis. Unknown raw citation fields are rejected. Catalog dimensions are likewise engine-owned; workers should omit them, although a supplied v4/v5 dimensions array is accepted only when it exactly matches catalog order.

For transcript citations, quote is always the exact source-language text needed for matching. When either preclaim metadata or the validated return's structured_data.delivery_language identifies non-English delivery, a non-empty English translation is required so readers still see English first. It remains optional for English delivery. The model never supplies a translated composite string as quote, because that string does not occur in the source transcript. persist-results.py verifies that the normalized quote occurs exactly once in the local transcript and stamps line_start/line_end; for timed_transcript, it also stamps start_seconds/end_seconds from a verified timing sidecar. Model-supplied locations are discarded. A slide_sequence must contain at least two consecutive ascending slide numbers. Slide numbers are checked against an independently resolved slide artifact/count. A video citation is valid only when the video was directly reviewed at that interval; the writer binds its range to an identity-bound local or timed artifact and checks the verified duration bound. A video URL alone cannot verify a timestamp. talk_metadata.value and owner_value_after_return are likewise writer-owned: the former records the pre-return source value and the latter binds freshness to the persisted owner value after the return is applied. Citation objects use these closed field sets; unknown model-supplied fields are rejected. talk_metadata.field is restricted to source/provenance fields declared by persist-results.py's TALK_METADATA_FIELDS and then to the pattern's narrower evidence_metadata_fields; generated prose such as rhetoric_notes cannot cite itself, and an irrelevant metadata field cannot stand in for pattern evidence.

Historical v1–v3 records may contain evidence_citations: []. That is a deliberate legacy marker: readers may render the old evidence prose, but must not present it as source-verified. The v4/v5 writer never accepts an empty array for a new detection. evidence_schema_version is writer-owned persisted state; workers must not return it, and legacy detections never acquire it by migration.

The same boundary applies to not_evaluable. Workers return only pattern_id and one exact current reason code. Persistence derives required_source_groups, available_source_groups, and capability_fact from the catalog and canonical inspection receipt. It also injects catalog dimensions and canonical slide count where applicable. The raw-return receipt remains the hash of exactly what the worker sent; canonical enrichment is deterministic and does not alter that receipt.

Transcript Timing and Quality Receipt Schemas

fetch-transcript.py and vtt-cleanup.py keep the readable transcript at transcripts/{id}.txt. When timing is trustworthy they also write transcripts/{id}.segments.json; otherwise a fresh/forced bundle removes any older timing sidecar. This closed receipt owns acquisition identity and timing only:

{
  "schema_version": 2,
  "transcript_sha256": "SHA-256 of the exact on-disk transcript bytes",
  "source": "captions|whisper|vtt",
  "provenance": {
    "kind": "youtube_captions",
    "video_id": "dQw4w9WgXcQ",
    "duration_seconds": 212.125
  },
  "segments": [
    {"text": "Timed source text", "start_seconds": 1.2, "end_seconds": 3.4}
  ]
}

The top-level keys are exact. provenance is exactly one compatible shape:

{"kind": "youtube_captions", "video_id": "dQw4w9WgXcQ", "duration_seconds": 212.125}
{"kind": "youtube_whisper", "video_id": "dQw4w9WgXcQ", "duration_seconds": 212.125}
{"kind": "local_media_whisper", "media_sha256": "64 lowercase hex characters", "duration_seconds": 212.125}
{"kind": "vtt_artifact", "artifact_path": "source.en.vtt", "artifact_sha256": "64 lowercase hex characters", "cue_extent_seconds": 212.125}

YouTube and local-media timing require a positive trusted duration. The VTT path is a safe transcript-directory-relative POSIX path to a non-symlink regular file; the digest binds its exact bytes and cue extent equals the final segment boundary. Every segment is canonical, joined segment text equals the transcript modulo Unicode whitespace layout, and no segment extends past its source-owned duration beyond the reader's one-second measurement tolerance. vtt-cleanup.py therefore requires both input and explicit output paths; an existing output bundle is preserved unless --force authorizes replacement.

fetch-transcript.py separately writes transcripts/{id}.quality.json. This closed receipt owns quality authority even when no timed segments exist:

{
  "schema_version": 1,
  "transcript_sha256": "SHA-256 of the exact on-disk transcript bytes",
  "policy": {
    "schema_version": 1,
    "min_words": 400,
    "duration_seconds": null
  },
  "provenance": {"kind": "fixed_default"}
}

The only other provenance forms are exact duration-bound objects:

{"kind": "youtube_duration", "video_id": "dQw4w9WgXcQ", "duration_seconds": 212.125}
{"kind": "local_media_duration", "media_sha256": "64 lowercase hex characters", "duration_seconds": 212.125}

The policy keys are exactly schema_version, min_words, and duration_seconds; policy schema is 1. min_words is the canonical floor actually applied. With duration_seconds: null, it is at least 400. A trusted short duration may derive a lower floor at 30 words per minute; an invocation value below that derived floor cannot lower it, while any value above the derived floor tightens it. A duration-bearing provenance object must repeat the policy duration exactly. youtube_duration.video_id binds to the owning YouTube talk; local_media_duration.media_sha256 binds to exact local-media bytes.

The owner of both receipt shapes is skills/vault-ingress/scripts/transcript_timing.py; current timing schema is 2 and quality-receipt/policy schema remains 1. Readers hash raw .txt bytes, never decoded/newline-normalized text. Any byte replacement, including CRLF→LF, invalidates both receipts.

Missing, malformed, owner-mismatched, text-incomplete, over-bound, or hash-stale timing leaves the plain transcript readable but makes timed_transcript evidence unavailable. Never copy timestamps from a stale timing receipt or silently downgrade a pattern whose semantics require timing. Writers do not emit empty timing receipts: a fresh or forced semantic bundle with no usable timing removes the old sidecar and keeps timed_path: null.

Timing schema v1 and minimal sidecars are archival only. Their missing owner artifact and duration bounds cannot be inferred safely, so they cannot supply timing or promote transcript provenance. There is no automatic in-place migration. Re-fetch/re-transcribe from the proved owner source, or re-import the original VTT file, to regenerate schema v2. Missing timing remains optional for ordinary transcript evidence; the independent schema-v1 quality receipt stays valid when its exact transcript bytes and owner context remain current.

Quality availability is independent. A successful fetch or existing-artifact validation returns quality_path for a current receipt even when timed_path is null. Missing legacy quality is unverified and must be revalidated before v5 scoring; malformed, hash-stale, wrong-owner, wrong-media, or duration-drifted quality fails closed. Worker-returned duration or talk analysis metadata is never quality authority. A stored policy is revalidated against its owner; tightening a caller's --min-words can reject existing text but cannot authorize replacement. Bundle writers stage transcript, timing deletion/replacement, and quality together. A caught failure rolls every attempted path back to its exact prior bytes.

For an already-valid transcript, caption timing enrichment is deliberately non-destructive. Pass the owner's known provenance to fetch-transcript.py via --existing-source. Only a known youtube_auto transcript may acquire a new caption timing receipt, and only when the fetched caption text differs from the existing UTF-8 text by Unicode whitespace alone. The script then writes only the hash-bound timing sidecar and preserves the transcript bytes exactly. Manual, Whisper, unknown-provenance, or text-mismatched transcripts remain untimed; they are never relabeled or overwritten by the enrichment path. The talk's recorded transcript_source remains canonical even when a sidecar is valid; timing receipts can confirm matching ownership but cannot rewrite it.

Fresh provider text may still be valid when optional segment timing is not. The fetcher prevalidates timing and, on malformed segments, transcript-text mismatch, or a source-bound violation, writes the semantic transcript and quality receipt while removing stale timing in the same transaction. Direct write_timing_receipt calls remain strict and reject those payloads.

Video Extraction Output Schema

Produced by skills/vault-ingress/scripts/video-slide-extraction.py. Stored in structured_data.video_extraction on the talk entry:

{
  "slide_source": "video_extracted",
  "schema_version": 3,
  "pipeline_version": "0.12.0",
  "source_video_id": "AbCdEfGhI_1",
  "source_video_path": "/vault/slides-rebuild/AbCdEfGhI_1/AbCdEfGhI_1.mp4",
  "total_frames_extracted": 1500,
  "unique_frame_count": 85,
  "authored_slide_count": null,
  "hash_threshold_used": 8,
  "slide_region_detected": true,
  "slide_region_applied": true,
  "slide_region_method": "manual",
  "slide_region_verified": true,
  "slide_region": [0.05, 0.02, 0.78, 0.98],
  "fps_used": 0.5,
  "retained_frames": [
    {"page_number": 1, "frame_index": 0, "timestamp_seconds": 0.0},
    {"page_number": 2, "frame_index": 6, "timestamp_seconds": 12.0}
  ],
  "artifacts": [
    {
      "path": "/vault/slides-rebuild/AbCdEfGhI_1/AbCdEfGhI_1.slide-region.pdf",
      "artifact_scope": "slide_region",
      "page_count": 85,
      "source_video_id": "AbCdEfGhI_1",
      "source_video_path": "/vault/slides-rebuild/AbCdEfGhI_1/AbCdEfGhI_1.mp4",
      "crop_method": "manual",
      "crop_verified": true,
      "trusted_for_authored_slide_analysis": true
    },
    {
      "path": "/vault/slides-rebuild/AbCdEfGhI_1/AbCdEfGhI_1.context.pdf",
      "artifact_scope": "full_frame_context",
      "page_count": 85,
      "source_video_id": "AbCdEfGhI_1",
      "source_video_path": "/vault/slides-rebuild/AbCdEfGhI_1/AbCdEfGhI_1.mp4",
      "crop_method": "none",
      "crop_verified": false,
      "trusted_for_authored_slide_analysis": false
    }
  ],
  "review_required": false,
  "review_reason": null
}

The owner of this record's shape is skills/vault-ingress/scripts/video-slide-extraction.py. Two version fields track two independent axes:

  • schema_version (integer) — the record's field shape. Current value: 3. The script bumps it on any field add/remove/rename. Reader contract: a record with no schema_version is the legacy pre-versioning shape — treat it as schema_version 0 and read the fields that are present; a record with a schema_version higher than the reader accepts is "no usable prior state" (re-extract to refresh). Readers never migrate in place — the owner script rewrites the record on the next extraction.
  • pipeline_version (string) — the extractor behavior (PIPELINE_VERSION) that produced the entry. The script bumps it when extraction behavior changes (see skills/vault-ingress/references/video-slide-extraction.md — "Pipeline Versioning"). The same value is mirrored in the output PDF's producer/creator metadata. A pre-versioning entry has no pipeline_version.

Version 2 added crop provenance. slide_region_detected is true only when the auto-detector returned a region; it is false for a manual region. slide_region_applied says whether any crop was used for hashing, slide_region_method records auto, manual, or none, and slide_region_verified is true only when the operator explicitly marked a manual crop as visually checked. For a version-1 record, readers may infer method auto, applied from whether slide_region is present, and verified false; re-extraction is still required before treating an old crop as verified.

Version 3 separates derived artifacts by provenance and scope. artifacts[].path and source_video_path are native absolute, symlink-resolved paths at extraction time. They must remain within the configured vault storage root, contain no NUL, raw dot segments, ~, device/current-drive syntax, foreign absolute flavor, or dual-flavor // form, and name non-symlink descendants. Persisted relative artifact locators elsewhere in the database use canonical / separators and exclude Win32-trimmed components, alternate-stream syntax, and reserved DOS device basenames. They are joined as logical root-relative components, never reinterpreted through the host's alternate separator or namespace rules. Before a current return is persisted, every manifest PDF—not only the trusted slide_region—must pass the bounded exact-generation PDF probe and match its recorded page count. A configured canonical vault symlink is admitted as the trusted root locator and mapped to its storage target; descendant symlinks remain forbidden. artifact_scope is one of:

  • slide_region — pages are physically cropped to the selected region. This is trusted for authored-slide analysis only when crop_method is manual, crop_verified is true, trusted_for_authored_slide_analysis is true, and top-level review_required is false.
  • full_frame_context — uncropped broadcast frames for room, stage, speaker, or PiP analysis. This is never an authored deck and is never a source for slide design, authored slide count, or slide-pattern claims.

Ingress validates the manifest as one referential unit before trusting it: schema and pipeline versions are present; source and artifact identities agree; normalized region geometry agrees with slide_region_method, slide_region_applied, slide_region_detected, and slide_region_verified; retained-frame and artifact page counts agree with unique_frame_count; and artifact scope, crop method, verification, and trust flags are mutually consistent. review_required: false is accepted only for a verified manual slide_region; setting one optimistic flag cannot turn a context PDF into a deck. Persistence replaces this complete owner-versioned manifest rather than deep-merging it, so obsolete v1/v2 fields cannot survive inside a schema-v3 record.

retained_frames maps each PDF page to the zero-based index in the sampled frame sequence and its approximate video timestamp (frame_index / fps_used). Both artifacts use the same page order. unique_frame_count is the number of retained samples and each artifact's page_count; it is not an authored slide count. The extractor deliberately leaves authored_slide_count null. Populate the talk's queryable slide_count only from corroborated deck numbering, a native deck, or another authored source.

An unverified auto or manual crop may still produce a slide_region candidate, but it must carry trusted_for_authored_slide_analysis: false and review_required: true. Visually inspect it against the source/context, then rerun with the checked coordinates and --region-verified; do not promote the candidate to slides/{youtube_id}.pdf or slides_local_path. A version-2 output_pdf may contain uncropped broadcast frames even when a crop was applied, and its unique_slides_count was actually a retained-frame count. Treat both fields as legacy, untrusted evidence and re-extract rather than inferring version-3 artifact scope.

PPTX Extraction Output Schema

Produced by skills/vault-ingress/scripts/pptx-extraction.py.

What the Script Extracts (mapped to slide-design-spec.md sections)

Spec SectionExtraction CoverageField
2. Background ColorsExact hex values + fill typebackground_color_hex, background_type
3. TypographyFont names, sizes, colors, bold/italicshapes_summary[].font_*
4. FooterPosition, font, color, separatorfooter_text, footer shape properties
5. Image PlacementWhether image is present (composition type needs PDF visual classification)has_image
6. Bubbles/StarburstsAuto-shape type enum, fill/line colorsauto_shape_type, fill_color, line_color
7. Layout TaxonomyPowerPoint layout name per slidelayout_name
10. Color SequencingFull sequence of hex valuescolor_sequence
Text-channel provenanceRecursive shape text, table cells, picture OCR, background OCRtext_channels[]
Unsupported visual containersSmartArt, charts, OLE/media, unknown graphic frames, damaged assetsunsupported_content[], render_required_reasons[]
Native timing/build structureRaw timing containers, behavior elements, visibility sets, transitions, media timing, and build-list entriesnative_timing, native_timing_summary

What the Script Does NOT Extract (still needs PDF visual analysis)

  • Image composition type (full-bleed vs side-by-side vs inset) — python-pptx can tell you an image exists and its position/size, but classifying the COMPOSITION PATTERN requires visual judgment
  • Content type (meme vs data chart vs quote) — requires understanding the content, not just the shapes
  • Section divider identification — requires understanding the rhetorical function
  • Background color NAME (the semantic register label like "purple_halftone") — python-pptx gives hex values; mapping hex to register names requires building the lookup table from the first few extractions
  • Observed playback, concurrency, perceived target, or delivery quality — raw timing elements establish package structure only. Counts do not show which markup branch or build ran, whether effects were simultaneous, or what the audience saw

Schema:

{
  "schema_version": 4,
  "pipeline_version": "1.5.0",
  "input_fingerprint": {
    "algorithm": "sha256",
    "digest": "64 lowercase hex characters",
    "size_bytes": 123456
  },
  "pptx_path": "Conference/Year/Talk.pptx",
  "slide_count": 60,
  "aspect_ratio": "16:9",
  "slide_width_inches": 13.33,
  "slide_height_inches": 7.5,
  "corrupt_assets": [
    {
      "part_name": "ppt/media/image7.png",
      "error_type": "crc_mismatch",
      "status": "recovered_with_placeholder"
    }
  ],
  "archive_recovery": [
    {
      "schema_version": 1,
      "part_name": "ppt/media/image7.png",
      "member_kind": "embedded_media",
      "error_type": "crc_mismatch",
      "status": "recovered_with_placeholder_asset",
      "content_replaced": true,
      "replacement_sha256": "64 lowercase hex characters"
    }
  ],
  "template_layouts": [
    {
      "index": 0,
      "master_index": 0,
      "name": "Title Slide",
      "placeholders": [{"idx": 0, "type": "CENTER_TITLE"}]
    }
  ],
  "per_slide_visual": [
    {
      "slide_number": 1,
      "slide_part_name": "ppt/slides/slide1.xml",
      "background_color_hex": "#5B2C6F",
      "background_type": "solid|pattern|image|gradient|solid_from_layout|solid_from_master|unknown",
      "background_asset_status": "not_applicable|available|corrupt|unavailable",
      "background_part_name": null,
      "background_asset_sha256": null,
      "layout_name": "Title Slide  (free text from slide.slide_layout.name — not an enum)",
      "shape_count": 3,
      "shape_count_recursive": 5,
      "has_text_frame_shapes": true,
      "has_extracted_text": true,
      "has_image": false,
      "image_area_ratio": 0.0,
      "text_extraction_confidence": "high|low",
      "text_content_preview": "Talk Title",
      "ocr_text": "",
      "text_extraction_method": "shapes|shapes+ocr|shapes+ocr_unavailable",
      "text_channels": [
        {
          "channel": "shape_text|table_cell_text|picture_ocr|background_image_ocr|<unsupported-kind>_text|group_container_text",
          "text": "Talk Title",
          "confidence": "high|medium|low",
          "status": "extracted|empty|partial|failed|skipped|unavailable|unsupported|requires_render",
          "provenance": {
            "source": "pptx_shape_text_frame",
            "shape_path": ["Group 1", "Title 2"]
          }
        },
        {
          "channel": "picture_ocr",
          "text": "Recovered label",
          "confidence": "low",
          "result_confidence": 91.25,
          "status": "extracted",
          "attempted": true,
          "engine": "tesseract",
          "engine_version": "5.5.1",
          "reason": null,
          "provenance": {
            "source": "embedded_picture_blobs",
            "shape_paths": [["Picture 3"]]
          },
          "ocr_receipts": [
            {
              "attempted": true,
              "engine": "tesseract",
              "engine_version": "5.5.1",
              "result_status": "text_recovered|low_confidence_text|genuine_empty|failed|unavailable|skipped",
              "result_confidence": 91.25,
              "error": null,
              "part_name": "ppt/media/image3.png",
              "asset_sha256": "64 lowercase hex characters",
              "shape_path": ["Picture 3"],
              "recovered_text": "Recovered label",
              "trustworthy_text": true
            }
          ]
        }
      ],
      "unsupported_content": [
        {
          "content_type": "smartart|chart|graphic_frame|embedded_ole_object|linked_ole_object|media|unreadable_picture|corrupt_embedded_asset",
          "shape_name": "Diagram 4",
          "shape_path": ["Diagram 4"],
          "graphic_data_uri": "http://schemas.openxmlformats.org/drawingml/2006/diagram",
          "reason": "visible text or labels may not be represented in PPTX text frames",
          "render_required": true
        }
      ],
      "has_unsupported_content": true,
      "render_required": true,
      "render_required_reasons": ["smartart"],
      "footer_text": "@handle | #conf | #topic | website",
      "has_speaker_notes": true,
      "native_timing": {
        "timing_element_present": true,
        "timing_element_count": 1,
        "transition_count": 1,
        "set_action_count": 2,
        "visibility_set_action_count": 1,
        "animation_behavior_counts": {
          "general": 1,
          "color": 0,
          "effect": 2,
          "motion": 1,
          "rotation": 1,
          "scale": 1,
          "total": 6
        },
        "media_timing_counts": {"audio": 1, "video": 0, "total": 1},
        "build_list_present": true,
        "build_list_count": 1,
        "build_entry_counts": {
          "paragraph": 1,
          "diagram": 1,
          "ole_chart": 0,
          "graphic": 0,
          "total": 2
        },
        "has_animation_behaviors": true,
        "has_media_timing": true,
        "has_build_entries": true,
        "provenance": {
          "source": "pptx_package_xml",
          "measurement": "raw_ooxml_element_counts",
          "observed_playback": false,
          "part_name": "ppt/slides/slide1.xml"
        }
      },
      "shapes_summary": [
        {"name": "Title 1", "shape_type": "PLACEHOLDER (14)", "has_text_frame": true, "is_picture": false, "is_graphic_frame": false, "graphic_frame_type": null, "graphic_data_uri": null, "left": 1.0, "top": 0.5, "width": 10.0, "height": 1.0, "shape_path": ["Title 1"], "group_depth": 0, "text_preview": "Talk Title", "font_name": "Bangers", "font_size": 36, "font_color": "#FFFFFF", "bold": true, "italic": false},
        {"name": "Cloud 2", "shape_type": "AUTO_SHAPE (1)", "has_text_frame": true, "is_picture": false, "is_graphic_frame": false, "graphic_frame_type": null, "graphic_data_uri": null, "left": 2.0, "top": 2.0, "width": 3.0, "height": 2.0, "shape_path": ["Cloud 2"], "group_depth": 0, "text_preview": "", "auto_shape_type": "CLOUD_CALLOUT (108)", "fill_color": "#FFFFFF", "line_color": "#000000"},
        {"name": "Picture 3", "shape_type": "PICTURE (13)", "has_text_frame": false, "is_picture": true, "is_graphic_frame": false, "graphic_frame_type": null, "graphic_data_uri": null, "left": 6.0, "top": 2.0, "width": 4.0, "height": 3.0, "shape_path": ["Picture 3"], "group_depth": 0, "picture_asset_status": "available", "picture_part_name": "ppt/media/image3.png", "picture_asset_sha256": "64 lowercase hex characters"}
      ]
    }
  ],
  "native_timing_summary": {
    "slides_with_timing_elements": 12,
    "slides_with_transitions": 40,
    "slides_with_animation_behaviors": 10,
    "slides_with_media_timing": 2,
    "slides_with_build_lists": 1,
    "slides_with_build_entries": 1,
    "timing_element_count": 12,
    "transition_count": 40,
    "set_action_count": 18,
    "visibility_set_action_count": 9,
    "build_list_count": 1,
    "animation_behavior_counts": {
      "general": 7,
      "color": 3,
      "effect": 15,
      "motion": 4,
      "rotation": 2,
      "scale": 5,
      "total": 36
    },
    "media_timing_counts": {"audio": 2, "video": 1, "total": 3},
    "build_entry_counts": {
      "paragraph": 1,
      "diagram": 1,
      "ole_chart": 0,
      "graphic": 0,
      "total": 2
    },
    "provenance": {
      "source": "pptx_package_xml",
      "measurement": "raw_ooxml_element_counts",
      "observed_playback": false
    }
  },
  "native_deck_audit": {
    "schema_version": 1,
    "extraction_schema_version": 4,
    "extraction_pipeline_version": "1.5.0",
    "source_pptx_sha256": "64 lowercase hex characters",
    "source_pptx_size_bytes": 123456,
    "slide_count": 60,
    "render_required_slide_numbers": [1],
    "render_required_reasons": {"1": ["smartart"]},
    "extraction_receipt_sha256": "64 lowercase hex characters",
    "rendered_page_inspection": {
      "schema_version": 1,
      "source_pptx_sha256": "64 lowercase hex characters",
      "rendered_pdf_sha256": "64 lowercase hex characters",
      "rendered_pdf_size_bytes": 98765,
      "rendered_page_count": 60,
      "inspected_page_ranges": [[1, 60]],
      "inspected_required_slide_numbers": [1],
      "complete": true,
      "binding_sha256": "64 lowercase hex characters"
    }
  },
  "global_design": {
    "fonts_used": {"Bangers": 45, "Arial": 10},
    "background_colors": {"#5B2C6F": 12, "#C0392B": 8},
    "shape_types_used": {"CLOUD_CALLOUT (108)": 15, "EXPLOSION1 (89)": 8},
    "color_sequence": ["#5B2C6F", "#FFFFFF", "#C0392B", "..."]
  }
}

schema_version tracks this JSON field shape. Missing means legacy shape 0; v1 added the version/fingerprint, v2 added native_timing to every slide plus native_timing_summary; v3 added the raw build-list timing lane plus closed archive_recovery and native_deck_audit records; and current v4 makes shape, picture, and background capability/asset bindings required. pipeline_version tracks extraction behavior and changes when the walk, classification, confidence, OCR, recovery, timing, or receipt behavior changes; current is 1.5.0. Pipeline 1.5 applies the shared bounded PDF ceiling, complete page-tree walk, and repair-diagnostic rejection to render receipts produced inside the already-contained PPTX extraction worker.

Extractor schema v4 is independent of persisted pattern-evidence schema v2, return schema v5, queue-claim schema v5, and tracking-database schema v1. Those downstream generations do not advance here; their current readers bind or validate the new nested records inside their existing contracts.

These records are transient per-invocation output, not a persisted artifact with an in-place migration. Regenerate old output with the current extractor. A timing reader must treat v0/v1 as timing unknown, never as all-zero. Schema v2 has the pre-build timing shape but lacks raw build-list evidence and cannot satisfy archive-recovery or audit-receipt requirements. Schema v3 lacks the required capability/asset bindings, so current analysis reruns it too. An unknown future schema version is no usable prior output. The vault-profile layout reader may read v1/v2/v3/v4 because template_layouts is unchanged, but it also rejects missing/unknown versions and reruns instead of guessing. This is the only declared cross-pipeline compatibility exception. input_fingerprint hashes the exact source PPTX bytes before any in-memory media recovery; identical bytes have the same fingerprint regardless of path.

Every slide binds its ordinal to the canonical python-pptx part name ppt/slides/slide{slide_number}.xml; native-timing provenance must name that same part. Every shape carries explicit has_text_frame, is_picture, and is_graphic_frame capabilities. Text preview and its five-field font bundle exist only for a text frame; table dimensions/text/fonts exist only for a table graphic frame; and picture asset status/part/digest exist only when is_picture: true. Graphic type is derived from and cross-bound to the exact DrawingML URI when present; a graphic frame with a missing/empty URI is retained as generic graphic_frame unsupported evidence with a null URI. Available picture and background OCR receipts must exactly match their part name and digest, while corrupt asset bindings must match the closed archive-recovery record.

Every public PPTX probe, native-audit recomputation, and extraction is executed in a separate authenticated worker. The request and response bind the exact file generation (including platform availability flags), operation, fixed limit profile, and extractor schema/pipeline. POSIX process groups and Windows Job Objects provide process-tree cleanup for the trusted worker boundary; on POSIX, cleanup covers the worker process group plus sampled descendants and is not an adversarial session-containment claim. Sampled aggregate RSS monitoring requires exactly psutil==7.2.2 and is fail-closed, but is not described as a kernel hard-allocation limit on macOS. Raw worker diagnostics are discarded after producing a bounded count/hash/ truncation receipt. Source-artifact and operation resource limits are script-owned; see skills/vault-ingress/scripts/pptx_evidence.pyPPTX_MAX_INPUT_BYTES and the top-level SupervisorLimits profiles. Exceeding a configured limit fails closed.

Directory extraction is selected only with --directory. Its public result is the strict schema-v1 completeness envelope below; this generation is independent of the per-deck extractor schema v4 and pipeline 1.5.0:

{
  "schema_version": 1,
  "kind": "pptx_directory_batch",
  "complete": false,
  "incomplete_reason_codes": ["pptx_batch_file_limit"],
  "results": [],
  "skipped": [{"path": ".", "reason": "pptx_batch_file_limit"}]
}

complete is true exactly when incomplete_reason_codes is empty, and both are recomputed from the closed skipped[] taxonomy. Exit zero admits either complete or partial output: safe per-deck results remain usable, but only complete: true authorizes full-catalog coverage or an absence conclusion. Whole-root and protocol failures exit nonzero and add the existing top-level error, bound to one root receipt and no results. Whole-root-only reason codes are invalid as ordinary partial receipts, and per-deck failures cannot be promoted into the top-level error. Its details object is path-neutral and may contain only one optional closed supervisor_reason_code. Each public skipped[].path is either . or one bounded canonical root-relative path; absolute, drive-qualified, backslash, traversal, empty-component, and control/format-bearing paths are rejected during decode. A legacy unversioned results/skipped object has unknown completeness and must be rerun before a coverage or absence claim.

The owner performs no root stat, type probe, or recursive enumeration: an authenticated worker with fixed input, output, memory, process, and wall limits validates and scans the root, then returns a private schema-v2 root-relative manifest. Its authenticated request carries the validated exact-component directory-exclusion list; the response echoes that exact ordered list and carries complete and incomplete_reason_codes. The owner rejects a response whose policy differs, fabricates an exclusion receipt, returns evidence below an excluded component, or nests any skip below another non-root skip, then independently recomputes completeness from skipped[]. For every encountered real directory, symlink and reparse-point rejection runs before the case-insensitive exact-component exclusion check. An excluded directory produces one pptx_batch_directory_excluded receipt, consumes no descendant scan budget, and is not traversed. The excluded dirent is charged to a separate finite policy enumeration ceiling rather than the eligible-entry ceiling, so excluded environment/cache directories cannot starve authored siblings. Exhausting either ceiling emits pptx_batch_entry_limit; enumeration remains bounded.

The envelope's complete and incomplete_reason_codes fields are the sole consumer authority; never recreate the per-receipt classification. Its closed taxonomy lives only in skills/vault-ingress/scripts/pptx_discovery_contract.py::{PPTX_DIRECTORY_POLICY_SKIP_REASON_CODES,PPTX_DIRECTORY_INCOMPLETE_REASON_CODES}. The worker rejects unusable or colliding directory identities and unknown redirecting Windows reparse tags; supported hydrated Cloud Files leaves remain eligible. Discovery and extraction share one enclosing deadline, and final compact-JSON accounting includes its wrapper and newline. Stronger root/leaf handle binding and handle-relative traversal remain tracked by #176; until then all recursive filesystem contact is at least confined to the termination-safe discovery worker rather than occurring in the owner.

archive_recovery is empty on a healthy package. A bad-CRC member under ppt/media/ is replaced only in an in-memory package with a transparent placeholder, allowing healthy text and slides to survive while recording the lost part and exact replacement digest. corrupt_assets remains the legacy three-field projection. Structural members (XML, relationships, content types, layouts, masters, and presentation topology) are never discarded; their corruption makes the deck unavailable. The source file is never rewritten.

native_timing inventories exact PresentationML element names under each slide's <p:timing> tree. general means the exact <p:anim> behavior, not a total; effect, motion, rotation, scale, and color likewise count only their specific behavior elements. visibility_set_action_count is the subset of <p:set> actions whose attribute name is visibility or ends in .visibility. Audio/video time nodes have a separate media_timing_counts lane, and slide transitions are counted separately whether or not a timing tree exists. The build lane counts only exact p:bldP, p:bldDgm, p:bldOleChart, and p:bldGraphic entries beneath p:bldLst, with fixed zero-valued keys and an explicit build_list_present flag. A build entry is raw package structure: it does not prove reveal order, visible state, execution, or delivered playback, and it is never merged into visibility-set counts.

All counts are raw OOXML structure. Markup Compatibility Choice and Fallback branches are both present in the package and both counted. The provenance field's observed_playback: false is load-bearing: timing-container presence, media timing, or a motion/build element does not prove execution, concurrency, smoothness, the perceived target, or delivered audience behavior. Adjacent static duplicate slides can still be progressive-reveal evidence after rendered-state inspection; they correctly carry zero native timing when the author implemented the build as separate slides.

text_extraction_confidence gates how the text fields may be read. text_content_preview aggregates native shape-frame and table-cell text for backward compatibility. text_channels is authoritative for provenance: recursive shape text, table cells, picture OCR, and background-image OCR remain distinct. Text rendered inside pictures, SmartArt, charts, or other unsupported containers can remain invisible. On a "low" slide:

  • empty text_content_preview means unreadable by native shape/table channels, never wordless
  • ocr_text holds the backward-compatible aggregate of picture and background image OCR channels when the engine ran (text_extraction_method: "shapes+ocr"). Use only receipt records with trustworthy_text: true for affirmative cites, transcript cross-checks, language policy on slide text, and pattern evidence; lower-confidence recovered text remains available for spelling review but is not trustworthy evidence
  • OCR channels add attempted, configured engine/version, aggregate numeric result_confidence (finite 0..100 or null), a closed reason, and ocr_receipts[]. Each per-asset receipt binds the exact package part_name, asset SHA-256, and shape path to one result. recovered_text is capped at 8,000 characters per receipt. With no readable blob the channel has attempted: false, status: "unavailable", reason no_readable_asset, and an empty receipt array. With --no-ocr, every readable asset gets an explicit attempted: false / result_status: "skipped" receipt and reason ocr_disabled. A missing engine records ocr_engine_unavailable; processing failures record ocr_failed or partial_ocr_results
  • text_extraction_method is "shapes" when OCR was not attempted (high confidence, --no-ocr, or no usable image blob), "shapes+ocr" when it ran, "shapes+ocr_unavailable" when the engine was missing
  • Dimensions 8/13 design judgment (density, two-layer legibility, composition) still requires the rendered image — OCR is inventory, not layout (see known-issues.md § "Shape Extraction Is Blind to Text Baked Into Images" and subagent-instructions.md)

has_text_frame_shapes reports shapes carrying text frames — not whether the slide shows text. has_extracted_text covers every emitted text channel, including table cells and OCR.

Groups and tables are traversed, but they still force low: nested transforms, merged cells, and embedded visual content can affect visible reading order. SmartArt, charts, OLE/media objects, unknown graphic frames, unreadable images, and recovered corrupt assets are listed in unsupported_content. Never infer completeness when render_required is true; use render_required_reasons to choose the fallback.

native_deck_audit binds the exact PPTX digest and size, extractor schema and pipeline, slide count, and every derived render-required slide/reason. Its optional rendered_page_inspection binds an equal-page-count PDF plus the exact inspected ranges. Hash, byte size, and page count come from one stable copied PDF generation; persistence snapshots the current canonical PDF again and compares all three, so a same-size replacement cannot inherit an old receipt. Every current return that declares, inspects, or cites native_deck must carry the current audit even when it reports zero findings. Native citations—including applicability citations—need rendered evidence only when their cited slide numbers overlap the audit's render-required slides; explicit authored visual summary fields require complete coverage of every render-required slide. Persistence recomputes the audit in a bounded worker and matches the receipt to owner-canonical native-deck and static-slide identities.

Current PDF and PPTX supervisor receipts preserve request, result, dependency, monitor, identity, containment, and resource-limit causes through the mappings owned by pdf_evidence.py and pptx_evidence.py. Successful evidence is unchanged, so this does not advance either extraction schema or pipeline. Previously persisted ambiguous failure/skip receipts remain readable and must not be relabeled: their closed details do not prove a narrower historical cause. Rerun ingress to regenerate them under the current mappings before relying on remediation advice.

image_area_ratio is the largest is_picture: true shape's area as a fraction of the slide as emitted by pptx-extraction.py and validated by pptx_evidence.py; it is always present. Script-owned normalization can make an extremely small picture report 0.0, so that value is not proof that the slide has no picture. PPTX_TEXT_BEARING_IMAGE_AREA_RATIO in pptx_evidence.py is the sole authority for when the reported value adds large_picture; prose and downstream consumers must not reproduce its predicate.

It measures picture shapes only, including an inserted picture placeholder whose OOXML element is a picture; media poster frames are not picture evidence. A slide whose image is a background reports background_type: "image" and text_extraction_confidence: "low" while image_area_ratio stays 0.0 — the background covers the canvas by definition and has no picture geometry to measure. When its relationship and blob are valid, OCR appears in a distinct background_image_ocr channel; a missing blob is recorded as status: "unavailable". Either way, rendering is still required for design judgment. Read the confidence, never the ratio, to decide whether a slide needs a visual pass.

skills

README.md

tile.json