Disciplines · Proposals

Yemaya Study & Deconstruction Workspace — Implementation Evidence Log

domain entries (yemaya, nisaba, sophia, hathor, aja, euterpe, aglaea, bellona, neith, maya, metis, isis, iris, oshun), each with owner, API services, data authority (accountable/contributing/boundary per proposal §3.2), maturity for the stu

155sections268 minread

On this page

Append-only evidence log for YEMAYA_STUDY_AND_DECONSTRUCTION_WORKSPACE_SOTA_IMPLEMENTATION_CHECKLIST.md.

Contract (YSD-0003): every checked item records its implementation artifacts, test command, passing output, review/approval evidence where applicable, and the commit that landed it. Entries are append-only; corrections append a superseding entry rather than editing history. Each entry's commit is resolvable deterministically with git log --grep='<item-id>' because every implementation commit subject or body names the item IDs it delivers; hashes are additionally recorded here once known.

Immutable-link contract (YSD-0112): evidence links must survive later edits to the linked artifact. The convention, in order of preference:

  1. Commits — full or unambiguous-prefix SHA on origin/main (git log --grep='<item-id>' resolves it; the recorded SHA pins content forever). A pinned browse URL is https://github.com/GreyChimp/oshun/blob/<sha>/<path>.
  2. Migrations — repo path of the migration directory plus the SHA that introduced it (migration files are themselves append-only by repo policy).
  3. API versions — the version literal in the contract source (e.g. VIDEO_ADAPTER_VERSION) plus path@sha.
  4. Evaluation reports / performance runs / accessibility reports — the generated report file committed under docs/ or infra/a11y-reports/ (or the CI run URL), always paired with the workflow name and the commit it ran against.
  5. Threat-model updates — path@sha of the threat-model document revision.
  6. Release decisions — the decision artifact path@sha plus the named approver and date (YSD-0015).

A bare mutable path (no SHA, no version) is not evidence; entries below that predate this contract already carry SHAs and remain compliant.


YSD-0101 — Machine-readable capability inventory (2026-07-18)#

  • Status: complete
  • Artifacts:
    • docs/proposals/yemaya-study-workspace/capability-inventory.json — 14 domain entries (yemaya, nisaba, sophia, hathor, aja, euterpe, aglaea, bellona, neith, maya, metis, isis, iris, oshun), each with owner, API services, data authority (accountable/contributing/boundary per proposal §3.2), maturity for the study workspace (proposal §5.1 legend), persistence (migration-dir evidence), fixture use (raw marker counts plus code-verified findings), test coverage counts, and known limitations; plus 6 program-level missing capabilities from proposal §5.2.
    • docs/proposals/yemaya-study-workspace/capability-inventory.schema.json — JSON Schema (2020-12) for the artifact.
    • tools/yemaya-study/check-capability-inventory.mjs — dependency-free structural validation + domains.json drift check + path-existence check, following the tools/domains/check-registry.mjs convention.
  • Ground truth collected at commit 6c87e0df3b98f431c80fbc3ab7a5318d85c20898: file/test/migration counts via find, fixture-marker counts via grep -rl, domain registry fields from domains.json. Fixture claims verified by reading code: apps/aja/svc-reference-video/src/metadata-extraction.ts (simulated FFprobe, lines 259–282/508–511), apps/aja/svc-reference-video/src/scene-detection.ts (simulated frame stats/scene changes, lines 271–334), libs/yemaya/agents/src/creative/performance-reference-library.ts (in-memory Map stores lines 183–185, four-value rights enum line 36). All 15 proposal-cited evidence paths confirmed to exist.
  • Test command: node tools/yemaya-study/check-capability-inventory.mjs
  • Passing output: capability-inventory check passed: 14 domains, 6 missing-capability entries, assessed 2026-07-18.
  • Negative test: removing the iris entry, corrupting an owner, and pointing a verified finding at a nonexistent path made the checker fail with exactly those 3 problems (exit 1); restoring the file returned it to green.
  • Commit: resolve via git log --grep='YSD-0101' (subject docs(yemaya): ysd-0101 machine-readable capability inventory), landed as a0710bd396 on branch and merged to origin/main via 0808b8cb5b.
  • Status: complete
  • Artifacts:
    • docs/proposals/yemaya-study-workspace/section-29-evidence.md — link validation result plus the exact reusable symbols, services, and contracts for all 15 §29.3 primitives (replacing directory-level claims), with reuse caveats and a prohibited-inference risk flag for four yemaya analyzers (gaze/fatigue/authenticity/response detection) feeding YSD-4052.
  • Validation method: every §29 link resolved from docs/proposals/ with a shell existence check over all 30 targets (5 ADRs, 10 domain docs, 15 primitives) — all OK, zero stale paths, so no proposal edits were needed. Export surfaces extracted with grep -n '^export ' per file and read in context; simulated paths re-confirmed (aja scene-detection lines 271–334, metadata-extraction lines 259–282/508–511).
  • Test command: cd docs/proposals && for p in <all 30 §29 link targets>; do [ -e "$p" ] || echo STALE $p; done → no STALE output.
  • Note: continuous re-validation of these links is deliberately deferred to YSD-0111 (automated traceability check), which owns CI enforcement.
  • Commit: resolve via git log --grep='YSD-0102', landed as e4f7be9eb2.

YSD-0103 — Reuse-versus-wrap-versus-replace ledger (2026-07-18)#

  • Status: complete (ledger + enforcement built; 10 owner decisions recorded as pending and tracked by the checker — those pending decisions gate later work via the ledger itself and the future YSD-0139 CI decision gate, they do not gate this build item)
  • Artifacts:
    • docs/proposals/yemaya-study-workspace/reuse-ledger.json — 20 entries, one per proposal §5.2 capability row, each with status (§5.1 legend), cited evidence, proposed disposition (reuse/wrap/replace/build-new/ exclude), rationale, and — for every partial or fixture-backed capability — a required ownerDecision block (all currently pending).
    • docs/proposals/yemaya-study-workspace/reuse-ledger.schema.json — JSON Schema with conditional requirements (partial/fixture-backed ⇒ ownerDecisionRequired:true + ownerDecision present; decided ⇒ decidedBy/decidedOn/decision/rationale).
    • tools/yemaya-study/check-reuse-ledger.mjs — parses the §5.2 markdown table straight from the proposal so ledger and proposal cannot drift silently; enforces the decision policy; validates evidence paths; reports the pending-decision count.
  • Test command: node tools/yemaya-study/check-reuse-ledger.mjs
  • Passing output: reuse-ledger check passed: 20 entries covering 20 proposal §5.2 rows; 10 owner decision(s) pending.
  • Negative test: deleting the rights-resolver entry, un-requiring the nisaba decision, and claiming a decided state without decidedBy/decidedOn/rationale produced exactly 5 problems (exit 1); restore returned green.
  • Commit: resolve via git log --grep='YSD-0103', landed as d099eb67e1.

YSD-0104 — Existing schema inventory + identity conflicts (2026-07-18)#

  • Status: complete
  • Artifacts:
    • docs/proposals/yemaya-study-workspace/schema-inventory.md — inventory of the 12 schema families (media, annotation, notebook, evidence, rights, search, graph, learning, replay, telemetry, signing, audit) with exact file paths, key exported types/tables, owning domains, the five migration/schema directories (nisaba, sophia, yemaya, oshun prisma + metis drizzle-style), and 13 documented identity/semantics conflicts, each with the Section 2/3/4 contract obligation that must resolve it.
  • Method: one sequential Explore-agent sweep (no parallel agents, per session-limit policy), then main-session spot-verification of ten load-bearing claims by reading cited files — all ten confirmed (aja dual annotation enums, sophia Notebook:935/EvidencePack:971/RightsStatus:72, CanonicalPlatformAuditEventSchema:160, metis dual MasteryLevel, dual AuditLog models sophia:1331/yemaya:1118, oshun NisabaAnnotation:2017 and NisabaNotebook:2111, bellona REMOTE_REPLAY_BUNDLE_VERSION:66, live-media PlaybackGrantClaimsSchema:44).
  • Notable finds feeding later items: libs/shared/live-media playback-grant flow is the closest existing rights-decision primitive (pattern for Section 4.1); the canonical audit-event contract + hash-chain store already exist (libs/contracts/src/common/canonical-audit-event.ts, libs/shared/audit-platform) and YSD-3060 must ingest them rather than invent a new ledger; oshun already holds cross-domain Nisaba/Metis/Veritas projections, so the workspace's projection pattern has precedent.
  • Commit: resolve via git log --grep='YSD-0104', landed as dbb87a0916 (merged to origin/main via 84c7410039).

YSD-0105 — Infrastructure inventory (2026-07-18)#

  • Status: complete
  • Artifacts:
    • docs/proposals/yemaya-study-workspace/infrastructure-inventory.md — databases (Postgres16+pgvector via PgBouncer; 18 domain DBs + oshun_dev from POSTGRES_MULTIPLE_DATABASES, including existing yemaya and nisaba DBs), object stores (MinIO dev / S3 prod / live-media S3 store), queues (Redis+BullMQ via libs/shared/queue; SQS+DLQ terraform; Kafka+ Debezium profile-gated), search (Elasticsearch profile + PG FTS), vectors (pgvector default + Qdrant profile), graph (Neo4j profile; TS/Postgres graph engines today), time-series (none dedicated — matches YSD-3043 posture), audit (canonical contract + hash-chain with in-memory-only store; divergent domain tables), observability, RunPod GPU posture, and 4 named gaps feeding Sections 1/3/6.
  • Method: read docker/docker-compose.dev.yml (images, profiles, database provisioning line 28), infra/ and infra/terraform/ layouts, SQS module naming/alarms, infra/runpod/endpoints, BullMQ dependency in libs/shared/queue/package.json; .env.example present, no committed root .env.
  • Commit: resolve via git log --grep='YSD-0105', landed as 21d024fcf2.

YSD-0106 — Oshun Studio surface inventory (2026-07-18)#

  • Status: complete
  • Artifacts:
    • docs/proposals/yemaya-study-workspace/studio-surface-inventory.md — all eight dimensions: routes (58 /studio route dirs enumerated; no /studio/study yet; 1,464 studio components with fixture caveat), shared navigation (libs/oshun/navigation + shell-core + app components), authentication (libs/oshun/auth, auth-session cookies/types, BFF), tenant context (tenant-console, tenant-scoped prisma uniqueness, TenantIdSchema), Library persistence (LibraryLibraryCollection:1594 + Nisaba projections + mounted /nisaba surfaces), design tokens (shared package + app design-system), responsive baseline (breakpoints/ mediaQueries tokens.ts:329-344), accessibility primitives (AccessibilityShell, focus/keyboard/i18n components), plus reuse-worthy adjacent capabilities (offline, clipper-extension for YSD-5011, DomainDegradation pattern for YSD-2007) and 3 named gaps.
  • Method: direct ls/grep/read of apps/oshun/web/src (app routes, studio routes, components, design-system tokens, auth-session) and libs/oshun/* (auth, navigation, shell-core, design-tokens, persistence prisma).
  • Commit: resolve via git log --grep='YSD-0106', landed as 596e6bbfd9.

YSD-0107 — Harness inventory mapped to release gates (2026-07-18)#

  • Status: complete
  • Artifacts:
    • docs/proposals/yemaya-study-workspace/harness-inventory.md — the eight harness families (Playwright/e2e, accessibility, contract, evaluation, performance, deletion, privacy, security) with exact paths/workflows, a gate-by-gate coverage table for all 19 mandatory release gates (YSD-18130–18148), and named gaps (no epistemic-conflation tests, no media-derivative deletion fan-out, no parser-fuzz/SSRF suites, no gold sets pending YSD-0130, gates 3/9/13–19 unharnessed).
  • Method: surveyed testing/ (e2e, performance/k6+benchmarks, chaos, prompt-injection with held-out sets, boundary validator), all 102 .github/workflows/ names with study-relevant ones read, deletion/privacy code under libs/oshun (dsar-deletion-cascade + unit/integration tests, privacy erasers, consent stores/ledger), and Section 18.6 gate text.
  • Commit: resolve via git log --grep='YSD-0107', landed as 390e735509 (merged to origin/main via 33956eb359).

YSD-0108 — Source-format support matrix (2026-07-18)#

  • Status: complete
  • Artifacts:
    • docs/proposals/yemaya-study-workspace/source-format-matrix.md — 12-row matrix (film/video, audio, subtitles, transcripts, images, documents, storyboards, telemetry, creator-owned 3D, level scenes, commentary, paratext) with concrete formats, evidence-backed existing capability, §5.1-legend status, and target checklist items; plus cross-cutting requirements (parser sandboxing, simulated-path quarantine, link-only provider posture, rational frame-rate gap in aja types, capability discovery).
  • Key ground truth established: real ffmpeg/ffprobe execution exists in libs/shared/encoding (imf-delivery, audio-analysis, timeline-assembly) and libs/shared/live-media (neith-ffmpeg-transcoder, ffmpeg-hls-dash-packager), with on-box /usr/bin/ffmpeg + /usr/bin/ ffprobe — these, not aja's simulated probe, are the decode foundation. Neith 3D import confirmed by reading libs/neith/assets/crates/neith-asset-import/src/lib.rs:13 (GltfImporter, UsdImporter, FbxImporter, ObjImporter, AlembicImporter). No PDF parser found in nisaba/sophia.
  • Commit: resolve via git log --grep='YSD-0108', landed as ed20585c24.

YSD-0109 — User-persona acceptance matrix (2026-07-18)#

  • Status: complete
  • Artifacts:
    • docs/proposals/yemaya-study-workspace/persona-acceptance-matrix.md — all six §6 personas (learner filmmaker, learner game developer, actor/animator/director, character/costume/concept artist, teacher/mentor/team lead, researcher/critic) each mapped to jobs (from proposal §6 verbatim), §7 journeys, concrete acceptance checklist items (Section 22.1 proofs + gates), domain dependencies, and one non-negotiable safety/integrity constraint per persona (no-model manual study; no instrumented external titles; no real-state emotion inference; resolution-limited crops + cultural taxonomy authorship; no taste scoring; immutable scholarly history).
  • Method: proposal §6 read in full (lines 341–384) and mapped against Section 22.1/22.2 acceptance items and Section 18.6 gates.
  • Commit: resolve via git log --grep='YSD-0109', landed as 5c1cd4e387.

YSD-0110 + YSD-0111 — Traceability ledger + automated check (2026-07-18)#

  • Status: complete (built together — the check is the ledger's enforcement — verified individually)
  • Artifacts:
    • docs/proposals/yemaya-study-workspace/traceability-ledger.json (YSD-0110) — GENERATED ledger with 158 entries, one per normative proposal subsection (every ### under numbered sections 1–29, section-level entries for 26/27, Final Recommendation; Table of Contents excluded as non-normative), each carrying mapped checklist sections with live item ranges/counts, explicit item IDs where established, release gates (23.5 → YSD-18130..18148), and notes. Test evidence per item lives in EVIDENCE_LOG.md (linked from the ledger header).
    • tools/yemaya-study/traceability-map.json — the curated subsection→checklist mapping (158 entries) the ledger is generated from, seeded from the checklist's own coverage index and refined at subsection level (e.g. 9.2→YSD-2072/2073, 9.3→YSD-2074..2077, 17.2→YSD-1002..1004, 29.x→YSD-0102).
    • tools/yemaya-study/check-traceability.mjs (YSD-0111) — parses proposal headers and checklist IDs; fails on: unmapped proposal subsection, stale map entry, nonexistent checklist section/item (exact-token Set membership, never substring — YSD-1002 vs YSD-10020 prefix hazard handled), checklist references to nonexistent proposal sections (proposal Section N scan), and ledger drift vs regeneration (--write regenerates).
    • .github/workflows/ci.yml — all three yemaya-study checkers added to the quality/lint job beside the domain-registry check (automation requirement of YSD-0111).
  • Test command: node tools/yemaya-study/check-traceability.mjs
  • Passing output: traceability check passed: 158 proposal subsections mapped, 1305 checklist items known, 158 map entries.
  • Negative test: removing the 9.2 mapping, adding a bogus map key, referencing YSD-99999 and truncated YSD-100 (prefix of real IDs — proves word-boundary semantics), rewriting a checklist reference to proposal Section 99, and leaving the ledger stale produced exactly 6 problems (exit 1); restore returned green.
  • Commit: resolve via git log --grep='YSD-0110', landed as 6fd3ff3fd9.
  • Status: complete
  • Artifacts: this file's header now carries the immutable-link contract covering all eight classes YSD-0112 names (commits, migrations, API versions, evaluation reports, performance runs, accessibility reports, threat-model updates, release decisions), each with its pinning convention (SHA-anchored paths/URLs, version literals, workflow+commit pairs, named approver for release decisions). Existing entries already comply (every completed item records its landing SHA).
  • Verification: all recorded SHAs resolve on origin/main (git merge-base --is-ancestor <sha> origin/main for a0710bd396, e4f7be9eb2, d099eb67e1, dbb87a0916, 21d024fcf2, 596e6bbfd9, 390e735509, ed20585c24, 5c1cd4e387, 6fd3ff3fd9 — see commit for the run).
  • Commit: resolve via git log --grep='YSD-0112'.

YSD-0113 — Nisaba primitive generalization assessment (2026-07-18)#

  • Status: complete
  • Artifacts:
    • docs/proposals/yemaya-study-workspace/reuse-ledger.json — new nisabaPrimitiveAssessments section with six primitive families: annotations-w3c-model (generalizes-with-extension — selector union takes new media selector kinds; FragmentSelector/Media-Fragments is a bridge but lacks rational frame rate/edition identity), editions-critical-edition (needs-new-contract — philology-shaped; VariantUnit review pattern templates EditionAlignmentMap), comparative-structures-collation (needs-new-contract), corpora-canon-registries (generalizes-with-extension — CTS URN/ canonical-uri/versification-mapping discipline is the SourceWork identity shape; versification mapping is the alignment-map precedent), study-plans-research-projects (generalizes), notebooks-library-items (generalizes).
    • Schema extended (reuse-ledger.schema.json) and checker extended (check-reuse-ledger.mjs) to require ≥5 assessments with valid outcomes, substantive rationales, and existing evidence paths.
  • Ground truth read: libs/nisaba/core/src/types/annotations.ts (W3C motivations + TextQuote/TextPosition/Fragment/XPath selector union), libs/nisaba/database/prisma/schema.prisma (CollationProject:306, VariantUnit:349 char-offset units, CriticalEdition:400), libs/nisaba/workspace/src/research-environment.ts, libs/nisaba/canon/.
  • Test command: node tools/yemaya-study/check-reuse-ledger.mjs
  • Negative test: invalid outcome magically-works + nonexistent evidence path produced exactly 2 problems (exit 1); restore returned green.
  • Commit: resolve via git log --grep='YSD-0113', landed as bf148f6544.

YSD-0139 — Decision log published + CI rollout gate (2026-07-18)#

  • Status: complete. The 18 EXT decisions themselves (YSD-0120, YSD-0122–YSD-0138) remain unchecked and proposed — per YSD-0015 they complete only with a named approver; the drafts, harness, and enforcement are this item's deliverable.
  • Artifacts:
    • docs/proposals/yemaya-study-workspace/decisions/ysd-0120.mdysd-0138.md (18 files) — one decision artifact per EXT item, each with Question, Recommendation (grounded in proposal §26 and the Section 0.1 inventories), Options considered with rejection rationale, Consequences, Machine-enforced outcome, and a pending Approval block naming the decision owner (@GreyChimp).
    • docs/proposals/yemaya-study-workspace/decisions/decision-log.json — published machine-readable log (18 entries, all proposed, all blocking: true).
    • tools/yemaya-study/check-decision-log.mjs — validates completeness of every required decision + artifact sections; rejects approved-without- approver/date/outcome (YSD-0015), log/artifact status disagreement, and expired approvals (reviewBy in the past); --enforce-rollout fails while any blocking decision is unresolved — the production-rollout gate the study-workspace deploy pipeline (YSD-1010) must invoke.
    • .github/workflows/ci.yml — plain-mode check added to the quality job.
  • Test command: node tools/yemaya-study/check-decision-log.mjs
  • Passing output: decision-log check passed: 18 decisions tracked, 18 blocking decision(s) still unresolved (informational; rollout gate not enforced); --enforce-rollout correctly fails listing all 18.
  • Negative test: approved-without-fields, artifact/log disagreement, expired reviewBy, and a removed required decision produced exactly 7 problems (exit 1); restore returned green.
  • Commit: resolve via git log --grep='YSD-0139', landed as 27ea6e7a56.

YSD-0150–YSD-0162 + YSD-0165 — Domain responsibility contracts (2026-07-18)#

  • Status: complete (14 definition items; YSD-0163 conformance tests and YSD-0164 architecture tests remain unchecked — they need real adapters and scaffolding to test, else they would be self-asserting gates)
  • Artifacts:
    • docs/proposals/yemaya-study-workspace/responsibility-contracts.md — version 1.0.0, superseded-not-edited. YSD-0150 defines the shared convention (one libs/<owner>/study-adapter per accountable owner, STUDY_ADAPTER_CONTRACT_VERSION semver, capability discovery + YSD-2007 response vocabulary, domain-native ID preservation, no direct cross-domain DB access). Each domain section (0151 Yemaya, 0152 Nisaba, 0153 Sophia, 0154 Hathor, 0155 Aja, 0156 Euterpe, 0157 Aglaea, 0158 Bellona+runtimes incl. the seven named DCC bridges and bridge-lib reuse, 0159 Metis, 0160 Isis, 0161 Oshun, 0162 Iris, 0165 Neith/Maya) carries Responsibilities (checked word-by-word against the checklist item's enumerated list — every listed responsibility present), Adapter boundary, and Must-not constraints.
  • Verification: each of the 14 sections was diffed against its item's enumerated responsibility list before marking; grounded in the YSD-0101 data-authority fields and the §3.2 matrix.
  • Commit: resolve via git log --grep='YSD-0150', landed as 8ccda3dc4c.

YSD-1001 — Architecture ADR (2026-07-18)#

  • Status: complete (ADR created with all seven required dimensions; ADR status is Proposed, acceptance explicitly gated on YSD-0120/0122 approvals — creation, not acceptance, is this item's deliverable)
  • Artifacts:
    • docs/adr/ADR-0074-yemaya-study-deconstruction-workspace-architecture.md — logical architecture (five planes with canonical owners), domain boundaries and data authority (Postgres-authoritative, existing yemaya/nisaba/sophia databases, single rights decision point, epistemic identity as contract property), trust boundaries (untrusted bytes/text, tenant, model, rollout gate), route placement (/studio/study per YSD-0122 draft), deployment topology (ECS Fargate + BullMQ/SQS + RunPod via Isis + MinIO/S3 + FTS/pgvector-first projections), and migration path from specialist apps (studio-web pages and svc-reference-video wrapped/retired-behind-redirect, simulated paths never migrated).
  • Verification: all seven content dimensions named by YSD-1001 checked present; consistent with responsibility-contracts.md v1.0.0, the YSD-0104/0105 inventories, and the 18 decision drafts (no contradiction with any proposed recommendation).
  • Commit: resolve via git log --grep='YSD-1001', landed as 398022c496.

YSD-1003 — Scaffold apps/yemaya/svc-study-workspace (2026-07-18)#

  • Status: complete
  • Artifacts: apps/yemaya/svc-study-workspace/ — Hono/node service with:
    • src/config.ts — zod env schema, fail-fast ConfigValidationError with readable per-field report; production consistency rules (DATABASE_URL required, JSON logs enforced, shutdown grace capped at 25s under the ECS 30s stopTimeout).
    • src/logging.ts — pino structured JSON logs with service/env base fields, credential redaction backstop, YSD-0009 hygiene note.
    • src/request-context.ts — X-Request-Id honor-or-mint (validated token pattern) + real W3C Trace Context: traceparent parse (zero-id rejection), trace continuation with fresh span id, response header emission; request-scoped child logger carries requestId/traceId/ spanId.
    • src/metrics.ts — prom-client registry with default process metrics + RED metrics (duration histogram, in-flight gauge, totals counter) labeled by matched route pattern (bounded cardinality).
    • src/readiness.ts — probe framework with per-probe timeout; real postgres SELECT 1 probe when DATABASE_URL configured; honest not-configured reporting otherwise (visible seam, not fake success).
    • src/app.ts — /health, /ready (503 on failed probe), /metrics, structured 404, error handler that never leaks internals in production; deliberately zero placeholder business routes.
    • src/index.ts — fail-fast config load, serve, graceful shutdown (SIGTERM/SIGINT drain with deadline, pool close, second-signal force exit).
    • project.json (tags scope:yemaya/type:app/plane:experience, build/ serve/lint/test/typecheck targets), catalog-only deps, deploy/README.md deployment manifest (ECS naming contract + gated registration steps + container contract), service README.
  • Test command: npx vitest run (in the app dir) — 15/15 passed; npx tsc --noEmit -p tsconfig.app.json — clean.
  • Runtime proof: booted live on :4321 — structured logs, /health 200 with identity, /ready 200 with honest postgres not-configured probe, inbound x-request-id: smoke-1 honored + valid traceparent emitted, /metrics served 24 histogram bucket lines, SIGTERM → drain → exit 0. Real-postgres ok-path deferred to walking skeleton (dev compose not running); probe failure/timeout paths unit-tested.
  • Note: a NUL byte corrupted one test string during authoring (made the file grep-binary); found via cat -A, fixed byte-safely, suite green.
  • Commit: resolve via git log --grep='YSD-1003', landed as 5238d19b9a.

YSD-0120, YSD-0122–YSD-0138 — All 18 P0 EXT decisions approved (2026-07-18)#

  • Status: complete — approved as recommended by the decision owner (@GreyChimp, the named approver) in this session's conversation ("approve them all as recommended"), recorded 2026-07-18, review-by 2027-07-18.
  • Artifacts: decisions/decision-log.json — every entry now status: approved with approvedBy/approvedOn/outcome/reviewBy; all 18 decisions/ysd-01xx.md artifacts flipped to approved with the approval line filled. Each artifact carries the YSD-0015 required set: decision, named approver, date, rationale, alternatives with rejection reasons, consequences, and machine-enforced outcome.
  • Gate result: node tools/yemaya-study/check-decision-log.mjs --enforce-rollout18 decisions tracked, 0 blocking decision(s) still unresolved (rollout gate: PASS).
  • Downstream effects: ADR-0074 status flipped Proposed → Accepted (was gated on YSD-0120/0122); YSD-1002 (route scaffold), YSD-0121 (Nx/CODEOWNERS boundary encoding), and Section 2 permanent storage shapes (YSD-0128) are now unblocked. The 10 reuse-ledger ownerDecision slots (YSD-0103) are a separate approval set and remain pending.
  • Commit: resolve via git log --grep='YSD-0120', landed as 64955a27a5.

YSD-0103 addendum — All 10 reuse-ledger owner decisions approved (2026-07-18)#

  • Status: the ledger's pending owner decisions are now decided — approved as proposed by the owner (@GreyChimp) in this session's conversation ("approve the reuse-ledger decisions as proposed too"), recorded 2026-07-18. Dispositions now binding: nisaba-study-notebook-substrate → reuse; aja-reference-video-model → wrap; aja-ingestion-scene-detection → replace; performance-reference-library → replace; yemaya-creative-analyzers → wrap (prohibited-inference modules excluded); director-replay-studio-panel → replace; hathor-theory-lenses → reuse; aglaea-fashion-knowledge → reuse; animation-learning-surface → replace; dailies-review-surface → replace.
  • Gate result: node tools/yemaya-study/check-reuse-ledger.mjs20 entries covering 20 proposal §5.2 rows; 0 owner decision(s) pending.
  • Commit: resolve via git log --grep='YSD-0103 addendum', landed as 7ea609cc42.

YSD-0121 — Ownership boundaries encoded and enforced (2026-07-18)#

  • Status: complete (all six encoding surfaces the item names)
  • Artifacts:
    • Nx tags: apps/yemaya/svc-study-workspace/project.json carries scope:yemaya, type:app, plane:experience; the adapter tag scheme (scope:<owner> + type:study-adapter on libs/<owner>/study-adapter) is defined in responsibility-contracts.md and enforced by the architecture checker the moment an adapter directory appears.
    • Dependency constraints: eslint.config.js depConstraints — new plane:experience rule (may depend only on scope:shared/contracts/ oshun/yemaya, type:study-adapter, plane:experience) and type:study-adapter rule (adapters may never import each other; cross-lens composition is experience-plane work). Service files lint clean under the new rules (eslint exit 0).
    • CODEOWNERS: verified generated coverage — libs/yemaya/ and apps/yemaya/ owned by @GreyChimp (lines 103–104), /tools/ covers tools/yemaya-study; staleness is already CI-guarded by check-registry.mjs, so no hand edits (they would break the generator drift check).
    • Contract namespaces: libs/contracts/src/study/ reserved as the single home for study contracts (responsibility-contracts.md YSD-0150; materialized by YSD-1005); scope:contracts depConstraint already prevents contracts from depending on domain code.
    • Service ownership metadata: domains.json registry entry (yemaya, owner @GreyChimp) + project.json tags; registry sync CI-guarded.
    • Architecture tests: tools/yemaya-study/check-architecture.mjs — scans experience-plane and adapter sources for (1) direct cross-domain imports bypassing study adapters, (2) cross-domain database/persistence imports (YSD-0164), (3) prohibited-inference analyzer imports (the four YSD-4052-flagged modules, tests included), and (4) missing required tags; wired into the ci.yml quality job with the other checkers.
  • Test command: node tools/yemaya-study/check-architecture.mjs
  • Passing output: study-workspace architecture check passed: 1 experience-plane project(s), 0 study adapter(s) scanned (1 total).
  • Negative test: a planted file importing @sophia/knowledge-graph, @hathor/database, and .../eye-contact-gaze-monitor produced exactly 3 problems, one per rule (exit 1); removal returned green.
  • Commit: resolve via git log --grep='YSD-0121', landed as e8434e418a.

YSD-1002 — Canonical Studio route /studio/study (2026-07-18)#

  • Status: complete (scaffold at the approved route with the approved layout conventions; the global "Study" nav entry is Section 7 work)
  • Artifacts:
    • apps/oshun/web/src/app/studio/study/page.tsx — follows the Studio route convention exactly (Metadata with canonical /studio/study per approved YSD-0122, ShellLayout active="studio", workspace component, quickAction links to real surfaces: /studio, /nisaba/notebooks, /library).
    • apps/oshun/web/src/components/studio/StudioStudyWorkspace.tsx — the entry surface with an explicit honesty contract: a typed STUDY_LAUNCH_CAPABILITIES list where every capability reports its real state; the six unshipped capabilities (projects/ingest, playback/ annotation, camera/performance/narrative/sound lenses per the approved YSD-0126 set) render "Not yet available" with zero interactive affordance; the two available entries link to genuinely shipped surfaces (Nisaba notebooks, shared Library).
    • apps/oshun/web/vitest.config.ts — include pattern extended to .spec.{ts,tsx} (new .test. files are ratcheted; existing pattern only matched .test.).
    • apps/oshun/web/src/app/studio/study/__tests__/study-page.spec.tsx — 4 tests: identity/posture; honesty invariant (every unavailable capability shows the explicit status and renders no link/button); available links point only to route directories that exist on disk (fs-verified, so the page can never advertise a dead destination); metadata declares the approved canonical route.
  • Test command: npx vitest run src/app/studio/study (in apps/oshun/web) — 4/4 passed.
  • Commit: resolve via git log --grep='YSD-1002', landed as 9eca3bec5e.

YSD-1005 + YSD-2001–YSD-2004 — Study contracts namespace + first primitives (2026-07-18)#

  • Status: complete (five items, each verified against its full text)
  • Artifacts: libs/contracts/src/study/ (the YSD-0121-reserved namespace), exported as @oshun/contracts/study subpath and StudyContracts namespace (namespace-only at root to avoid name collisions with other domains' SegmentId/Timecode):
    • ids.ts (YSD-2001) — 35 branded identifiers covering every Section 2.2 core entity plus AnalysisRunId, RightsDecisionId, ReviewDecisionId, StudyEventId, StudyExportId, ProductionBacklinkId; domain-owned identities referenced through *Ref brands over native formats (Nisaba/Metis/Sophia/Isis — never re-minted); DomainNativeObjectRef preserves arbitrary native IDs verbatim (YSD-3002 mapping philosophy).
    • author.ts (YSD-2004) — strict discriminated union human/model/imported-system/collaborative-group; model identity carries modelId/modelVersion/analysisRunId and structurally cannot validate with a userId (conflation-rejection tested both directions).
    • envelope.ts (YSD-2002) — all nine required fields (schemaVersion, tenant, project, creator, timestamps, revision, supersession, provenance, deletionState) with supersession-chain refinements and the pure append-only superseding() helper (deterministic — caller supplies time; refuses already-superseded and legal-hold records).
    • locators.ts (YSD-2003) — all nine primitives: rational frame rate (exact cross-multiplication equality, gcd canonicalization, known-rate constants), real SMPTE 12M drop-frame timecode (dropped-label validation; bidirectional conversion), monotonic time (bigint-safe string nanoseconds + clock-domain-guarded comparison), wall-clock, normalized/pixel/spatial/document coordinates with refinements, and runtime-event locators requiring at least one locating field.
    • study.spec.ts — 21 tests including known-correct SMPTE anchors (00:00:59;29→1799, 00:01:00;02→1800, 00:10:00;00→17982, 01:00:00;00→107892), a dense 2,000-frame DF round-trip sweep plus 59.94 DF samples, envelope chain negatives, and region round-trips.
  • Test command: npx vitest run src/study (in libs/contracts) — 21/21 passed; npx tsc --noEmit -p tsconfig.json — clean.
  • No duplicate domain types (YSD-1005): tenant identity follows the platform's canonical-audit-event convention (opaque token, no new brand); Nisaba/Metis/Sophia records are referenced, not redefined.
  • Commit: resolve via git log --grep='YSD-1005', landed as 1a4a675bd9 (SKIP_TYPECHECK=1 used per the documented nx-typecheck fan-out trap — 289-project hook typecheck surfaced pre-existing common/citation.ts errors in untouched files; libs/contracts itself tsc-clean, verified directly).

YSD-2005–YSD-2007 — Review states, confidence, response contracts (2026-07-18)#

  • Status: complete (three items, each verified against its full text)
  • Artifacts (in libs/contracts/src/study/):
    • review.ts (YSD-2005) — the six states as a closed enum; the legal transition table (suggested→accepted/corrected/contested/rejected; accepted|corrected→contested/superseded; contested→accepted/corrected/ rejected; rejected→superseded; superseded terminal) with canTransitionReviewState/assertReviewTransition and a typed IllegalReviewTransitionError; immutable ReviewEvent (subject id + pinned revision, reviewer, reason, occurredAt) with refinements: models cannot accept/correct/reject (suggest-and-contest only), corrected transitions must carry corrections; replayReviewEvents derives state by replay and rejects broken chains.
    • confidence.ts (YSD-2006) — calibrated confidence with required calibration lineage (method, set id+version, validatedAt), bracketing uncertainty interval with coverage, and applicability boundary; explicit uncalibrated (raw score + native scale) and missing (typed reason) variants; isDisplayableProbability allows only calibrated values — bare numbers do not parse.
    • responses.ts (YSD-2007) — CapabilityDescriptor (dotted capability key, supported/unsupported/unavailable with mandatory reason when not supported, adapter contract version) and makeStudyResponseSchema producing the full outcome union: ok, ok-degraded (with mode+reason), partial-success (≥1 completion, exact failure manifest), unsupported, unavailable (retryable/retryAfterMs coherence), forbidden (policy), and insufficient-rights (denied action + inspectable RightsDecisionId).
  • Test command: npx vitest run src/study (libs/contracts) — 32/32 passed (11 new: exhaustive 36-pair transition matrix, model-reviewer rejection, replay chain breaks, interval bracketing, partial-success coherence); tsc --noEmit clean.
  • Commit: resolve via git log --grep='YSD-2005', landed as 78135b41a5.

YSD-2008 — Schema publication, examples, compatibility tests (2026-07-18)#

  • Status: complete. Component-by-component against the item text: JSON Schema ✓ (17 draft-2020-12 schemas published from the registry); OpenAPI ✓ (openapi.components.json 3.1 components document); runtime validation ✓ (zod layer, declared authoritative in the manifest); generated client — satisfied by the typed package (zod-inferred types are the generated client surface for TS consumers) plus the published schema set proven independently consumable via ajv in the spec suite; HTTP-API client generation explicitly belongs to YSD-1009 where service APIs exist — recorded here so the 22.3 audit can contest the interpretation; canonical examples ✓; negative examples ✓; backward-compatibility tests ✓.
  • Artifacts:
    • libs/contracts/src/study/schema-registry.ts — the single publication registry (17 contracts); a contract cannot exist without entering the pipeline (manifest-vs-registry equality is tested).
    • libs/contracts/scripts/generate-study-schemas.ts — emits per-contract JSON Schema with versioned $id, the OpenAPI components doc, and a manifest carrying the runtime-vs-structural caveat; --check fails on drift or breaking changes; --write-baseline freezes the per-major snapshot (schemas/study/baseline/v0/); exports findBreakingChanges (removed contract/property, type change, enum narrowing, newly-required property, additionalProperties tightening, union narrowing) — the YSD-2010 detector foundation.
    • libs/contracts/src/study/examples.ts — canonical + structural-negative sets for all 17 contracts, plus zodOnlyNegatives pinning exactly where the runtime layer is stricter (cross-field refinements, formats).
    • libs/contracts/src/study/schema-publication.spec.ts — 7 tests: no-drift, example coverage for every contract, canonical examples pass BOTH zod and ajv-against-published-schema, structural negatives fail BOTH layers, runtime-only negatives fail zod, no breaking changes vs the committed baseline, and detector unit tests on synthetic diffs.
    • CI: --check added to the quality job; package scripts generate:study-schemas / check:study-schemas.
  • Test command: npx vitest run src/study (libs/contracts) — 39/39 passed; npx tsx scripts/generate-study-schemas.ts --check17 contracts published, no drift, no breaking changes vs baseline v0.
  • Commit: resolve via git log --grep='YSD-2008', landed as 0ec646e9cf (merge 822f3c900b; generated schemas added to .prettierignore after the pre-commit prettier reformat broke byte-exact drift comparison — schemas and baseline regenerated to generator format).

YSD-2009 + YSD-2010 — Property round-trips + compatibility policy (2026-07-18)#

  • Status: complete (both items verified against their full text)
  • Artifacts:
    • libs/contracts/src/study/compat.ts (YSD-2009) — the version-tolerant boundary: SUPPORTED_STUDY_SCHEMA_VERSIONS, assertSupportedSchemaVersion (explicit refusal of unknown versions), and parseWithCompat — strips ONLY pure unknown-key failures (a newer writer's optional fields), reports every stripped path, never mutates the caller's object, and refuses to rescue any real validation failure (mixed failures stay failures).
    • libs/contracts/src/study/roundtrip.property.spec.ts (YSD-2009) — fast-check property suites (fast-check added to the pnpm catalog): constructively-valid arbitraries for envelope/author/region/monotonic/ locator/confidence; per-contract properties that (a) JSON wire round-trips are identity AND both serializer layers (zod runtime, ajv-compiled published schema) agree on 200 random instances each, and (b) unknown optional fields from a newer writer fail strict parsing but pass the compat boundary with knowns preserved and the injected key reported; 700-run drop-frame timecode round-trip property (29.97/59.94 DF + 24 non-drop); arbitrary-length superseding-chain lineage property; prior-supported-version suite (every supported version's canonical examples parse under the current runtime; unknown versions refused explicitly; compat never masks real failures).
    • docs/proposals/yemaya-study-workspace/compatibility-policy.md (YSD-2010) — semver rules (breaking=major with new baseline, additive=minor via the compat boundary, append-only history), the per-class table for contracts/events/taxonomies/saved-views/portable- exports with class-specific rules, the registry-as-chokepoint mechanism (a class entering the registry automatically enters CI breaking-change detection — no per-class opt-in to forget), support windows, and the no-self-approval escalation rule for claimed-safe breaking changes. Automated detection is live in CI (--check in the quality job) for everything published; events/taxonomies/views/exports enter it by construction when their schemas land.
  • Test command: npx vitest run src/study (libs/contracts) — 56/56 passed (17 new).
  • Commit: resolve via git log --grep='YSD-2009', landed as 500ec3a395.

YSD-2020–YSD-2023 — StudyProject, StudyQuestion, SourceWork, SourceEdition (2026-07-18)#

  • Status: complete (four items, each verified against its enumerated field list)
  • Artifacts (in libs/contracts/src/study/entities/):
    • study-project.ts (YSD-2020) — all ten required facets: members (role enum, ≥1 owner enforced, unique userIds), purpose, curriculum (MetisConceptRefs), policies (RightsGrantIds), sources, notebooks (NisabaNotebookRefs), collections, target projects (yemaya-production/instrumented-game refs), archive state, and append-only membership history with rosterFromHistory replay (remove-wins, joinedAt stable across role changes — tested).
    • study-question.ts (YSD-2021) — question text, the exact four-state lifecycle (open/under-investigation/answered/abandoned) with a legal transition table including evidence-driven reopening, scope, originating context (description + optional anchor), linked evidence/ comparisons/collections/exercises, and resolution history whose replay must equal the stored lifecycle (drift rejected); answers must cite ≥1 evidence anchor (YSD-2112 alignment).
    • source-work.ts (YSD-2022 + YSD-2023) — SourceWork with copy- independent identity, ambiguity-preserving identity candidates (closed certainty vocabulary — numeric pseudo-confidence rejected), and human-reviewed append-only merge events; SourceEdition with the full enumerated identity (cut/build/patch/platform/region/language/ frame-rate/aspect/color/audio/subtitle), rational-only frame rates (floats rejected), sha256 content-hash immutable technical revision, game-builds-require-platform and frames-require-rate refinements.
    • All four registered in the publication registry (now 21 contracts) with canonical/structural-negative/zodOnly example sets — automatically inside the dual-layer validation and baseline compat pipeline.
  • Test command: npx vitest run src/study (libs/contracts) — 66/66 passed (10 entity tests + expanded publication coverage); tsc clean.
  • Commit: resolve via git log --grep='YSD-2020', landed as 5498a37c73.

YSD-2024–YSD-2027 — Alignment, SourceAsset, RightsGrant, MediaTrack (2026-07-18)#

  • Status: complete (four items, each verified against its enumerated field list)
  • Artifacts (in libs/contracts/src/study/entities/):
    • alignment.ts (YSD-2024) — EditionAlignmentMap pinning exact endpoint technical revisions (sha256), five construction methods, correspondence kinds exact/approximate/reordered/added/removed/rescored/regraded/ unmapped with side-presence rules (added has no source range, removed no target range — enforced), per-correspondence confidence + review state, EXPLICIT gaps (surfaced, never interpolated — YSD-2135), and anchor-migration PROPOSALS with index-validated correspondence links (proposals never self-apply — YSD-2133/2134).
    • source-asset.ts (YSD-2025 + YSD-2027) — SourceAsset covering all ten kinds with a storage discriminated union: owned bytes (object-store + content hash) XOR link-only provider references (stream references cannot store bytes and files cannot be links — YSD-4009 enforced both directions); rights-bound from birth (≥1 grant); engine assets/scenes must keep native identity; sidecar metadata objects. MediaTrack with all ten stream kinds, per-kind clock requirements (video⇒rational frame rate, audio⇒sample rate, custom⇒label).
    • rights-grant.ts (YSD-2026) — all fourteen enumerated facets; the twelve YSD-4002 actions as a closed deny-by-default vocabulary; model-training gated on unrevoked per-participant consent (YSD-4036 — revoked consent tested to not count); user-declared references quarantined from sharing/model/export actions (YSD-4010); attribution text required when flagged; validity ordering; ISO territory codes.
  • Test command: npx vitest run src/study (libs/contracts) — 76/76 passed (10 new); 25 contracts published; tsc clean.
  • Commit: resolve via git log --grep='YSD-2024', landed as d4ef0debfd.

YSD-2028–YSD-2032 — Segment, EvidenceAnchor, StudyEntity, Detection, Observation (2026-07-18)#

  • Status: complete (five items, each verified against its enumerated field list)
  • Artifacts (in libs/contracts/src/study/entities/):
    • anchor.ts (YSD-2029) — the six-kind SourceLocator discriminated union (frame-range, frame-region, transcript-span with pinned exactText for tokenization-drift detection, document, runtime-event-range, spatial with nodePath-or-position rule) and EvidenceAnchor pinning exact edition + sha256 technical revision, policy-permitted representations (YSD-2102 vocabulary), acquisition provenance, ≥1 rights-grant dependency, review state, envelope-immutable versioning, and the deep-link restore path.
    • segment-entity.ts (YSD-2028 + YSD-2030) — StudySegment with the film+game kind vocabulary plus labeled custom kinds (YSD-2071 — domain-native terms never coerced), hierarchy with self-parent rejection, pinned source revision, segmentation version, author, confidence, append-only corrections, and the born-accepted wall (model-authored segments must start suggested — YSD-0019); StudyEntity with the ten core kinds + domain-owned extensibility requiring owning domain + native kind, and the work-scope note (cross-work identity is a reviewed cross-reference, never implicit — YSD-2082).
    • epistemic.ts (YSD-2031 + YSD-2032) — Detection requiring processor code identity, exact configuration, analysisRunId (no orphan outputs — YSD-2113), ≥1 anchor (YSD-2112), confidence, review state, and applicable limitations; Observation as human-authored-by-type (model authorship structurally rejected — the epistemic wall of YSD-2108 at the contract layer), with modality, explicit scope, and ≥1 anchor.
  • Test command: npx vitest run src/study (libs/contracts) — 84/84 passed (8 new); 31 contracts published; tsc clean.
  • Commit: resolve via git log --grep='YSD-2028', landed as ff941438b2.

YSD-2033–YSD-2036 — InterpretationClaim, CraftHypothesis, TaxonomyTerm, ComparisonSet (2026-07-18)#

  • Status: complete (four items, each verified against its enumerated field list)
  • Artifacts (in libs/contracts/src/study/entities/):
    • claims.ts (YSD-2033 + YSD-2034) — InterpretationClaim with the exact eight-status YSD-2109 epistemic vocabulary, mandatory support anchors (YSD-2112), first-class counterevidence, coexisting dissents (YSD-2111 — the original reading survives beside them, tested), corpus boundary, limitations, review history, and two walls: model authors are forced to model-suggested status (YSD-2108) and contested status must actually show dissent or counterevidence. CraftHypothesis with derivation requirement (claims or anchors), the four-value named causal-strength classification (numeric strength rejected), counterexamples, intended practice, contextual applicability, and the no-universal-laws rule (experimentally-tested requires limitations or counterexamples).
    • taxonomy-comparison.ts (YSD-2035 + YSD-2036) — TaxonomyTerm with owner domain, semver vocabulary version, preferred label, aliases, BCP-47-validated translations, provenance (YSD-4072 authorship), broader/narrower/related with self-relation rejection, applicability, and a lifecycle union where deprecation REQUIRES a migration rule. ComparisonSet with research question, stated inclusion logic, exclusions, example/counterexample members, index-validated pairwise alignments with frame offsets, five layouts with the 2–8 focused / matrix-beyond-8 rule (YSD-22011 at the contract layer), corpus boundary, collaborators, and revision-pinning frozen snapshots.
  • Test command: npx vitest run src/study (libs/contracts) — 95/95 passed (11 new); 35 contracts published; tsc clean.
  • Commit: resolve via git log --grep='YSD-2033', landed as d94b08d387.

YSD-2037–YSD-2040 — Inspiration, CrossReference, CreativePrinciple (2026-07-18)#

  • Status: complete (four items, each verified against its enumerated field list)
  • Artifacts (in libs/contracts/src/study/entities/):
    • inspiration.ts (YSD-2037 + YSD-2038) — InspirationCollection with creative question, target projects/concepts, intended audience effect, scope, exclusions, saved views (presentation-only state — spatial proximity never creates evidence relations, YSD-2154), collaborators, and a mandatory diversity posture; InspirationItem with all twelve facets — authorized anchor + restated rights binding, relevant aspect, intended effect/problem, principle/question links, a MANDATORY do-not-copy boundary (the YSD-4033/4035 transfer-safeguard seed), six-role vocabulary, mandatory attribution, cultural context, confidence, review, envelope versioning.
    • cross-reference.ts (YSD-2039 + YSD-2040) — CrossReference with the approved fifteen-relation vocabulary (YSD-0133, shared with YSD-2151), symmetric-relations-cannot-be-directed enforcement (YSD-2152), revision-pinned endpoints (YSD-2156), rationale/evidence/ counterevidence/context-bounds/rights/validity, self-edge rejection, and the identity rules of YSD-2155 in the type system: identity edges cannot be born accepted and models cannot assert identity at all (similarity only). CreativePrinciple with abstracted wording, supporting AND contradicting sources, mandatory transformation rationale (the distance-from-source argument), limitations, review, outcome-typed track record, and project applicability.
  • Test command: npx vitest run src/study (libs/contracts) — 106/106 passed (11 new); 39 contracts published; tsc clean.
  • Commit: resolve via git log --grep='YSD-2037', landed as 7966c5a747.

YSD-2041–YSD-2043 — OriginalConcept, CreativeDecision, OutcomeEvidence (2026-07-18)#

  • Status: complete (three items, each verified against its enumerated field list)
  • Artifacts (libs/contracts/src/study/entities/creative.ts):
    • OriginalConcept (YSD-2041) — project-owned identity, seven concept kinds, typed self-rejecting relationships, rationale, envelope version history, and the two structural prohibitions: authorship is source-independent (humans/groups only — model and imported-system authors rejected) and the masquerade ban is architectural — the schema has NO locator/edition/anchor fields and .strict() rejects any attempt to add them (tested with editionId/locator/anchorId injections); source connection exists only through principle derivations and inspiration items.
    • CreativeDecision (YSD-2042) — all eleven facets; committed decisions must carry ≥1 principle/source path (the YSD-22013 source-to-decision trail) and ≥1 named approval, while proposed decisions may still be forming; immutable revisions via the envelope.
    • OutcomeEvidence (YSD-2043) — intended effect, four source kinds, consent evidence REQUIRED for playtests/screenings (YSD-4056) but not releases, ≥1 anchor, five-value result vocabulary, human interpretation separate from the result value, mandatory uncertainty statement, linked decision, and epistemic identity fixed by literal to 'practice-result' (cannot claim source-fact status — YSD-2107).
  • Test command: npx vitest run src/study (libs/contracts) — 115/115 passed (9 new); 42 contracts published; tsc clean.
  • Commit: resolve via git log --grep='YSD-2041', landed as c47f8033ab.

YSD-2044–YSD-2048 — Notebook/StudyCard + learning-loop contracts (2026-07-18)#

  • Status: complete (five items, each verified against its enumerated field list)
  • Artifacts (in libs/contracts/src/study/entities/):
    • notebook.ts (YSD-2044) — StudyNotebook keyed by NISABA canonical identity (no workspace UUID field exists; injecting one is rejected — tested), source-grounded narrative with marker-deduplicated anchored citations, typed rights-aware embeds (authored representation from the YSD-2102 vocabulary; rights re-checked at view time), card refs, three-state publication where publishing requires ≥1 citation, and envelope versioning. StudyCard as the atomic cited insight (≥1 anchor mandatory) with optional claim/comparison distillation.
    • learning.ts (YSD-2045–2048) — PracticeExercise with objective, Metis concept, ≥1 constraint, behavior-anchored rubric (≥1 criterion), anchored examples requiring rights grants, expected evidence, accommodations, self-paced/due union, self-study/assigned union; PracticeAttempt learner-owned with ≥1 artifact, revisions, evidence-linked feedback with retained learner challenges (YSD-4077), reflection, structured scoped sharing consent with revocation, and the learner-deletion-request field feeding the YSD-3063 saga; MasteryRecord referencing the Metis canonical record with named-level+uncertainty estimate (numeric scores do not parse), system/human assessor union, history, and the taste wall as a literal (measures: 'concept-mastery-only' — taste/originality scoring unrepresentable, injection tested); SpacedReviewItem with the four source kinds (card/claim/term/drill), FSRS schedule state, explicit scheduler version, anchors, mandatory discrimination target, rating-typed review history, and rights-permitted derivative references.
  • Test command: npx vitest run src/study (libs/contracts) — 126/126 passed (11 new); 48 contracts published; tsc clean.
  • Commit: resolve via git log --grep='YSD-2044', landed as a6e34e44b0.

YSD-2049–YSD-2052 — Transfer, AnalysisRun, ReviewDecision, fixtures capstone (2026-07-18)#

  • Status: complete — Section 2.2 is now 33/33
  • Artifacts:
    • entities/transfer-run-review.ts — ProjectTransfer (YSD-2049) whose payload union contains ONLY abstract kinds (principle/constraint/ question/task — a media payload is unrepresentable, YSD-4035), with destination, produced artifact, sanctioning decisions (≥1), rights review state, inspectable advisory originality warnings (YSD-4038), recorded-never-silent blocked content with typed blocked classes, backlink, and downstream impact refs. AnalysisRun (YSD-2050) with all eleven facets and four lifecycle coherence rules (cancelled⇒who+why, failed⇒failures, succeeded⇒outputs — success without outputs is not success, queued/running⇒no actual cost). ReviewDecision (YSD-2051) with reviewer authority vocabulary, five actions, correct⇒corrections, supersede⇒higher version, prior/new versions, conflict context, timestamp, and canonical audit event identity.
    • fixtures.ts + scripts/generate-study-goldens.ts + fixtures-golden.spec.ts (YSD-2052) — deterministic builders (no randomness, no clock, no restricted media, schema-validated construction) covering every published contract with minimal + complete cases, and ALL seVEN required case kinds: prior-version (oldest supported schema version), rejected (every reviewable contract), rights-expired (grant with ended validity + revocation), contested (claim with retained dissent), superseded (built through the real superseding() helper for every enveloped contract), plus soft-deleted deletion cases; 213 golden fixture cases committed and byte-compared, with the golden check wired into CI beside the schema check.
  • Test command: npx vitest run src/study (libs/contracts) — 131/131 passed; goldens --check green; 51 contracts published; tsc clean.
  • Commit: resolve via git log --grep='YSD-2049', landed as fe41f98ed4.

YSD-2070–YSD-2077 — Hierarchy + film/game anchors + sync contract (2026-07-18)#

  • Status: complete (eight items, each verified against its enumerated vocabulary)
  • Artifacts (in libs/contracts/src/study/anchors/):
    • hierarchy.ts (YSD-2070/2071) — the ten ordered levels verbatim from the item text; depth/legality helpers (child strictly finer, skipping allowed, inversion rejected); the native-term map carrying film AND game vocabulary onto shared levels without coercion (round ≡ shot in level, not in name; unknown terms return null — never guessed); HierarchyNodeSemantics with parent/order/overlap/provenance.
    • film.ts (YSD-2072/2073) — FilmStructureAnchor with the full thirteen-kind structural vocabulary (reel…word, frame, region, language-track), edition + sha256 revision pinning, rational frame rate + optional SMPTE timecode, per-kind data rules (region⇒region, word/line/track⇒language track); FilmEntityAnchor with the twelve entity kinds and audio-side kinds requiring their audio track.
    • game.ts (YSD-2074–2077) — GameIdentityAnchor with all nine identity facets (explicit empty-mods assertion, capture configuration, content-manifest hash); GameSessionAnchor with the twelve session kinds, locator-required rule, save-states requiring engine-native refs; GameEntityAnchor with the twelve entity kinds carrying engine-native identity; SessionSyncContract as a TWO-variant union — deterministic replay (seed + input-stream hash) or video-clock mapping with MANDATORY uncertaintyMs — no third option exists, so unstated sync error is unrepresentable.
    • All seven anchor contracts registered (58 published contracts; 227 golden cases).
  • Test command: npx vitest run src/study (libs/contracts) — 142/142 passed (11 new); tsc clean.
  • Commit: resolve via git log --grep='YSD-2070', landed as efd67632c2.
  • Status: complete — Section 2.3 is now 15/15
  • Artifacts (in libs/contracts/src/study/anchors/):
    • document-still.ts (YSD-2078/2079) — DocumentAnchor addressing document/edition/page/panel/block/line/word with rectangular OR polygonal regions and page/ordinal/char-span rules; StillImageAnchor pinning image revision + canvas, optional layer (never fabricated for flat formats), pixel/normalized region union with out-of-canvas rejection, review-gated detected entities, palette regions with sRGB-validated dominance-ordered colors, and user guides (line/curve/silhouette-trace/grid).
    • three-d.ts (YSD-2080/2081) — Asset3DAnchor with all fifteen kinds and per-kind data obligations (vertex/face sets enumerate indices, uv-island/material-slot carry ordinals, texture regions carry regions, clips/time-samples carry names+times), engine-native paths verbatim; LevelSceneAnchor with the ten level kinds, native refs, and gameplay references naming their system.
    • identity-canon.ts (YSD-2082/2083) — CrossViewIdentityCandidate with five evidence bases, MANDATORY uncertainty, and two walls: appearance-only identity can never be accepted (corroborating bases required) and model proposals stay suggested/contested. Canonicalization/equality: exact cross-rate frame-time equality by rational cross-multiplication (unreduced 23.976 forms equal; same frame number at different rates correctly unequal; 24@24fps == 25@25fps == 1s), transcript tokenization drift fails loudly via pinned exactText, pixel↔normalized equivalence with explicit one-pixel tolerance, and telemetry-clock cross-domain comparison returning null — never a silent boolean.
    • deep-link.ts (YSD-2084) — loss-free URL encoding (canonical key-sorted JSON in base64url) restoring exact edition + revision + locator + entity + permitted view; deterministic; malformed links throw typed errors; round-trip verified for every locator kind including a unicode transcript span, plus a 100-run property test.
  • Test command: npx vitest run src/study (libs/contracts) — 155/155 passed (24 anchors tests); tsc clean. One test-authoring error caught and fixed: the unreduced-rate equality case had doubled the frame count (1001s ≠ 2002s) — corrected to same-count-same-time.
  • Commit: resolve via git log --grep='YSD-2078', landed as c95d73e57e.

YSD-2101/2103/2109–2113 — Epistemic integrity contracts + invariant suites (2026-07-18)#

  • Status: complete (seven items). YSD-2100/2102/2104/2105/2106/2107/ 2108 remain deliberately unchecked: their contract halves exist (pinned identity, representation vocabulary, rights binding, display predicate, authorship/review models, distinct epistemic types) but the items demand runtime/UI/persistence ENFORCEMENT that lands with Sections 3/5/7 — no under-verified marking.
  • Artifacts:
    • entities/compound-locator.ts (YSD-2101) — conjunction of 2–10 distinct component locators (frame range + region + transcript span + entity + runtime events + document location all combinable), duplicate components rejected, mandatory conjunction rationale; registered + published.
    • EvidenceAnchor derivation (YSD-2103, additive) — parent anchors (≥1)
      • processor/codeVersion/model/configuration, completing acquisition method + source revision + processor + model + code + configuration + derivation lineage on evidence.
    • InterpretationClaim additive fields (YSD-2110) — named supportStrength (single-source→corpus-wide), reviewerAgreement tally, culturalContextBounds; joins existing counterevidence/corpus-boundary/ limitations to complete the item's stored set.
    • YSD-2111 verified complete on existing artifacts: the Dissent model (coexisting readings tested — original survives beside dissent) plus the contradiction relation type for contradictory claims.
    • epistemic-invariants.spec.ts (YSD-2112/2113 + 2109 closure) — the six unanchored-rejection invariants (analysis, observation, claim, comparison member, inspiration item, feedback item) and the identity invariants (no machine output without run + processor code version; no interpretation without epistemic status + author); closed-vocabulary assertion for the eight governed statuses.
    • Pre-release tightening, documented: YSD-2112 exposed that feedback items could be unanchored (evidenceAnchorIds had no minimum) — real gap, fixed to min(1). Per the compatibility policy this is breaking; handled as a documented pre-release tightening: STUDY_CONTRACTS_VERSION 0.1.0→0.2.0, 0.1.0 retained in the supported list, baseline v0 regenerated same-day (nothing consumes v0 externally yet). Recorded here rather than silently.
  • Test command: npx vitest run src/study (libs/contracts) — 168/168 passed (13 new invariants); 59 published contracts; 229 golden cases; tsc clean.
  • Commit: resolve via git log --grep='YSD-2101'.

2026-07-18 — YSD-2132, YSD-2133, YSD-2134, YSD-2135, YSD-2137 (Section 2.5: alignment provenance + anchor migration)#

  • Scope: the migration-logic half of Section 2.5. Deliberately left unchecked: YSD-2130 (manual authoring/review UI — Studio surface, Section 7), YSD-2131 (real audio-fingerprint/shot-structure/subtitle/telemetry proposal pipelines — media plane, Section 5), YSD-2136 (build/patch map persistence + revalidation task queue — Section 3/5; the contract SHAPE is exercised here but live-service revalidation scheduling is not built yet).
  • Artifacts (libs/contracts/src/study/):
    • Correspondence processor (YSD-2132, additive) — optional codeVersion/modelId/modelVersion object on each correspondence, completing the stored set: kind + method + model/code version + confidence + source/target ranges + review state (the rest already existed on EditionAlignmentMap/Correspondence from YSD-2064).
    • entities/anchor-migration.ts (YSD-2133/2134/2135) — pure deterministic logic. proposeAnchorMigrations(map, anchors): throws unless the map is accepted (proposals never come from suggested/contested/rejected maps) and unless every anchor sits on the map's exact source edition + technical revision; output is proposals + gap notices only — nothing self-applies (YSD-2133). Anchors overlapping explicit gaps or removed/added/reordered/ rescored/regraded correspondences return MigrationGapNotice with a reason and are never interpolated (YSD-2135); only ACCEPTED exact/approximate correspondences that fully contain the anchor range map it, by exact offset arithmetic. confirmAnchorMigration requires a human/collaborative-group ACCEPTING ReviewEvent (model reviewers and non-accepting events throw), parses the result through EvidenceAnchorSchema, and returns { migrated, original } — the original is untouched so rollback = drop the migrated anchor.
    • EvidenceAnchor migratedFromAnchorId (YSD-2134, additive) — both ends of the lineage persist: the migrated anchor names its origin; the original remains valid on its own edition.
    • entities/anchor-migration.spec.ts (YSD-2137) — the five named categories over a representative theatrical→director's-cut map (censor-cut removal, -500 offset shift, reorder, added material, plus a REJECTED overlapping correspondence) and a game build→patch telemetry-landmark map: accuracy (known-correct offset-mapped target frames, both offsets), false-correspondence (rejected correspondence covering the same frames is never chosen), gap-detection (censor cut, boundary straddle, reorder → gap notices with reasons, "not interpolated" asserted), anchor-migration (human confirmation → lineage both ends; model reviewer and contested events rejected), rollback (original returned untouched, migratedFromAnchorId absent on it). End-to-end re-verification against real media rides Section 5's pipelines.
  • Test command: npx vitest run src/study (libs/contracts) — 176/176 passed (8 new); 61 schema artifacts regenerated + baseline v0 (additive only: optional processor, optional migratedFromAnchorId); 229 golden cases byte-stable; tsc clean.
  • Commit: resolve via git log --grep='YSD-2133'.

2026-07-18 — YSD-2150..YSD-2159 (Section 2.6: typed relationship graph contracts)#

  • Scope: the full graph contract layer — vocabulary, per-relation semantics, presentation separation, permission model, query contracts with reference algorithms, and structural invariants. PostgreSQL-side enforcement and the Studio graph surface ride Sections 3/6/7 on these contracts.
  • Artifacts (libs/contracts/src/study/):
    • GRAPH_NODE_KINDS (YSD-2150) — 29 addressable node kinds: the 25 proposal kinds (corpus, work, edition/build, sequence, scene, encounter, shot, beat, frame, image, region, entity, material, palette, pose, motion, sound, transcript, runtime-event, document-passage, claim, principle, original-concept, creative-decision, production-artifact) plus the four workspace record kinds already addressable as endpoints (anchor, segment, comparison-set, inspiration-item). CrossReference endpoints now accept all of them (enum widening — additive).
    • YSD-2151 verified complete on the existing RELATION_TYPES — exactly the fifteen approved relations, asserted by count and name in tests.
    • RELATION_SPECS (YSD-2152) — one spec per relation: directionality (5 symmetric / 10 directed; SYMMETRIC_RELATIONS now DERIVED from the table so they cannot drift), forward + inverse plain-language labels, one-line description, endpoint kind constraints (support/contradiction/counterexample land on claim|principle; derived-principle on principle; informed-decision on creative-decision; avoidance originates from the creative plane; identity requires same-kind endpoints), transitivity policy (never-infer / path-display-only / ordering-composes — nothing ever materializes stored edges), and acyclicity. Parse-level enforcement added to CrossReferenceSchema (directed coherence + endpoint constraints) — zod-side rules on the pre-release 0.2.0 contract; JSON-schema layer is purely additive (endpoint enum widening + optional field), so the baseline compat check stays green by construction.
    • Correspondence culturalBounds — first-class optional cultural scope on edges (YSD-2153) so cultural claims are never smuggled into generic context text; the item's full stored set (rationale, author or suggesting model, time, confidence, review state, evidence, counterevidence, context bounds, cultural bounds, rights dependencies, validity, supersession) asserted present in one test.
    • entities/graph.ts (YSD-2154..2159) —
      • Presentation state (YSD-2154): CanvasPlacement/CanvasGroup/ SavedGraphView are .strict() with no relation fields — a placement literally cannot express a relationship; tests prove adjacent + co-grouped nodes produce no backlinks, no paths, and two singleton clusters.
      • YSD-2155: identityMergeCandidates returns ACCEPTED identity edges and nothing else — an accepted maximally-confident similarity edge is asserted to never be a merge candidate; suggested identity stays provisional (born-accepted ban and model-author ban already parse-level from YSD-2039).
      • YSD-2156: endpoint revisions pinned via refRevision (existing); supersedeEdgeForEndpointChange requires an accepting human/collaborative-group ReviewEvent, uses the envelope superseding() lineage, pins the new endpoint revision on the successor, keeps the old revision historical; stale revisions, non-accepting reviews, and model reviewers throw.
      • YSD-2157: GraphPermissionEvaluator applied to EVERY node, edge, and derived result in every function — an edge to a hidden node disappears entirely (no existence leakage); tests show hidden nodes break paths, vanish from neighborhoods, and zero out dependency-impact.
      • YSD-2158: backlinks (bidirectional, per-side plain-language labels), findPaths (bounded simple-path enumeration with hop directions), neighborhood (bounded BFS reporting truncated honestly), connectedClusters (union-find), dependencyImpact (forward closure over support/derived-principle/informed-decision/ transformation with edge paths + severed-edge list), GraphTraversalFilter on every function (relation/kind/review/ superseded filters), SavedGraphView (grouping + saved views), and GraphQueryRequest — a discriminated union whose bounds are schema-enforced (an unbounded neighborhood request does not parse). Both new schemas registered + published (63 artifacts, 233 golden cases).
      • YSD-2159: checkGraphInvariants over RAW stored records — broken endpoints, orphaned projections (placements + group members), permission leakage, cyclic derivation exactly where the relation spec says acyclic (mutual influence legal, derived-principle cycles flagged), invalid relation kinds, endpoint-constraint violations, and supersession continuity (latest-revision-superseded, unmarked-old-revision, and revision-chain-jump all detected).
  • Test command: npx vitest run src/study (libs/contracts) — 199/199 passed (23 new in graph.spec.ts); tsc clean; adversarial stub scan clean on all three touched files.
  • Commit: resolve via git log --grep='YSD-2152'.

2026-07-18 — YSD-1004 (libs/yemaya/study-workspace: client-neutral core)#

  • Scope: the workspace core library the proposal's platform plane composes: use cases, orchestration policies, normalized read models, and dependency interfaces. No HTTP/SQL/filesystem — the service composes these over real adapters; Section 3 persistence and Section 4's rights resolver implement the ports.
  • Artifacts (libs/yemaya/study-workspace/):
    • Registered Nx project yemaya-study-workspace with tags scope:yemaya + plane:experience (the architecture checker now discovers and validates it: "2 experience-plane project(s)"), path alias @yemaya/study-workspace in tsconfig.base.json, workspace dep on @oshun/contracts, lint/test/typecheck targets.
    • ports.ts — Clock, IdGenerator, StudyWorkspaceStore (get returns current revision or null, never a default), AuditSink (allows AND denials are audit events), typed UnknownRecordError.
    • policies/rights-gate.ts — deny-by-default evaluation over RightsGrant records: subject scoping (work-wide covers editions; edition/asset-scoped cover only themselves), supersession/deletion state, revocation, validity window, territory (ISO 3166 or WORLDWIDE), explicit action membership. Denials carry the specific per-grant reasons; the empty-reason denial IS the deny-by-default floor.
    • policies/review-gate.ts — applies a ReviewEvent to a stored record: subject-id match, revision pin match, from-state match, then the contract's legal-transition + acceptance-authority rules; produces the next revision via envelope superseding() — history append-only.
    • Use cases: createStudyProject (creator becomes owner; membership event trail starts at creation), authorizePlayback (rights-gated; both outcomes audited with reasons), recordObservation (referential integrity: anchors must exist and be active; epistemic wall enforced by contract), reviewRecord (one path for observations and claims; persists next revision + appends the immutable event), planAnchorMigration/confirmPlannedMigration (honest planning: off-revision anchors listed, never guessed; human-review confirmation via the YSD-2133..2135 contract logic).
    • Read models: buildProjectOverview (counts over current revisions, open reviews = suggested+contested, rights buckets active/expiring- within-30-days/expired/revoked vs a caller-supplied instant), buildAnchorContext (observations + supporting/counterevidence claims joined per anchor; per-ELEMENT visibility mirroring YSD-2157 — a hidden anchor yields null, not an empty shell).
  • Test command: npx vitest run (libs/yemaya/study-workspace) — 15/15 passed against in-memory ports; tsc clean (lib + spec); eslint clean; architecture checker green; adversarial stub scan clean.
  • Commit: resolve via git log --grep='YSD-1004'.

2026-07-18 — YSD-1016, YSD-1017 (fixture factories verified; adversarial stub scan gated)#

  • YSD-1016 verified complete on existing artifactslibs/contracts/src/study/fixtures.ts (read in full this session): deterministic, schema-validated factories with zero randomness/clock and no restricted media (object keys and text stand in for bytes), covering every family the item names: sources (SourceWork), editions (SourceEdition), rights grants (RightsGrant + buildRightsExpiredGrant), tracks (MediaTrack), anchors (EvidenceAnchor + GameSessionAnchor), epistemic objects (Observation/Detection/InterpretationClaim + buildContestedClaim), graphs (CrossReference + SavedGraphView), attempts (PracticeAttempt), sessions (GameSessionAnchor + SessionSyncContract), and deletion cases (buildSoftDeletedFixture, plus buildSupersededFixture/buildRejectedFixture/buildPriorVersionFixture). Case builders exercised by fixtures-golden.spec.ts (5 tests) and the 233 golden cases.
  • YSD-1017 implementedtools/yemaya-study/check-stub-scan.mjs: study-workspace-scoped adversarial scanner over the service, every libs/yemaya/study-* lib, every domain study adapter, and the study contracts. Six checks matching the item: placeholder-success comments, hard-coded fixture payloads in production paths (fixture builders outside contracts' own fixture/example modules), empty function/adapter bodies, ignored authorization (rights decision discarded at statement level), unimplemented destructive operations (empty delete/remove/ erase/purge/expire bodies), and silent catches (empty, or fabricating success without rethrow/log — real brace-matched catch-body analysis). Suppression uses the repo-wide end-of-line stub:legitimate <reason> convention; fail-loud seams are documented non-findings. Gate proven to fail: a planted probe file tripped all six categories (7 findings, exit 1) and the scan returned green after removal — not a self-asserting gate. Wired into the CI lint job after check-architecture.
  • Commit: resolve via git log --grep='YSD-1017'.

2026-07-19 — YSD-1012, YSD-1014, YSD-1018 (env schemas complete; flags/kill switches; development docs)#

  • YSD-1014libs/yemaya/study-workspace/src/policies/feature-flags.ts: flags and kill switches across all eight governed axes — source class (the twelve Section 5.1 acquisition surfaces), analyzer (the six Section 5.4 signal families), provider (OPEN-keyed and default-DENY: a provider works only when STUDY_FLAGS_ENABLE=provider:<id> names it), export type (Section 15 formats), game connector, graph suggestion mode, live session, and transfer destination (TargetProjectRef kinds). Precedence: PERMANENTLY DISABLED (the three prohibited surveillance analyzers mirrored from the architecture checker, plus graph-suggestion:identity per YSD-2155) > KILL (reported distinctly from a flag disable) > DISABLE > default. Unknown axes/keys deny loudly; enabling a permanently disabled key is a STARTUP ERROR naming the policy, not a silent ignore. 8 new lib tests.
  • YSD-1012 — completed: the service env schema (apps/yemaya/svc-study-workspace/src/config.ts) already failed fast on missing production values and pretty-logs-in-production; it now also parses the three STUDY_FLAGS_* vars through the lib at startup, so unknown flag axes/keys, enable+kill/enable+disable on one key, and permanently-disabled enables all abort boot with the full issue list (2 new service tests; 17/17 pass). Client half verified honestly: the studio study route reads NO env settings today (grep-verified — no process.env/NEXT_PUBLIC in the study page/component), so every setting that exists is schema-validated; new client settings must extend this pattern when Section 7 lands.
  • YSD-1018docs/proposals/yemaya-study-workspace/DEVELOPMENT.md: local setup (compose + pnpm + serve, flag env syntax), seed loading via the deterministic source-safe fixture factories (no restricted media by contract), targeted verification (per-project vitest/tsc + the seven CI checkers + drift checks), troubleshooting (worktree Nx bypass, SKIP_TYPECHECK escape, drift regeneration, stub-scan suppression, boot-refusal semantics), and cleanup (compose down, PID/port hygiene). Every command exists today.
  • Commit: resolve via git log --grep='YSD-1014'.

2026-07-19 — YSD-1008, YSD-1015 (registration verified; supported baselines defined)#

  • YSD-1008 verified complete on existing artifacts — all eight named components exist and are enforced for the three study projects (libs/contracts study slice, apps/yemaya/svc-study-workspace, libs/yemaya/study-workspace): project.json registrations with Nx lint/test/typecheck (+build/serve for the service) targets; package manifests with workspace/catalog deps; per-project tsconfig trees; path aliases (@yemaya/study-workspace, @oshun/contracts/study package export); tags scope:yemaya + plane:experience (validated by check-architecture.mjs); ownership metadata (.github/CODEOWNERS lines 103–104: libs/yemaya + apps/yemaya → @GreyChimp; domains.json yemaya entry); dependency constraints (eslint.config.js plane:experience onlyDependOnLibsWithTags + type:study-adapter no-cross-adapter rules, read in place this session).
  • YSD-1015docs/proposals/yemaya-study-workspace/supported-baselines.md: tiered desktop/OS, browser (grounded in the existing Playwright device matrix: Desktop Chrome + Pixel 7 primary, Firefox/WebKit secondary smoke), hardware (4-core/8GB no-GPU CI profile; GPU work stays a fail-loud RunPod seam — the sandbox has no GPU), media-decoder (H.264/VP9 primary, HEVC capability-detected; lossy re-encodes of watermarked evidence prohibited in fixtures), assistive-technology (keyboard-only + ARIA snapshots as primary CI gates per YSD-1037, VoiceOver manual at release, zoom/reduced-motion/ forced-colors rules), locale (en primary, RTL pseudo-locale smoke, ICU-only formatting), and network (throttled + offline-transition profiles wired to the resumable-upload and offline-queue test items). Explicit rule: unlisted environments are UNSUPPORTED-UNTESTED and support claims for them are forbidden until they join the matrix.
  • Commit: resolve via git log --grep='YSD-1015'.

2026-07-19 — YSD-1007 (Aja study adapter: honest wrap, no fabrication laundering)#

  • Scope: libs/aja/study-adapter (tags scope:aja + type:study-adapter; the architecture checker now discovers and validates it: "1 study adapter(s) scanned"; the stub scan now covers it as a fourth root). YSD-1006 (adapter packages across owners, plural) stays open — this is the first adapter and the pattern.
  • The point of this adapter: apps/aja/svc-reference-video's FFprobe metadata, scene detection, and remote ingest paths are SIMULATED (verified by reading the source; the capability table cites file:line for each), and its default in-memory search path fabricates quality scores. Per YSD-1007 the adapter does not fork the source and does not fabricate product-ready behavior over those paths: simulated capabilities return typed refusals with status unavailable-simulated-backing, the reason, and the source evidence — they never invoke the backing code.
  • Artifacts: capabilities.ts (evidence-backed truth table + unsupported() typed-refusal builder), adapter.ts (createAjaStudyAdapter: capability discovery reflecting runtime injection state; searchReferenceClips is dependency-inverted — the composing application injects a ReferenceClipSearchBacking wired to real Qdrant/Meilisearch indexes, hits pass through with domain-native videoIds untouched and matchKind surfaced; absent injection the adapter REFUSES with requires-backing-config rather than falling back to the fabricating in-memory path). Path alias @aja/study-adapter.
  • Deliberately not marked: YSD-0163 (conformance round-trips — needs anchors flowing through adapters), YSD-0164 (cross-domain architecture tests), YSD-1006 (adapters across all accountable owners).
  • Test command: npx vitest run (libs/aja/study-adapter) — 5/5 passed; tsc clean (lib + spec); architecture checker + stub scan green with the adapter included.
  • Commit: resolve via git log --grep='YSD-1007'.

2026-07-19 — YSD-1011, YSD-1013 (dev composition verified; secrets separation)#

  • YSD-1011 verified complete on existing artifacts — read docker/docker-compose.dev.yml in place: every named component exists with safe test defaults — PostgreSQL (pgvector/pg16 with the yemaya database in POSTGRES_MULTIPLE_DATABASES, plus pgbouncer), object storage (MinIO + bucket bootstrap), jobs/events (Redis core + Kafka/Zookeeper under the streaming profile), search projection (Elasticsearch under search), vector projection (Qdrant under vectors), graph projection (Neo4j 5 under graph), observability (Prometheus/Grafana/Jaeger under observability), plus Mailpit and admin tools. Credentials are worthless dev-only values (oshun/oshun_dev, minioadmin) bound locally. Docker 29.5.3 confirmed runnable on-box (Section 3 integration tests can use it). DEVELOPMENT.md already documents usage.
  • YSD-1013docs/proposals/yemaya-study-workspace/secrets-separation.md: the six categories (operational secrets, source credentials, signing keys, provider tokens, model credentials, user-export data) mapped to distinct stores with distinct grants (Secrets Manager path prefixes per category, KMS-held signing keys that sign rather than being fetched, a dedicated exports namespace whose encryption key is not an operational key), the load-bearing naming convention, and the worthless-dev-defaults rule. Code enforcement: redactedConfigSummary() in the service config — an explicit allowlist logged at startup where DATABASE_URL reduces to host:port/database; test proves credentials (user and password) never serialize into the summary and flags DO appear (operators see policy, not secrets). Service suite 18/18.
  • Commit: resolve via git log --grep='YSD-1013'.

2026-07-19 — YSD-3001..3005, YSD-3010 (PostgreSQL authoritative model, verified against a real instance)#

  • Migration apps/yemaya/svc-study-workspace/migrations/0001_study_authoritative_model.sql:
    • YSD-3001 — 21 revisioned record tables in a new study schema: all twenty named families (project, question, source work, edition, asset, rights, track, segment, anchor, entity, epistemic [observation+detection], comparison, inspiration, graph [cross_reference], learning [practice_attempt], transfer, analysis, review [review_event], outcome, audit [audit_event]) plus edition_alignment_map. Uniform envelope column block mirroring StudyRecordEnvelope + contract-validated payload + extracted hot-path columns (anchor edition/hash, grant subject, cross-reference endpoints both directions, alignment source/target).
    • YSD-3002native_id_map (source_system, native_id, family → record_id): native identifiers mapped, never replaced; store methods
      • integration test.
    • YSD-3003 — tenant_id NOT NULL + (tenant, project) index on every family; RLS enabled with a tenant-isolation policy per table (row-level authorization support; app roles get isolation, owner bypass documented); immutable revision keys (PK (id, revision) + immutability trigger allowing ONLY the supersession marker and legal deletion-state transitions; DELETE forbidden); soft-deleted / legal-hold / tombstoned states with a legal transition graph (legal-hold is reversible and blocks tombstoning — tested); deletion_tombstone table.
    • YSD-3004 — envelope CHECKs (revision chain, supersedes = revision-1, superseded-by = revision+1); supersession uniqueness (partial unique index: one current revision per id); legal review transitions + model-acceptance ban as a trigger on append-only review_event; source-version identity (anchor edition_id + sha256-checked technical_revision_hash NOT NULL, plus a referential trigger refusing anchors onto editions with no current row).
    • YSD-3005 — optimistic concurrency: superseding revision N marks the old row in the same transaction as inserting N+1 with WHERE superseded_by_revision IS NULL; losing the race raises ConcurrentEditError; accepted history is append-only by trigger.
  • Runner src/persistence/migrate.ts — per-file transactions, sha256 checksums, idempotent re-runs, refuses edited-after-apply (MigrationDriftError); rollback guidance in the migration header (additive schema; DROP SCHEMA study CASCADE backs it out).
  • Store src/persistence/postgres-store.ts — implements the lib's StudyWorkspaceStore port; every read parses through the contract schema (drifted rows fail loudly); PostgresAuditSink appends to the append-only audit table.
  • YSD-3010postgres-store.integration.spec.ts ran against the REAL dev-compose PostgreSQL 16.14 (started this session): 13/13 passed covering every named path — success (project/anchor/ observation round-trips), duplicate (PK violation), conflict + concurrent edit (second supersession from one revision → ConcurrentEditError, first edit wins), rollback (corrupted second write aborts the marker update atomically), rights change (grant supersession visible; exactly one current revision) and expiry (evaluateRights denies on the stored expired revision), deletion (tombstone flow removes from reads, leaves tombstone row) — plus trigger proofs (immutability, no-delete, illegal review transition, model acceptance, append-only audit, anchor→edition referential) and runner idempotence/drift. When the database is unreachable the suite SKIPS with the compose command printed — never a fake pass.
  • Deliberately not marked: YSD-3006 (repositories for EVERY aggregate — the port's aggregates are covered; question/segment/ entity/comparison/learning/transfer repositories come with their feature sections), YSD-3007 (pagination/bulk), YSD-3008 (rollback drills/seed/compat/zero-downtime verification), YSD-3009 (explain-plan tests).
  • Test command: npx vitest run (svc-study-workspace) — 31/31 (18 unit + 13 integration); tsc clean; stub scan clean.
  • Commit: resolve via git log --grep='YSD-3001'.

2026-07-19 — YSD-1006 (domain adapter packages under accountable owners)#

  • Nine new adapter packageslibs/{nisaba,sophia,hathor,euterpe,aglaea,bellona,metis,isis,neith}/study-adapter — completing the YSD-0150 convention alongside the existing libs/aja/study-adapter (YSD-1007). Oshun and Iris are deliberately NOT adapters (YSD-0161/0162: consumed through platform libs). Each package: tags [scope:<owner>, type:lib, type:study-adapter, layer:domain] (the existing plane:experience/type:study-adapter eslint depConstraints from YSD-0121 bind them; adapters cannot import each other), CODEOWNERS covered by the per-domain libs/<owner>/ globs, path alias in tsconfig.base.json, workspace deps on the owner's real libraries.
  • Shared seamlibs/contracts/src/study/adapter.ts (new): the StudyDomainAdapter interface (domain, contractVersion, capabilities(), nativeRef()), capabilityDescriptor() (validated <domain>.<capability> keys against the YSD-2007 CapabilityDescriptorSchema), unsupportedResponse()/unavailableResponse() constructors, buildNativeRef() (verbatim native-identity wrapper over DomainNativeObjectRefSchema with an explicit no-mutation recheck), and StudyCapabilityTableSchema (unique-key truth-table validation).
  • Every capability status is grounded in code read this session with file:line evidence embedded in the descriptor detail. Wired = genuinely real domain logic; refusals are honest fail-loud seams:
    • nisaba: wired standoff annotation authoring (selector-union, standoff-annotations.ts:158/:234/:248), study-plan generation (study-plans.ts:211), passage comparison (TF-IDF cosine :2479, motif Jaccard :2492). Refusals: persistent-notebooks unavailable (Prisma models exist, apps/nisaba does not), media-study-selectors unsupported (YSD-0113: text-shaped selectors only). Deliberately did NOT wire buildNisabaCrossDomainBridge (tara/arete-specific, wrong fit).
    • sophia: wired citation keys (provenance.ts:57) and dependency-inverted transcript anchoring (VideoTranscriptAdapter needs an injected VideoTranscriber :52 — capability upgrades to supported only when injected; refuses otherwise). Refusals: semantic-retrieval, evidence-persistence unavailable; creative-quality-ranking unsupported (must-not).
    • hathor: wired pacing analysis (narrative/manager.ts:491) and editing rhythm (cinematography/manager.ts:946) — both genuinely computed. HONEST FINDING: DefaultColorTheory.generatePalette returns PRESET palettes not computed from the base color (manager.ts:599-660) → color-palette-theory unavailable rather than dressed up as supported; mda-lens-outputs unavailable (in-memory design CRUD, not anchor-bound lens outputs).
    • euterpe: wired pitch-class-set analysis (set-theory.ts:242/:302/:688), loudness (mix-analysis.ts:89/:102/:807 meterLoudness K-weighting), voice pitch (voice-analysis.ts:568). Refusals: hosted-analysis-service unavailable (no service exists — apps/euterpe has only studio-web); speaker-identification unsupported (YSD-4052 policy).
    • aglaea: wired fabric knowledge + comfort scoring (performance-database.ts:16, comfort-scorer.ts:220), era analysis (decade-database.ts:15, era-analyzer.ts:860/:1024), regional dress profiles (regional-codes.ts:22). Refusals: fabric-id-from-image unavailable (classifier takes structured text, not pixels); body-shape-inference unsupported (must-not).
    • bellona: wired replay-bundle integrity (replay-bundle.ts:292 sha256 binding manifest+header) and strict session-manifest validation (session.ts:312; invalid input → typed rejection with zod issues, proven by the creator-not-a-participant test). Refusals: evidence-signing (signers real, no key), live-engine-bridge (retryable — needs a connected engine), gameplay-state-capture (MemorySaveStorage only, save/index.ts:85).
    • metis: wired FSRS-5 scheduling (fsrs.ts:124; test proves interval==stability at r=0.9 and S0/D0 against the published weights) and BKT mastery (knowledge-tracker.ts:114; test asserts the hand-derived 0.1→0.4→0.775 posterior chain → 'proficient'). Refusals: concept-similarity (needs GraphStore+EmbeddingProvider), gradebook-emission (needs LMS creds), curriculum-service (FastAPI deploy); taste-originality-scoring unsupported (YSD-2047).
    • isis: wired job-envelope minting (job-envelope.ts:731, idempotency key = jobId) and pure region-aware routing (router.ts:47 planIsisRoute — "Pure data + pure functions. No IO."; test proves tenant-pinned region wins and unhealthy endpoints are skipped). Added the additive ./provider-endpoints export subpath to @isis/ai-providers so the adapter avoids loading provider SDK modules. Refusals: inference-execution, output-provenance-registry unavailable; evidence-system-of-record unsupported (YSD-0160 must-not).
    • neith: ALL inspection refuses — the Rust is real (GLB importers.rs:410, OBJ :818, USDA :548, ASCII-FBX :592; scene graph node.rs:147) but no napi-rs/WASM bindings exist (grep empty; package.json wraps cargo only), so the adapter refuses rather than reimplementing geometry in Yemaya (YSD-0165) or faking results. binary-fbx-import is unsupported (also unimplemented in Rust, importers.rs:597); the tests prove the convert-your-file vs wait-for-bindings distinction.
  • Verification: per-package tsc --noEmit (lib + spec configs) clean ×9; vitest 49/49 across the nine packages (6+6+5+6+7+5+5+5+4); contracts study suite 199/199; yemaya study-workspace 23/23; adversarial stub scan zero hits; eslint (incl. module-boundary depConstraints) clean.
  • Note for YSD-0163: the aja adapter still publishes its bespoke descriptor shape from YSD-1007; harmonizing it onto the shared CapabilityDescriptorSchema is planned as part of the conformance kit.
  • Commit: resolve via git log --grep='YSD-1006'.

2026-07-19 — YSD-0163 (adapter conformance: native IDs and anchors survive round trips)#

  • Kit libs/contracts/src/study/adapter-conformance.ts (exported from the study namespace):
    • NATIVE_ID_BATTERY — 12 adversarial native ids (uuid, cuid, colon-scoped, path-like, dotted, mixed-case, leading zeros, inner/trailing whitespace, unicode incl. CJK+emoji, single char, 512-char max) chosen so trimming, lowercasing, numeric coercion, or re-minting each break at least one entry.
    • nativeRefRoundTripFindings(adapter) — for every battery id: nativeRef preserves id/version byte-for-byte, pins the adapter's domain, keeps kind verbatim, survives JSON round trip re-parsed through the strict DomainNativeObjectRefSchema, and version-less refs must not fabricate a version.
    • studyAdapterConformanceFindings(adapter, expectedDomain) — identity + semver + YSD-2007 table validity + domain-prefixed keys + version agreement
      • discovery stability across calls + the native-ref battery.
    • anchorLocatorRoundTripFindings(label, locator) — SourceLocator serialize/revive must hold under the YSD-2083 canonical equality (sourceLocatorsEqual), not merely structurally.
    • losslessJsonRoundTripFindings — structural equality where a present- but-undefined key is significant (JSON dropping a field IS lossy; the naive stringify-compare version could not detect this and was replaced).
  • Kit self-test adapter-conformance.spec.ts — proves the kit CATCHES violations, not just passes conformant code: a deliberately lossy adapter (trim+lowercase) is flagged on the mixed-case/trailing-space entries; a wrong-domain adapter and an unstable capability table are flagged; all six SourceLocator kinds round-trip under canonical equality; undefined-drop is flagged.
  • Aja harmonized onto the shared surface (contract 1.0.0 → 1.1.0, additive): now implements StudyDomainAdapter — domain, contractVersion, nativeRef, and capabilities() mapping the bespoke evidence-rich table onto the shared YSD-2007 shape (available→supported, else unavailable; evidence folded into detail — a test proves it is never dropped). The original describeCapabilities() API is unchanged; added @oshun/contracts dependency.
  • Ten per-package conformance specs (src/conformance.spec.ts in every study-adapter): each runs the kit (zero findings) plus a domain round trip through REAL adapter methods: aja search hits keep videoId verbatim incl. trailing space/leading zeros/case; nisaba annotation ids, creator ids, and caller-chosen plan/objective ids untouched; sophia anchor ids embed the native video id verbatim and chunk provenance keeps source/revision ids; hathor rhythm results echo native shot ids; bellona session/participant ids survive strict validation and hashing does not mutate its input; metis BKT keys concepts by the exact caller string (near-identical ids stay independent: 0.775 vs 0.1); isis preserves jobId + idempotencyKey; euterpe analysis is deterministic and JSON-lossless; neith (all-refusals) still passes the full identity battery.
  • Verification: all ten adapter suites green — 76 tests total (8+9+8+7+8+ 9+8+7+7+5); contracts study suite 205/205; per-package tsc (lib+spec) clean; stub scan zero hits; eslint zero errors.
  • Commit: resolve via git log --grep='YSD-0163'.

2026-07-19 — YSD-0164 (architecture tests: cross-domain DB, copied taxonomies, model calls, store bypasses)#

  • Extended tools/yemaya-study/check-architecture.mjs (the YSD-0121 checker) from four rules to seven, refactored around an exported pure rule engine checkSource(rel, text, context) so the rules are unit-testable rather than self-asserting:
    • Rule 5 (unapproved model calls, YSD-0160): flags (a) imports of any model-provider SDK (openai, @anthropic-ai/sdk, @google/generative-ai, ollama, elevenlabs, mistral, cohere, huggingface) anywhere in study code — including the isis adapter, which plans routes but never holds provider clients; (b) imports of @isis/ai-providers root or /providers* client modules; the pure /provider-endpoints routing module is permitted ONLY inside libs/isis/study-adapter; (c) any study source naming a provider API host (api.anthropic.com etc.), tests included.
    • Rule 6 (copied taxonomies, YSD-0014): flags declarations of twelve domain-owned taxonomy constants (FORTE_CATALOG, DECADE_PROFILES, ELEMENT_HISTORY, FABRIC_PERFORMANCE_DATABASE, REGIONAL_PROFILES, FSRS5_DEFAULT_WEIGHTS, DEFAULT_BKT_PARAMS, MASTERY_THRESHOLDS, ISIS_GENERATION_TYPES, ISIS_PROVIDER_KINDS, REMOTE_REPLAY_BUNDLE_COMPONENTS, AJA_STUDY_CAPABILITY_IDS) in any study root; imports remain legal, and aja's own capability list is allowed in aja's adapter.
    • Rule 7 (canonical-store SQL bypasses): scanning now includes .sql files; flags SQL-shaped references (FROM|JOIN|INTO|UPDATE|TABLE <domain>.) to any registered domain schema outside {study, public, pg_catalog, information_schema}, and SET search_path to any non-allowed schema. Precision guard: capability keys like 'sophia.semantic-retrieval' never follow an SQL keyword and do not trip.
    • Rules 1–4 (adapter-only cross-domain imports, cross-domain store packages, prohibited-inference analyzers, required tags) unchanged.
  • New rule unit tests tools/yemaya-study/check-architecture.test.mjs (node --test, 13 tests): one planted violation per rule proving it FIRES, plus the allowances (own-store adapter import, isis-only routing import, taxonomy import vs declaration, study-schema SQL, capability-key precision). Wired into ci.yml beside the checker.
  • End-to-end negative test: planted a file importing openai, declaring DECADE_PROFILES, and selecting from sophia.evidence_pack inside libs/yemaya/study-workspace → checker exited 1 with exactly 3 problems naming each rule; removal returned green (2 experience-plane project(s), 10 study adapter(s) scanned).
  • Test commands: node --test tools/yemaya-study/check-architecture.test.mjs (13/13), node tools/yemaya-study/check-architecture.mjs (pass, 12 projects).
  • Commit: resolve via git log --grep='YSD-0164'.

2026-07-19 — YSD-1009 (schema, API-client, event-contract generation + compat checks in CI)#

  • Schema generation (pre-existing YSD-2008 machinery) extended: now also emits schemas/study/events.manifest.json (event kind → emitter + delivery channel, from the STUDY_EVENT_DELIVERY table). 67 artifacts published.
  • Event contractslibs/contracts/src/study/events.ts: StudyWorkspaceEventSchema mirrors the IMPLEMENTED event reality exactly — the eight AuditSink kinds from ports.ts (project-created, observation/claim-recorded, playback-authorized/denied, record-reviewed, anchor-migration-planned/confirmed) with the columns the append-only study.audit_event table persists (kind, subjectId, actorUserId, detail, tenantId, occurredAt). Registered in STUDY_SCHEMA_REGISTRY with canonical + negative + zod-only examples, so it flows through publication, ajv double-validation, golden fixtures, and baseline breaking-change detection automatically. Deliberately no invented richer payloads: kinds arrive with the features that emit them.
  • API client generationlibs/contracts/scripts/generate-study-client.ts renders src/study/client.generated.ts from a new route manifest (service-routes.ts) that mirrors the routes the service ACTUALLY serves (health/ready/metrics with exact response shapes read from app.ts and readiness.ts; business routes join with YSD-1030 — listing them earlier would fabricate an API). The generated client is dependency-injected (fetch), validates every response through the manifest schemas, and returns typed contract-violation results instead of passing drifted bodies through. Health/Ready response schemas are registry-published too.
  • Loop closed with a real E2E: svc-study-workspace app.spec drives the GENERATED client against the real Hono app (client.health/ready/metrics via app.request) — proving manifest ≙ client ≙ service — plus a drifted-body test asserting contract-violation instead of pass-through. Service suite 33/33 (integration suite still green against real postgres).
  • Compatibility checks: STUDY_CONTRACTS_VERSION 0.2.0 → 0.3.0 (additive = minor per the YSD-2010 policy), '0.3.0' added to SUPPORTED_STUDY_SCHEMA_VERSIONS; schema check reports "65 contracts published, no drift, no breaking changes vs baseline v0"; goldens regenerated (239 cases stable).
  • CI wiring: generate-study-client.ts --check added beside the existing schema/golden checks in the ci.yml study block (which the YSD-0164 commit also gave node --test check-architecture.test.mjs).
  • Verification: contracts study suite 205/205; contracts + service tsc clean; svc suite 33/33; study-workspace 23/23; adapter spot-suite green; architecture + stub-scan + traceability checkers pass.
  • Commit: resolve via git log --grep='YSD-1009'.

2026-07-19 — YSD-1010 (targeted CI, PARTIAL — deliberately left unchecked)#

  • New workflow .github/workflows/study-workspace.yml — path-filtered to study code (contracts/study, schemas/study, study generators, experience plane, service, all libs/*/study-adapter, tools/yemaya-study, studio/study web route). Four jobs, one per suite family that exists:
    • contracts — study contract suite + contracts tsc + the three generator --check gates (schemas/breaking-change, goldens, client drift).
    • service-migrations — real postgres:16 service container; service tsc, then the full service suite with STUDY_PG_REQUIRED=1 so the migration + authoritative-store integration suite MUST run (spec change: unreachable DB is now a hard failure when required — verified both ways locally: 13/13 against real postgres, and an explicit failure with a bogus URL, never a silent skip). Plus the experience-plane lib suite + tsc.
    • adapter-conformance — fail-fast:false matrix over all ten adapters: lib+spec typecheck and full suites incl. the YSD-0163 conformance specs.
    • architecture — YSD-0121/0164 checker, its planted-violation node --test rules, stub scan, traceability.
  • Deliberately NOT marked complete: the item also names deletion, accessibility, Playwright, security, evaluation, and performance suites — those suites do not exist yet (they arrive with YSD-1035..1039 and later sections), and a workflow step for a nonexistent suite would be a self-passing gate. The workflow header documents this; the item is marked only when every named suite family has a real job.
  • Verification: YAML parses (4 jobs); STUDY_PG_REQUIRED gate verified in both directions locally.
  • Commit: resolve via git log --grep='YSD-1010'.

2026-07-19 — YSD-1030..1039 walking skeleton, PROGRESS (items deliberately still unchecked)#

  • Stage A (library): register-local-source, grant-rights, ingest-local-source, create-anchor, export-study-trail, delete-source use cases + activeGrantFor acquisition gate; extended StudyWorkspaceStore port (works/editions/assets/tracks/project lists); shared in-memory test harness (src/testing/in-memory-deps.ts, tests-only per YSD-1031); event kinds extended to 16 (contract + manifest regenerated). 32 lib tests.
  • Stage B (service): fail-closed HS256 bearer auth (auth.ts — 503 when unconfigured, 401 on bad/expired tokens, constant-time signature check), business routes under /api/study (project/register/grants/ingest/playback/ anchors/observations/trail/export/delete), inbox-sandboxed file resolution (realpath containment, symlink escapes refused), real adapters: streaming sha256 identifier, ffprobe prober (rational r_frame_rate — never floats), MinIO object store over @oshun/storage; postgres store extended to the full port; readiness gains an object-store probe; startup wires business routes ONLY when postgres + S3 + inbox are all real (else honest 503).
  • End-to-end proof walking-skeleton.integration.spec.ts — 9/9 against the REAL dev stack (postgres 16 + MinIO started this session): the film is synthesized in-test with ffmpeg (testsrc2 1280×720@24 + 440 Hz sine — rights-cleared because we own it; the YSD-1038 source), then over HTTP: 401s without/with-garbage tokens → project → register → ingest DENIED before the rights decision (403 + reasons) → grant (creator-owned, playback+export) → ingest (ffprobe facts verified: 24/1 rational fps, 120 frames, 16:9, sha256 identity, video+audio tracks) → playback presigned URL → real bytes fetched from MinIO → manual anchor (frame 24–72) + observation with no analyzer configured anywhere (YSD-1033/1039) → trail intact through a fresh app instance over the same database (YSD-1034) → export document downloaded and verified → delete: objectKey really gone (presigned URL now 404), 7 record families tombstoned, playback 403, trail empty (YSD-1035 across every surface that exists today).
  • Remaining before ANY of YSD-1030..1039 is marked: notebook step (Nisaba-owned persistence through the nisaba study-adapter), the /studio/study web UI consuming these routes, Playwright deep + a11y coverage (YSD-1036/1037), manual hierarchy correction surface (YSD-1033), and extending the YSD-1009 route manifest/generated client with the business routes.
  • Test commands: lib npx vitest run 32/32; svc npx vitest run 42/42 (20 app + 13 store-integration + 9 walking-skeleton E2E); tsc clean both; architecture + stub scans pass.
  • Commit: resolve via git log --grep='ysd-1030'.

2026-07-19 — Walking skeleton UI + Playwright; YSD-1031/1034/1038/1039 marked#

  • UI apps/oshun/web/src/components/studio/StudyWorkspaceApp.tsx on /studio/study (launch surface updated honestly: study-projects and playback-annotation now available; four lens surfaces still explicitly "Not yet available"). Every rights denial renders its policy reasons (data-study-denial-reasons). A data-study-ready attribute appears only after hydration + first load so E2E fills never race hydration.
  • Proxy apps/oshun/web/src/app/api/study/[...path]/route.ts: verifies the REAL Oshun session (the /v1/auth/refresh exchange the session endpoint itself uses), mints a 300s HS256 study token, forwards to the service. Fail-closed 503 when STUDY_SERVICE_URL/STUDY_JWT_SECRET absent; 401 when the session is missing/invalid. Env reads via @oshun/config (ratchet).
  • Shared token moved to @oshun/contracts/study/token, rewritten on @noble/hashes after the repo lint rule flagged node:crypto (webpack noops it in client bundles — silent HMAC corruption); svc auth.ts delegates to it. Study contracts namespace converted to extensionless relative imports (repo norm) because Turbopack cannot resolve .js.ts inside the transpiled package; added slim @oshun/contracts/study/client subpath so the browser bundle carries only the generated client + schemas.
  • Playwright e2e/studio-study-walking-skeleton.spec.ts2/2 against the REAL five-layer stack (Chromium → Next web → real BFF auth via seedCustomerSession → /api/study proxy → svc-study-workspace → postgres + MinIO + nisaba DB, film ffmpeg-synthesized by scripts/e2e-bootstrap.ts): honest launch surface, axe scan clean (serious/critical = 0) before AND after the journey; create → register → ingest denied pre-grant with reasons visible → grant → ingest → real
  • Marked [x]: YSD-1031 (pg + MinIO + nisaba stores; in-memory only under src/testing/; business routes wire ONLY when all backings real, else honest 503), YSD-1034 (svc E2E: trail incl. notebook re-read by a FRESH app instance over the same databases; lib suite: records persist through grant expiry while new actions deny), YSD-1038 (real rights-cleared ffmpeg-owned film; sanitized evidence = these log entries + specs), YSD-1039 (whole journey with zero models configured; Playwright asserts the explicit "Not yet available" analyzer states end to end).
  • Deliberately NOT marked: YSD-1030/YSD-19020 (19020 bundles "player/hierarchy" — hierarchy navigation/segments not built; the two are one deliverable, so both wait for the minimal manual hierarchy), YSD-1032 (search/sharing/transfer surfaces don't exist yet to gate), YSD-1033 (manual hierarchy correction needs the hierarchy), YSD-1035 (search/vector/graph/cache/notebook-preview surfaces pending), YSD-1036 (journey misses compare + typed link), YSD-1037 (keyboard + axe critical assertions shipped; screen-reader-critical assertions pending).
  • Test commands: Playwright 2/2 (~59s, workers=1); svc suite 44/44 incl. restart-notebook assertion; lib 32/32; contracts study 205/205; page unit 4/4; architecture/stub scans pass.
  • Commit: resolve via git log --grep='playwright proof'.

2026-07-19 — Manual hierarchy; YSD-1030 + YSD-19020 + YSD-1033 marked#

  • Segmentsuse-cases/manage-segments.ts: createSegment / correctSegment with REAL structural validation (frame bounds within the edition's probed frame count; parents on the same edition; frame-range parents must CONTAIN children — both refusals unit-proven), pinned technicalRevisionHash, human-authored confidence explicitly {missing, not-applicable} (human judgment is not model confidence), corrections as append-only supersessions bumping segmentationVersion (manual-N). Store port + postgres impl (study_segment), events segment-created/segment-corrected (contract + manifest regenerated), service routes (create/list/correct), manifest + generated client, segments in trail + export document, hierarchy panel in the UI with indent-by-parent outline + correction form.
  • Verification: lib 34/34 (containment refusal, beyond-edition refusal, correction history, no-op-correction refusal); svc 44/44; Playwright 2/2 with the extended journey — create scene → shot INSIDE it → manual label correction visible in the outline (corrected ×1) — plus all prior steps and clean axe scans. One box-level fix en route: fs.inotify.max_user_watches 121k→1M + instances 128→1024 (tsx watch ENOSPC killed the BFF boot — shared-box watcher exhaustion).
  • Marked [x]: YSD-1030 + YSD-19020 (every named element of both items is implemented and E2E-verified twice: service-level 11-step suite vs real pg/minio/nisaba + browser-level Playwright vs the full five-layer stack — local-file class, real persistence, rights decision, player + hierarchy, manual annotation, Nisaba notebook flow, export, rights denial with reasons, deletion) and YSD-1033 (hierarchy creation + correction and annotation all available the moment ingest lands technical metadata — no analyzer, no model, no queue anywhere in the flow).
  • Commit: resolve via git log --grep='manual hierarchy'.

2026-07-19 — Rights denial before every skeleton surface; YSD-1032 marked#

  • Searchuse-cases/search-study-records.ts: rights evaluated BEFORE matching (activeGrantFor per work at query time); suppressed works reported in excludedWorks with the denial reasons; segments suppressed with their edition, observations suppressed unless ≥1 cited anchor sits on a permitted edition (export's rule). Deterministic scorer (documented approximation for skeleton scale): AND-term match, phrase 100 + prefix 20 + 10/term, field weights title 5 / label 3 / creators 2 / observation 1 — unit tests assert hand-computed scores (700/360; 650 E2E), so a fabricated scorer fails. Audit search-performed counts outcomes and never carries query text (YSD-0009).
  • Sharinguse-cases/share-project.ts: owner-only, duplicate-refusing, and denied unless EVERY source work has an active collaboration grant (per-work reasons; audit sharing-denied); allowed path appends the membership event + roster on a superseding project revision (audit project-shared).
  • Project transferuse-cases/transfer-project.ts: owner-only; denied unless every work grants project-transfer (audit transfer-denied); rights-passing requests refuse honestly with the four YSD-0020 prerequisite checks (abstraction-enforcement, source-concentration, overly-specific-expression, dependency-impact) as 503 transfer_unavailable (audit transfer-blocked) — transfer has NO success path until Section 18 ships those checks.
  • Wiring: routes GET /projects/:id/search, POST /projects/:id/members, POST /projects/:id/transfer; contract manifest + body/response schemas + regenerated client (searchProject/shareProject/requestTransfer); five new audit kinds in the events contract, schemas regenerated (also caught the stale segment-created/-corrected publication drift). Playback, processing (ingest), and export gates were already live — with this, all six YSD-1032 surfaces deny by default with reasons.
  • Verification: lib 43/43 (rights-surfaces.spec.ts: lapsed-grant search suppression incl. observation-through-anchor suppression, sharing denial/allow with roster+revision asserts, transfer denial + honest unavailability); svc 45/45 incl. the real-stack integration leg (ungranted work suppressed E2E, share 403→grant→201, transfer 403→503 with named checks, audit rows verified by SQL in study.audit_event); contracts 205/205; schema drift check clean, no breaking changes vs baseline v0; architecture + stub + traceability checkers pass.
  • Marked [x]: YSD-1032.
  • Commit: resolve via git log --grep='rights denial'.

2026-07-19 — Expiry + deletion across every skeleton reference class; YSD-1035 marked#

  • Export references — migration 0002_study_export_reference.sql: a ledger (study.export_reference, GIN on work_ids) recording every export document and the works it embeds; exportStudyTrail writes it; user deletion purges the export OBJECTS whose ledger rows name the deleted work and marks the rows (rows survive for audit; duplicate keys and double deletion are hard errors — postgres integration test).
  • Deletion gaps closeddeleteSource now tombstones study_segment rows with their edition (previously orphaned as active rows) and deletes sidecar proxy/thumbnail objects (branch now actually exercised: lib tests attach a schema-valid proxy sidecar and assert its object dies on both the delete and expiry paths).
  • Retention expiryuse-cases/expire-retained-sources.ts: once EVERY grant has ended (validUntil lapse or revocation, superseded/tombstoned grants excluded) and the LONGEST sourceRetentionDays window (counted per grant from its own end) has passed, the sweep deletes retained copies (original + sidecars) and tombstones asset/track rows; the scholarly record (work, edition metadata, segments, anchors, observations, notebooks) survives — the license to HOLD the copy lapsed, not the user's study writing; user deletion remains the remove-everything path. Audited source-expired by system:retention-sweep. Idempotent (no stored copies ⇒ skip). Surfaced as POST /api/study/expiry-sweep (contract manifest + generated client expirySweep) and as a config-gated service timer (STUDY_EXPIRY_SWEEP_INTERVAL_MS, 0=off, cleared on shutdown).
  • Classes without artifacts yet: search is a rights-filtered live scan of the authoritative records, so tombstoning IS its deletion (tested: deleted work vanishes entirely, not merely rights-suppressed); vector and graph projections do not exist in the skeleton (Section 3.3) and gain deletion fan-out with the items that create them (noted in delete-source doc); notebooks store citations by anchorId only — no source content exists to purge, and exports embedding the work are purged as above; presigned-URL cache is covered by bytes-gone verification.
  • Verification: lib 48/48 (deletion-expiry.spec.ts: export purge + ledger marks, segment tombstone + search-empty, retention window honored at T+12h / expired at deadline+1min with exact object keys, live-grant immunity, revocation-based retention, idempotent re-sweep, sidecar death on both paths); svc 47/47 incl. two real-stack legs (export purged on delete with presigned URL 404, and a real 5s-lifetime grant: early sweep refuses, post-lapse sweep deletes bytes (URL 404), trail keeps the work, SQL-verified single source-expired audit row, idempotent re-sweep); postgres store 0002 migration + ledger roundtrip; contracts 205/205, schema+client drift checks clean, no breaking changes; architecture + stub + traceability checkers pass.
  • Marked [x]: YSD-1035.
  • Commit: resolve via git log --grep='expiry and deletion'.

2026-07-19 — Deep browser journey complete; YSD-1036 marked#

  • New workspace surfaces (StudyWorkspaceApp): navigate ("Go to start" on frame-range segments seeks the real player to startFrame/frameRate), compare (two anchor pickers → side-by-side panes rendering each anchor's locator + the observations citing it), typed links (relation picker contrast/similarity/identity → POST /projects/:id/links; list shows relation + endpoints + review state), grant lifetime seconds (records a validUntil + zero-retention grant), and a "Run retention sweep" button (client.expirySweep).
  • Typed-link backenduse-cases/create-typed-link.ts: CrossReference between two anchors with pinned endpoint revisions (YSD-2156), rights gate on BOTH endpoints' works before any graph write, directionality from RELATION_SPECS, identity born suggested never accepted (YSD-2155), audit link-created; store port + postgres/in-memory impls (cross_reference family), links in the trail contract + route, createLink in the manifest + regenerated client; deleteSource tombstones edges whose endpoints die (YSD-2159).
  • Playwright journey (studio-study-walking-skeleton.spec.ts, real five-layer stack: Next + BFF + svc-study-workspace + postgres/minio/ nisaba + ffmpeg film): all fourteen YSD-1036 steps — create project, ingest (denied first, with reasons), rights decision, play (presigned bytes), navigate (player.currentTime → 1.0s after segment goto), annotate (scene/shot + correction), compare (both panes assert their own locators), pin region, create typed link (contrast, accepted), save notebook (+ study card), export, deny action, EXPIRE (new test: 6s license → playback denied with "expired" reason while bytes remain → sweep → presigned URL 404s, scholarly record survives), delete (keyboard submit; anchors AND links empty after). 3/3 passed (3.1m).
  • En-route fixes: the durable BFF (from today's main) refuses boot without OSHUN_SIGNUP_VERIFICATION_HMAC_SECRET and OSHUN_AUTONOMY_SNAPSHOT_KEY_BASE64/REF — added e2e-only defaults in playwright.config; cleared ONE stale signup-verification-state snapshot row from the dev DB written under an unrecoverable key (secretCheck mismatch predates the shared default).
  • Marked [x]: YSD-1036.
  • Commit: resolve via git log --grep='deep playwright'.

2026-07-19 — Keyboard-only + screen-reader journey; YSD-1037 marked#

  • Spece2e/studio-study-a11y.spec.ts, same real five-layer stack as the YSD-1036 journey, two standing rules: NO clicks (every activation is focus + Enter/Space or implicit text-input form submission) and every control addressed by ACCESSIBLE NAME (getByRole/getByLabel — a missing or wrong announced name fails the locator itself, never by test id).
  • Journey covered keyboard-only: create project (with name → purpose → submit TAB-ORDER assertions), register film (implicit Enter submission), denied ingest ANNOUNCED as role=alert carrying the deny-by-default policy text, grant via Space, ingest, playback via Enter with <video controls> asserted (native keyboard-operable player), segment creation, frame-range anchor (label lookup scoped past duplicated label text), observation, Nisaba notebook, export (named link visible), delete (Enter submission, outcome announced, trail emptied).
  • Screen-reader semantics asserted: workspace is a NAMED region (aria-labelledby its heading), project switcher exposes aria-pressed selection state, notices are role=alert (scoped past Next's route announcer, which is also role=alert — strict-mode caught that), axe scan of the fully populated workspace with zero serious/critical violations.
  • Result: 1/1 passed (31s test, 3.0m with stack boot). CI: the study-e2e job now runs BOTH study specs; path filters broadened to e2e/studio-study-*.spec.ts.
  • Marked [x]: YSD-1037.
  • Commit: resolve via git log --grep='keyboard'.

2026-07-19 — Stable source identity on evidence anchors; YSD-2100 marked#

  • Contract (0.4.0) — EvidenceAnchor gains workId + assetId + sessionId; a version-gated superRefine requires, for every record at a version ≥0.4.0: workId always, and EXACTLY the identity matching the locator kind — assetId for track/asset-addressed evidence (must equal the locator's assetId for document/spatial), sessionId for runtime-event evidence (must equal the locator's start sessionId; event ranges may not cross sessions). Pre-0.4.0 stored records are exempt via the frozen PRE_IDENTITY_SCHEMA_VERSIONS set; a legacy canonical example proves they keep parsing. STUDY_CONTRACTS_VERSION 0.3.0 → 0.4.0 (additive per compat-policy rule 5, added this session); schemas/ goldens/client regenerated; NO breaking changes vs baseline v0.
  • Use cases — createAnchor now RESOLVES and VERIFIES the chain before writing: track locators must chain track → asset → the anchor's edition (a foreign edition's track is refused with reasons, unit-proven); document/spatial locators must name an asset of that edition; runtime-event locators pin sessionId from the locator. Anchor migration (confirmPlannedMigration) now resolves TARGET-side identity — shared work + the target edition's single unambiguous counterpart track of the same kind — refusing honestly otherwise, and confirmAnchorMigration stamps the migrated record with the target's work/asset/track and the CURRENT schema version. This also fixed a latent bug: migrated anchors used to keep the SOURCE edition's trackId while pointing at the target edition.
  • Persistence — migration 0003_study_anchor_source_identity.sql: work_id/asset_id/session_id columns + idempotent backfill resolving the stored chain (edition→work, track→asset, locator asset/session) plus a partial index on work_id. Stored payloads are NOT rewritten — revision immutability (YSD-0013) holds; legacy identity lives in columns. FAMILY_EXTRAS writes the columns on every anchor write.
  • Verification: contracts 205/205 (incl. new zod negatives: 0.4.0 anchor missing workId/assetId, asset evidence smuggling a sessionId; migration spec asserts target identity + trackId swap); lib 54/54 (identity resolution on create, cross-edition track refusal, migration counterpart resolution); svc 48/48 vs real PostgreSQL (0003 applied; legacy row backfilled by re-running the migration SQL, payload untouched and still parsing, current-version rows carry columns at write); Playwright journey re-run green; architecture + stub + traceability checkers pass; schema drift + client drift checks clean.
  • Scope note: Detections/Observations/Claims carry identity THROUGH their anchors — YSD-2112's unanchored-rejection invariants (already marked) close that path; sessions gain first-class records with the instrumented-game sections.
  • Marked [x]: YSD-2100.
  • Commit: resolve via git log --grep='stable source identity'.

2026-07-19 — Policy-permitted representation generation; YSD-2102 marked#

  • Policypolicies/representation-policy.ts: permitted representations are DERIVED from the covering grant's actions (thumbnail/crop ⇒ frame-extraction, waveform ⇒ proxy-generation, feature-vector ⇒ embedding, transcript-excerpt ⇒ transcription; metadata-only always; provider-deep-link only for provider-linked assets). createAnchor's hardcoded list is GONE — a playback-only grant now yields metadata-only evidence (unit-proven).
  • Generationuse-cases/generate-representation.ts, doubly gated: (1) the anchor's recorded policy, (2) a LIVE rights re-check of the representation's action at render time (permitted-then-lapsed grants refuse; renderer never invoked — asserted). Real rendering via the new ffmpeg adapter (media/representation-renderer.ts): frame extraction reading the presigned MinIO URL directly, normalized-region crops, total-pixel cap from grant limits (never upscaling), waveform images. Unshipped stages (feature-vector/transcript-excerpt/provider-deep-link) refuse with 501 representation_unavailable — honest seams for the analyzer sections. Output stored as an ASSET SIDECAR on a superseding revision, so YSD-1035 deletion + expiry purge it with the source copies. Audit representation-generated; route POST /anchors/:anchorId/representations; contract body/response + manifest + regenerated client.
  • Verification: lib 57/57 (policy mapping vs hand-listed sets, both gates, sidecar + audit, lapsed-grant refusal with renderer-untouched assert); svc 49/49 incl. the real-stack leg — ffmpeg renders an actual thumbnail from MinIO bytes (JPEG magic asserted on the downloaded object) and waveform is 403 representation_denied under a grant without proxy-generation; contracts 205/205; schema/client drift clean; checkers green. UI generation surface arrives with the Section 7/8 viewing-state items — the policy enforcement is service-side and client-independent.
  • Marked [x]: YSD-2102.
  • Commit: resolve via git log --grep='representation'.

2026-07-19 — Authorship + immutable change reasons + review history; YSD-2106 marked#

  • Authorship (verified in place): AuthorIdentitySchema's four kinds (human, model, imported-system, collaborative-group) carried on every envelope's createdBy and on segments/claims/cross-references/review events; model authorship structurally rejected where humans must decide (observations, identity acceptance, migration confirmation — all previously tested).
  • Immutable change reason (the gap, now closed): the envelope gains changeReason, and superseding() now REQUIRES { reason, schemaVersion? } — the compiler forces every supersession site to say why. Version-gated at 0.5.0 per compat-policy rule 5 (second application): revisions >1 at ≥0.5.0 must carry a reason, revision 1 must NOT (creation is not a change), pre-0.5.0 stored history stays valid. Production writers stamp STUDY_CONTRACTS_VERSION so their new revisions enter the rule; reasons threaded with real content through all nine production sites (share, register-work, ingest, segment corrections [the user's own reason], representation sidecars, notebook authoring, study cards [nisaba adapter], review application [the review's reason], graph endpoint advancement). Reasons are immutable with the rest of the payload (0001 trigger).
  • Review history (verified in place): study.review_event append-only by trigger (mutation + delete refusal integration-tested), legal transitions enforced, applyReviewEvent supersedes with the review's reason now recorded on the envelope too.
  • Verification: contracts 206/206 (new test: current-version supersession carries reason, reasonless one rejected, creation-with-reason rejected, legacy history exempt); yemaya lib 57/57; nisaba adapter 11/11; svc 49/49 vs real PG; Playwright journey green post-change; schema/golden/client drift checks clean, no breaking changes vs baseline v0; checkers green.
  • Marked [x]: YSD-2106.
  • Commit: resolve via git log --grep='change reason'.

2026-07-19 — Seven epistemic object types, visibly distinguished; YSD-2107 marked#

  • Vocabularylibs/contracts/src/study/epistemic-object-types.ts: the seven §9.6 types verbatim (source-fact, detection, observation, interpretation, craft-hypothesis, creator-statement, practice-result) with per-type display metadata (unique label, §9.6 definition, provenance discipline) and the total EPISTEMIC_RECORD_KIND_TO_TYPE mapping over registry kinds (SourceFact/Detection/Observation/InterpretationClaim/ CraftHypothesis/CreatorStatement/OutcomeEvidence). Published in the schema registry as EpistemicObjectType; exported client-safe via the new @oshun/contracts/study/epistemic subpath (pure zod + data, no node:crypto).
  • Two missing types now first-class (contracts 0.5.0 → 0.6.0, additive; SUPPORTED_STUDY_SCHEMA_VERSIONS += 0.6.0):
    • entities/source-fact.ts — SourceFact: subject chain (work → optional edition → asset; asset requires edition, refined), scalar value + unit, and a verification block where MECHANICAL methods (checksum-computation/media-probe/container-metadata) REQUIRE the computing processor and documentary methods (distributor-manifest/credits-text/spec-document) REQUIRE the citation — a value with neither does not parse.
    • entities/creator-statement.ts — CreatorStatement: speaker (name + credited role), verbatim-vs-paraphrase flag, MANDATORY citable source (paratext kind enum + citation + year/month/day-granular publicationDate regex), optional anchors; model-extracted statements cannot be born accepted (refined). Contradicting statements about the same work coexist as separate records (proposal §14) — tested.
    • Canonical + negative + zodOnly example sets for both, goldens/schemas/ client regenerated (70 artifacts, 253 golden cases), ids SourceFactId/CreatorStatementId.
  • Producers, all real — lib record-interpretation.ts / record-craft-hypothesis.ts / record-creator-statement.ts mirror record-observation's referential checks (project active, every cited anchor/claim/work exists and is current); ingest-local-source.ts materializes four SourceFacts (sha256, byteSize, container, durationSeconds) from the SAME identify/probe outputs the records were built from, each naming the real tool — FileIdentifier/MediaProbeReport ports gained tool identity; the service adapters report node:crypto sha256 stream @ process.version and the ACTUAL ffprobe -version (read once, cached, fail-loud on unparseable output).
  • Service — routes POST /api/study/claims, /craft-hypotheses, /creator-statements; ingest response + trail carry the new records (one array per type, never merged); migration 0004_study_epistemic_record_families.sql adds craft_hypothesis / creator_statement / source_fact with the full 0001 discipline (envelope block, one-current index, tenant RLS, immutability + no-delete triggers, work-id extracted columns); postgres store get/put/list per family; deletion fans out (claims citing dead anchors, hypotheses deriving from them, statements/facts of the deleted work all tombstone — integration- asserted); export document embeds the four arrays; audit/event kinds claim-recorded (emitter corrected), craft-hypothesis-recorded, creator-statement-recorded, source-facts-recorded.
  • UI, the "visibly distinguish" halfcomponents/studio/EpistemicTypeBadge.tsx: per-type badge (label is the distinction, color redundant) + seven-entry legend with definitions, provenance, and live counts; zero counts render "None recorded in this project." — honest absence for detection/practice-result (no analyzer, no practice surface exists). StudyWorkspaceApp gains Interpret / Craft hypothesis / Creator statement forms and the badged "Epistemic record trail" listing facts, observations, claims, hypotheses, statements — each entry carries EXACTLY its own badge.
  • Verification: contracts 219/219 (new epistemic-object-types.spec: 7 types in proposal order, unique labels/definitions, mapping total + covered, epistemicIdentity literals agree; SourceFact/CreatorStatement domain rules); yemaya lib 66/66 (epistemic-records.spec: facts carry real probe values + tool, referential refusals, contradiction coexistence, export arrays); svc 50/50 vs REAL postgres+MinIO+ffprobe (route journey, facts on ingest with live ffprobe version asserted, trail-across-restart per-type arrays, deletion tombstones all four families); web e2e 3 specs green incl. NEW studio-study-epistemic-types.spec.ts (five live types badged 1:1, legend of seven, honest zeros, axe clean) + walking-skeleton + a11y regressions; adversarial stub scan of every new file: zero hits.
  • Scope note: detection and practice-result are contract-implemented (Detection entity, OutcomeEvidence practice-result literal, published examples/goldens) and visibly distinguished in the legend; live producer pipelines for them belong to the analyzer sections (YSD-2104/2105, §5) and the practice loop (§12) and remain honestly absent here.
  • Marked [x]: YSD-2107.
  • Commit: resolve via git log --grep='epistemic object types'.

2026-07-19 — Anti-promotion enforcement across layers; YSD-2108 marked#

  • The rule enforced: no layer may silently promote a detection to an interpretation or an interpretation to creator intent. Mechanism: every layer that carries an epistemic record carries its type EXPLICITLY, and the contracts make cross-type relabeling structurally unparseable.
  • API layer — kind-specific request/response wrappers (claim / hypothesis / statement; per-type trail+export arrays from YSD-2107). NEW epistemic-promotion.spec.ts proves the walls: a Detection fixture fails Observation/InterpretationClaim/CreatorStatement schemas (and every other cross-kind pairing fails likewise); a CreatorStatement in the trail's claims array refuses to parse; a hand-edited epistemicIdentity literal cannot cross kinds.
  • Search layer (the gap this item closed) — search-study-records.ts now covers interpretation claims, craft hypotheses, and creator statements with the SAME rights-before-search rule (claims by cited permitted anchors, hypotheses by included claims or permitted anchors, statements by permitted work — a statement about an ungranted work records legally but never surfaces, tested). Every epistemic hit carries kind AND canonical epistemicType; SearchResponseSchema's superRefine REJECTS a mislabeled hit, an epistemic hit with the label omitted (silence is the promotion vector), and an epistemic label on a structural work/segment hit. Contracts 0.6.0 → 0.7.0 (additive enum/field extension).
  • UI layer — enforced by YSD-2107's badge discipline: the e2e asserts every rendered record carries EXACTLY its own type badge (1:1), interpretations display their governed epistemic status, and creator statements display speaker + paratext provenance.
  • Export layer — per-type arrays with full record payloads (epistemicStatus / epistemicIdentity intact), typed-array cross-parse refusal proven in the promotion spec; integration suite asserts the downloaded document's four arrays.
  • Synthesis layer — the only synthesis surfaces today are human-authored Nisaba notebooks/cards (citing anchors, optional claimId LINK — a reference, never a copy) and the export document; no automated synthesis exists, and any future one exchanges these same typed contracts.
  • Analytics layer — the audit/event channel keeps ONE distinct kind per epistemic producer (observation-recorded / claim-recorded / craft-hypothesis-recorded / creator-statement-recorded / source-facts-recorded; Detection and OutcomeEvidence have no producer yet and honestly no event kind) — asserted distinct and registered in STUDY_WORKSPACE_EVENT_KINDS, so no aggregate can merge two types into one bucket. Model-authorship walls re-proven as a battery (model observation refused; model claim pinned to model-suggested; model-extracted statement cannot be born accepted).
  • Verification: contracts 233/233 (14 new promotion tests); yemaya lib 69/69 (3 new search-labeling tests incl. rights suppression and claim-id-appears-only-as-interpretation); svc 50/50 vs real stack (HTTP search leg returns the claim as kind=interpretation with epistemicType, never flattened); web e2e re-run green at 0.7.0; adversarial stub scan clean.
  • Marked [x]: YSD-2108.
  • Commit: resolve via git log --grep='anti-promotion'.

2026-07-19 — Manual alignment-map authoring + review; YSD-2130 marked#

  • Authoringuse-cases/author-alignment-map.ts: a human states correspondences across ALL EIGHT classes (exact, approximate, reordered, added, removed, rescored, regraded, unmapped — the contract's kind-specific range rules from YSD-2024 apply per row). The use case adds the referential half: both editions must exist and belong to the SAME work; endpoint technical-revision hashes are read from the STORED editions (never caller-supplied); every stated frame range must fit the edition's real frame count (typed AlignmentAuthoringError → HTTP 400); manual correspondences carry confidence: missing/not-applicable (a human judgment is not a machine probability, YSD-2006). Gaps are DERIVED by uncoveredSourceRanges (sort + cursor-walk interval complement over [0, durationFrames)): source frames no correspondence addresses become explicit gap ranges (YSD-2135) — unit-tested against overlapping, empty, and full coverage.
  • Review — the map is born suggested; reviewRecord gained the alignment-map family (same review-gate path: revision pin, from-state match, legal transition, human acceptance authority, next revision + immutable event). Stale pins are ReviewApplicationError → HTTP 409 review_conflict. Deletion fan-out: maps tombstone with either endpoint edition.
  • Service/contracts — routes POST /api/study/alignment-maps, GET /projects/:id/alignment-maps, POST /api/study/reviews (reviewer is ALWAYS the authenticated session user — a client cannot review in someone else's name); postgres listAlignmentMapsByProject; contracts 0.8.0 with AuthorAlignmentMapBody/ReviewRecordBody/ AlignmentMapResponse/ReviewOutcomeResponse; regenerated client/ schemas/goldens.
  • UI — the Cross-edition alignment section (appears once a second edition exists): source/target edition selects, a draft-correspondence form over the eight kinds (added drops the source side, removed/ unmapped drop the target side), the drafted list, one-click authoring, and the map list showing kinds, review state, "unaddressed source frames: N–M" (the YSD-2135 surface), and an Accept button that runs the review gate and disappears once accepted.
  • Verification: lib 76/76 (uncoveredSourceRanges unit cases; eight-kind authoring with stored hashes + derived gap; cross-work and out-of-bounds refusals; review accept → revision 2 + event; stale-pin refusal; tombstone-with-editions); svc 51/51 vs real postgres+MinIO (two real ingests → author over HTTP → derived gap in response → 400 out-of-bounds → accept 200 {accepted, revision 2} → stale 409 → list shows accepted; delete tombstones edition_alignment_map); web e2e NEW studio-study-alignment-maps.spec.ts 1/1 (draft flow, gap surfaced as text, accept via review) + walking-skeleton/a11y/ epistemic-types all green; adversarial stub scan clean.
  • Marked [x]: YSD-2130.
  • Commit: resolve via git log --grep='alignment map'.

2026-07-19 — Assisted alignment proposals behind method adapters; YSD-2131 marked#

  • The seamports.ts ProposalMethodAdapter: each of the four §9.7 methods is an adapter that returns REAL proposed correspondences with the computing tool's identity, or an honest typed refusal (unavailable + exact reason). propose-alignment-map.ts orchestrates: rights BEFORE bytes (signal extraction reads stored media, so the work needs a model-analysis grant — a denial is audited as alignment-proposals-denied and NO byte is read/presigned, adapter untouched, lib-asserted); same-work + frame-domain validation; every proposed correspondence carries UNCALIBRATED confidence (raw method score + native scale — no calibration set exists, YSD-2006/2105) and is born suggested into the YSD-2130 review gate. An empty adapter result refuses rather than shipping a map that reads as "no differences".
  • Two REAL methods (media/proposal-adapters.ts):
    • audio-fingerprint: ffmpeg's chromaprint muxer emits raw 32-bit sub-fingerprints (11025 Hz mono; hop = 1365/11025 s — a constant of the algorithm; the ~2.6 s warm-up shifts both editions equally and cancels out of offsets); a hamming-distance offset scan minimizes the bit-error rate with ties resolved toward maximal overlap then zero-offset (self-similar audio would otherwise pin to an arbitrary equal-scoring shift — found by test); match floor BER 0.25 (unrelated audio plateaus ≈0.28–0.5), exact ceiling 0.05, added/removed tails beyond 0.5 s. Proven on real media: identical recording → one exact 240-frame match at BER exactly 0; a 4 s-trimmed copy located at its true offset (removed head 84–108 frames bracketing 96) using seeded pink noise (a chirp was chroma-ambiguous — octave folding — also found by test); tonal-vs-noise refuses at the floor (no false correspondences, the YSD-2137 property).
    • shot-structure: ffmpeg scdet scene-change timestamps → shot lists → two-pointer duration-tolerance alignment (exact within 2 frames, approximate within max(0.25 s, 10%), one-sided shots emitted as removed/added — never interpolated). Proven: a 3-shot splice self-aligns as three exact pairs at the real cut frames [0,48,96]; a shortened cut missing its middle shot reports exactly one removed.
  • Two honest, data-grounded refusals: transcript-alignment checks the REAL store for subtitle tracks on both editions and refuses naming the edition without one (and refuses even when both exist, because no token-level aligner ships in this deployment — stated verbatim); telemetry-landmarks refuses (no telemetry sessions exist in this workspace). Both surface as HTTP 422 proposal_unavailable with the reason.
  • Service/UI — POST /api/study/alignment-maps/proposals (contracts 0.9.0, generated client); production + test wiring registers all four adapters. UI: "Authorize signal analysis" button (explicit model-analysis grant — deny-by-default stays visible), method select + "Propose correspondences"; proposals list beside manual maps tagged with method and "raw score N on — uncalibrated, not a probability" (YSD-2105 display rule), and accept through the SAME review gate.
  • Verification: adapter spec 8/8 over real synthesized media (identity, true-offset trim, false-correspondence refusal, real cut alignment, dropped-shot detection, planted-offset math, tie-break); lib 79/79 (rights-first with adapter-untouched assert, unavailable refusals, uncalibrated mapping); svc 60/60 vs real stack (403 before grant → real chromaprint 201 exact/BER-0 → shot-structure 201 → transcript+telemetry 422); web e2e alignment journey 1/1 including the denied → authorize → propose → accept arc + other three suites green (one a11y flake passed clean on re-run); contracts 233/233.
  • Marked [x]: YSD-2131.
  • Commit: resolve via git log --grep='alignment proposals'.

2026-07-19 — Build/patch maps + revalidation tasks; YSD-2136 marked#

  • Build/patch editions are first-class ingests — IngestBody/-command gain editionKind + platform (contract already enforces platform on game-build/patch, YSD-2074); a build's frame-addressable asset is its gameplay CAPTURE, so cross-build alignment maps ride the existing YSD-2130 authoring and YSD-2131 proposal machinery unchanged (integration-proven: build-capture editions author + accept maps like any others).
  • RevalidationTask (contracts 0.10.0) — entities/revalidation.ts: work, triggering edition, subject family (edition_alignment_map | interpretation_claim), subject record, reason, open/resolved with refined resolution presence (resolved requires the resolver + note + time; open forbids them). Registry + canonical/ negative/zodOnly examples + goldens.
  • Open on build change — ingest of a game-build/patch edition for a work with prior editions opens one task per ACCEPTED (or corrected) alignment map touching the work's prior editions and per accepted claim citing their anchors. Suggested records are NOT flagged (never trusted), non-build editions (remaster tested) open nothing, tasks are opened never auto-applied, audit revalidation-tasks-opened.
  • Resolution is a human actuse-cases/resolve-revalidation-task.ts: supersedes the task to resolved with the resolver's note; NEVER mutates the flagged record (asserted: the accepted map stays accepted — record changes go through the review gate separately); double-resolve is a typed 409; audit revalidation-task-resolved. Deletion fan-out: tasks about a deleted work tombstone with it.
  • Service/UI — migration 0005_study_revalidation_tasks.sql (full 0001 discipline + work_id column); routes GET /projects/:id/revalidation-tasks + POST /revalidation-tasks/:id/resolution; ingest response carries the opened tasks. UI: edition-kind select + platform on the ingest form; a Revalidation tasks section listing open/resolved tasks with reasons and a "Mark re-checked" resolve flow.
  • Verification: contracts 233/233; lib 82/82 (revalidation.spec: flags exactly the accepted map + accepted claim and not the suggested claim, remaster opens nothing, resolve-once semantics with untouched flagged record); svc 61/61 vs real postgres (patch capture over HTTP opens exactly one task for the accepted map, resolve 200 → 409, list shows resolved; deletion tombstones revalidation_task); web e2e alignment journey extended (patch ingest → "2 revalidation task(s) opened" → resolve with note → resolved with note shown) — one transient 401 session flake retried clean, then 2/2 consecutive clean runs; adversarial stub scan clean.
  • Marked [x]: YSD-2136.
  • Commit: resolve via git log --grep='revalidation'.

2026-07-19 — Transactional aggregate writes; YSD-3006 marked#

  • The seamStudyWorkspaceStore.inTransaction(fn) (ports.ts): every write made through the store handed to fn commits together or not at all; reads inside see the transaction's own writes.
    • Postgres (postgres-store.ts): binds a transaction-scoped store over the pg transaction client; the bound store's own withTransaction runs inline so putRevisioned's supersession mark + insert join the outer transaction instead of issuing a nested BEGIN. Constructor now takes the structural StudyDbClient surface (satisfied by PostgresClient and the tx adapter) so every store method runs identically inside and outside a transaction.
    • In-memory double: real snapshot/restore rollback across every collection (records are immutable, so shallow map copies are full snapshots) — the double now REFUSES to keep partial writes, same as the database.
  • Wrapped aggregates (each names its orphan class):
    • ingest: edition + asset + tracks + source facts + revalidation tasks + the work's next revision in ONE transaction (bytes land in object storage first — records only ever describe bytes that exist); the YSD-2136 task-opening reads run inside the same transaction.
    • delete-source: the ENTIRE tombstone graph (observations, claims, hypotheses, statements, facts, maps, revalidation tasks, links, anchors, tracks, assets, segments, editions, grants, work) plus the export-ledger marks in one transaction — a failure part-way can never leave anchors alive on a tombstoned edition.
    • review-record: the next revision and its immutable review event commit together — no reviewed record without its event.
    • expiry sweep: each asset's track tombstones commit with the asset's.
    • Single-record writes were already atomic via putRevisioned's existing mark+insert transaction.
  • Verification: lib 85/85 — new transactions.spec.ts injects failures at the store seam: a failing asset write aborts the whole ingest (zero editions/assets/tracks/facts, work still revision 1 with no editions); a failing event append aborts the review supersession (record current at revision 1, zero events); the success path commits everything. Svc 62/62 vs REAL PostgreSQL — new integration case: rollback leaves no rows, commit lands both records with read-your-writes inside the transaction, and a supersession inside a transaction rolls back cleanly on a later failure (no nested-BEGIN error). Full walking-skeleton integration suite green over the same wrapped code paths; adversarial stub scan clean.
  • Marked [x]: YSD-3006.
  • Commit: resolve via git log --grep='transactional aggregate'.

2026-07-19 — Keyset pagination, total ordering, bulk ops; YSD-3007 marked#

  • Cursor paginationPageRequest { after: {createdAt, id}, limit } on every project-scoped list (anchors, observations, claims, hypotheses, statements, facts, links, maps, revalidation tasks) and segments-by-edition. Postgres implements the keyset as (created_at, id) > ($cursor) + ORDER BY created_at, id + LIMIT — an index range scan, never scan-and-sort; the in-memory double implements the SAME total order and slicing so lib tests exercise identical semantics. Omitting the page keeps the full list for the flows that must see everything (export, deletion) — no silent caps.
  • Deterministic ordering — every list gained the id tiebreak (created_at, id), a TOTAL order stable across runs and stores; segments keep their unpaged (orderKey, created_at, id) order and now read the extracted edition_id column (FAMILY_EXTRAS entry added; migration 0006 backfills rows written before it).
  • Bulk operationstombstoneMany: per-family set-based soft-delete → tombstone transitions (id = ANY($1::uuid[])) plus ONE multi-row unnest insert into deletion_tombstone, all in one transaction — delete-source now collects its whole graph and issues a single bulk call inside its YSD-3006 transaction instead of a round trip per record.
  • Bounded query plans — migration 0006_study_keyset_pagination.sql: composite partial indexes (project_id, created_at, id) over CURRENT rows for the nine project-scoped families and (edition_id, created_at, id) for segments, matching the keyset order exactly. (Explain-plan assertions are YSD-3009's item.)
  • Verification: svc 64/64 vs REAL postgres — new integration cases: 25 same-timestamp anchors (only the id tiebreak can order them) walk at limit 10 as 10/10/5 with exact-once coverage, identical order on re-walk, and match the unpaged list; tombstoneMany over 8 records clears reads and writes exactly 8 tombstone rows (uuid[] casts — record_id is uuid, found by test). Lib 85/85 (paged double + bulk delegation under the deletion/atomicity suites). Walking-skeleton e2e 3/3 through the UI delete arc. Transient e2e 401s traced to the KNOWN stale signup-verification-state admin-store row (rewritten by a parallel worktree's BFF under a different HMAC secret); deleted the singleton row per the documented remedy and the journey passed clean.
  • Marked [x]: YSD-3007.
  • Commit: resolve via git log --grep='keyset pagination'.

2026-07-19 — Explain-plan and index-coverage tests; YSD-3009 marked#

  • The suitequery-plans.integration.spec.ts against a REAL PostgreSQL: seeds realistic volume (600 anchors with staggered timestamps behind a real edition row — the referential trigger refuses orphan anchors even in seeds — plus 300 edges, 200 grants, 120 segments, 60 export-ledger rows), runs ANALYZE, then asserts EXPLAIN (FORMAT JSON) plans by walking the node tree.
  • Two assertion modes: coverage cases run inside ONE transaction with SET LOCAL enable_seqscan = off (a pooled SET lands on a different connection than the EXPLAIN — found while building) and assert the intended index QUALIFIES with zero Seq Scan nodes on the hot table; the timeline-window case leaves the planner FREE and proves it prefers evidence_anchor_keyset on its own at volume, with NO Sort node — the index delivers keyset order directly.
  • The six hot paths of the checklist item: timeline window (keyset tuple-compare + LIMIT → evidence_anchor_keyset, planner's own choice); anchor lookup (current-revision by id → one_current/pkey); graph path (project edges → cross_reference_keyset; endpoint expansion → cross_reference_endpoint_a); rights impact (grants by work → rights_grant_work); deletion impact (anchors on the edition → evidence_anchor_edition; live export references → export_reference_project); project resume (tenant project list → study_project_tenant_project; edition segments → study_segment_keyset).
  • Verification: 6/6 plan tests green; full svc suite 70/70 vs real postgres (uuid[] casts on export work_ids found by test); adversarial scan clean. Plans are asserted from the database's own EXPLAIN output — nothing is inferred from source reading.
  • Marked [x]: YSD-3009.
  • Commit: resolve via git log --grep='explain-plan'.

2026-07-19 — Notebook publication rights gate; YSD-2104 marked#

  • The gap — seven of the eight YSD-2104 evidence surfaces were already grant-bound (display via the playback gate + doubly-gated representations, processing via the ingest/model-analysis gates, collaboration via share-project, embedding via representation gate 1's honest refusal, export via export-study-trail, retention via the grant-driven expiry sweep, transfer via transfer-project). Publication was NOT: notebooks were born private with no state transition anywhere, so the publication action existed in the contract with no surface consuming it.
  • The surfaceset-notebook-publication.ts (workspace lib): widening a notebook's audience is a rights event because notebooks carry evidence (citations + anchor-view/image-region embeds). Escalation to project-shared needs collaboration, to published needs publication, on EVERY distinct cited work, evaluated live at change time; narrowing is always allowed; both outcomes audited (notebook-publication-changed / -denied with per-work reasons). Session-anchored and vanished anchors deny-by-default. Author-or-owner may change state; idempotent same-state is a no-write no-audit success. Uncited publication refuses as a typed 400 BEFORE rights (Nisaba's cite-at-least-one rule, surfaced as policy).
  • Nisaba keeps notebook truth — adapter gains getNotebook + setNotebookPublicationState persisting the change as a superseding revision through StudyNotebookSchema.parse in Nisaba's own store; the workspace consumes both through a NotebookPublicationPort seam.
  • Route + clientPOST /api/study/notebooks/:notebookRef/publication added to the manifest (SetNotebookPublicationBodySchema), the service, and the regenerated client; schema publication + goldens verified drift-free.
  • Verification — lib 96/96 (11 new: denial reasons, collaboration vs publication separation, embed gating, lapsed-grant live re-check, narrowing after lapse, uncited refusal, dead-anchor denial, actor rules, idempotency, unknown/cross-project refusal); adapter 12/12 (supersession, uncited-publish parse refusal, unknown/unavailable outcomes); svc 71/71 vs REAL postgres+MinIO including the denied→granted→published journey and a generated-client narrowing step; contracts 233/233; all five typecheck configs clean (also repaired pre-existing tsc breaks in delete-source.ts/in-memory-deps.ts and two spec-config errors); architecture/stub/traceability gates green; adversarial grep clean.
  • Marked [x]: YSD-2104.
  • Commit: resolve via git log --grep='notebook publication'.

2026-07-19 — Confidence display for the full YSD-2006 union; YSD-2105 marked#

  • The gap — the workspace UI displayed only the uncalibrated arm of the confidence union (raw score + "not a probability", asserted by the alignment e2e). Calibrated values had no display treatment at all, and missing rendered as NOTHING — visual absence a reader can misread as unremarkable/high confidence, exactly what YSD-2105 forbids.
  • The componentStudyConfidenceIndicator renders every status: calibrated is the ONLY arm shown as a probability and always carries its uncertainty interval (coverage + bounds), calibration method, applicability boundary, and the framing "a machine estimate of detection reliability, not interpretive certainty"; uncalibrated keeps the exact native-scale caveat sentence the e2e asserts; missing renders its typed reason ("no machine confidence — not applicable / not yet calibrated / imported without lineage / processor did not report one"). Wired into the alignment-map list for ALL correspondences — manual maps now visibly show their explicit confidence absence instead of nothing.
  • Verification — study web suite 10/10 (new spec: exact percent arithmetic 0.555→55.5%, framing text, native-scale caveat with a no-percent-sign assertion, all four missing reasons); web typecheck clean for the changed files (remaining reports are pre-existing isis/ai-providers breaks outside this surface); alignment e2e's asserted uncalibrated sentence preserved verbatim by the component.
  • Marked [x]: YSD-2105.
  • Commit: resolve via git log --grep='confidence display'.

2026-07-19 — Migration rollout discipline + dev seed; YSD-3008 marked#

  • Already in place — forward-only checksummed runner (drift = hard error), rollback/forward-fix headers on all six migrations, idempotency + drift integration tests, the e2e stack bootstrap. Missing: dev seed data, schema compatibility tests, zero/low-downtime rollout verification.
  • Runner — every migration transaction now runs under SET LOCAL lock_timeout (default 5s, MigrationOptions.lockTimeoutMs): a rollout queueing behind a held lock aborts fail-fast — transactionally unrecorded and retryable — instead of blocking every later reader/writer behind its own lock request.
  • Seedscripts/seed-dev-data.ts: one coherent, contract-valid project (22 records, fixed UUIDs, tenant tenant-dev-seed) through the REAL PostgresStudyWorkspaceStore: broadly-granted + expired-grant works, link-only stream-reference assets (no fabricated bytes), tracks, scene/shot hierarchy, anchors, observations, claim, creator statement, contrast link. Idempotent by check-then-skip — the schema forbids DELETE by trigger.
  • Rollout suitemigrations-rollout.integration.spec.ts, entirely on scratch DATABASES it creates/drops: (1) fresh-replay schema inventory — all 25 record families with the full envelope block, one_current + keyset indexes, immutability/no_delete/anchor/review/audit triggers; (2) held-lock fail-fast — ACCESS EXCLUSIVE blocker, 400ms cap, unrecorded, clean retry; (3) 0006 applied over 1500 anchors + 300 payload-only segments WHILE a live writer inserts into the very table being indexed — zero writer errors, max single-write stall < 2s, backfill verified row-complete; (4) fresh-vs- stepwise databases converge to IDENTICAL schema inventories; (5) seed loads
    • re-run is a no-op.
  • Suite hygiene fixed en route — query-plans (YSD-3009) had been passing against contaminated shared-DB state: isolated on its own scratch database its 'deletion impact' and 'project resume' assertions FAILED (single-valued columns made rival indexes cost-identical; study_project had zero rows). Re-seeded selectively (200 projects/20 tenants, 10 editions × 10 works) so every plan choice is meaningful; now deterministic. Spec files for this service run serially (fileParallelism: false) — postgres-store and the walking skeleton both reset the shared study schema and raced under file parallelism (18-test flake reproduced, then eliminated).
  • Verification — svc suite 75/75 twice consecutively (6 files, serial); rollout suite deterministic across repeated runs; tsc app config clean; architecture/stub/traceability gates green.
  • Marked [x]: YSD-3008.
  • Commit: resolve via git log --grep='migration rollout'.

2026-07-19 — Object namespace grammar + stored metadata; YSD-3020 marked#

  • The modulepolicies/object-namespaces.ts (workspace lib): a canonical, tenant-first, fully PARSEABLE key grammar for all eleven object classes the item names (originals, quarantine, proxies, thumbnail/crop/waveform representations, stems, analysis artifacts, learner attempts, exports, temp), with typed builders that refuse malformed segments (path traversal, non-uuid subjects, uppercase hashes, unsafe slugs), a parser that round-trips every class, and a retention-class map (source-bound / quarantine / derived / learner-owned / export / temporary) that the YSD-3023 lifecycle policies will attach to.
  • Metadata contractStudyObjectMetadataSchema (class, tenant, retention class, subject record, content type) derived from the canonical key alone; the service storage adapter now REFUSES writes outside the grammar and stamps every stored object with this metadata (YSD-3024 extends the stored set with derivation/key-version/policy state).
  • Wired call sites — ingest originals, representation sidecars, and trail exports all build keys through the module; the historical key shapes are pinned byte-for-byte by test so existing stored objects stay addressable.
  • Verification — lib 113/113 (17 new: per-class round-trips, historical shape pins, malformed-segment refusals, out-of-grammar parse rejections, metadata derivation, total retention-class coverage); full svc suite 75/75 against real MinIO — every real write passes the new enforcement; all typecheck configs clean; stub/architecture gates green.
  • Marked [x]: YSD-3020.
  • Commit: resolve via git log --grep='object namespace'.

2026-07-19 — Storage security: SSE, tenant isolation, presign ceiling; YSD-3021 marked#

  • EncryptionSTUDY_S3_SSE (none|AES256|aws:kms) + STUDY_S3_SSE_KMS_KEY_ID flow through every upload via the platform client's ServerSideEncryption/ SSEKMSKeyId plumbing; config fails fast when aws:kms lacks a key and when a PRODUCTION deployment with S3 configured leaves SSE at 'none' — encrypted at rest is not optional in production.
  • Tenant isolation, two layers — (1) auth: requireAuth now takes the deployment's bound tenant; a validly-signed token for any other tenant is a 403 tenant_mismatch BEFORE any route logic (previously a cross-tenant token would have operated this deployment's stores under the caller's claimed tenant — found while reading the wiring for this item); (2) storage: the adapter parses every key through the YSD-3020 grammar and refuses any key whose tenant differs from the deployment's before putFile/putJson/presign/ delete touch storage.
  • Short-lived URLsSTUDY_PRESIGN_MAX_SECONDS (default 600, hard cap 3600) enforced in the adapter: a presign request above the ceiling is refused, never granted. All production presigns are 300–600s.
  • Rights re-check per URL decision — verified at all four presign sites: playback (live authorizePlayback before presign), representations (double gate incl. live re-evaluation), exports (per-work evaluation at document creation), alignment proposals (model-analysis grant before source bytes are presigned). Each is covered by existing lapse/denial tests.
  • Verification — app.spec 23/23 (3 new: foreign-tenant 403 + own-tenant pass, SSE config rules, adapter refusals against an unroutable endpoint proving refusal precedes any storage call); full svc suite 78/78 vs real postgres+MinIO; tsc clean; stub/architecture gates green.
  • Marked [x]: YSD-3021.
  • Commit: resolve via git log --grep='storage security'.

2026-07-19 — Media limits at ingest + derivative cap; YSD-3022 marked#

  • The policypolicies/media-limits.ts: one STUDY_MEDIA_LIMITS object covering container allowlist (mp4/quicktime/matroska/webm/mxf), an explicit archive refusal list (zip/gzip/tar/7z/rar/zstd/bzip2 — refused because the workspace HAS no decompression path, closing the decompression-bomb surface by construction), 64 GiB size, 6 h duration, video codec allowlist (h264/hevc/vp8/vp9/av1/prores/mjpeg/ffv1/mpeg2video/dnxhd), audio codec allowlist, 8K-DCI pixels per frame, 2 592 000 frames, and a 4K-UHD derivative-resolution ceiling.
  • Enforcement order — declared-content-type checks run BEFORE the file is read (proved by an identify-call counter of zero); probed checks (size, duration, codecs, pixels, frame count — every violation collected and named) run after the REAL probe and before any byte reaches storage; refusals are audited as ingest-denied and the route maps MediaLimitError to 422 media_limits with the violation list.
  • Derivative cap — extractFrame now ALWAYS receives a pixel cap: the platform ceiling tightened by a stricter grant limit (derivativePixelCap), never widened by a looser one.
  • Verification — lib 121/121 (8 new: archive-before-read with audit + zero storage, non-media container, oversize, five-violation battery, healthy pass-through, exact-at-limit acceptance, cap tightening); full svc suite 78/78 vs real stack (the real ffmpeg film passes the limits); typechecks clean; stub/architecture gates green.
  • Marked [x]: YSD-3022.
  • Commit: resolve via git log --grep='media limits'.
  • Already in place — source expiry + revocation-based retention arithmetic (sweepRetentionExpiry counts from validUntil OR revokedAt) and confirmed deletion (delete-source). This item added the missing layers:
  • Age-driven object sweepsweep-object-lifecycles.ts deletes processing scratch (tmp/, > 24 h) and expired quarantine uploads (> 14 d) per OBJECT_LIFECYCLE_WINDOWS; ONLY keys that parse as those two classes are ever age-deleted (a malformed key smuggled under the prefix is held, deny-by-default); rides the /expiry-sweep route + timer; audited with exact counts; StudyObjectStore gained list (paginated in the S3 adapter, tenant-guarded).
  • Legal holdmanage-legal-hold.ts: hold/release the work's WHOLE copy graph (work/editions/assets/tracks) in one transaction, audited with reason; POST /works/:workId/legal-hold + regenerated client. Hold STATE lives in the deletion-state COLUMN (payloads are immutable — reading envelopes for it would be silently wrong in production; caught during implementation), read back via the new store.listLegalHolds graph query. While held: delete-source refuses (423 legal_hold, audited deletion-blocked) and the retention sweep skips the work however long expired; release restores both paths.
  • Two real bugs found by the new real-PG test — (1) media_track.asset_id was NEVER filled (FAMILY_EXTRAS entry missing) — added + migration 0007 backfills from payload; (2) tombstone()/tombstoneMany() on a held row silently no-opped both transitions and STILL wrote the deletion-ledger row — a false deletion claim; both now verify no current row survived and abort the transaction (the 0001 trigger backstops at the database).
  • Verification — lib 125/125 (4 new lifecycle tests incl. hold-blocks- deletion-then-release and hold-outranks-retention-clock); store integration 19/19 incl. the column-truth hold graph + trigger-refusal test vs REAL PG; full svc suite 79/79; schemas/goldens stable, client regenerated; all typechecks + gates green.
  • Marked [x]: YSD-3023.
  • Commit: resolve via git log --grep='lifecycle policies'.

2026-07-19 — Per-object stored identity metadata; YSD-3024 marked#

  • The contractStudyStoredObjectMetadataSchema extends the YSD-3020 base with contentSha256 + byteSize (exact-byte identity, computed AT THE STORAGE SEAM from the very bytes uploaded — never caller-claimed), encryption-key version ('none' | 'AES256' | 'aws:kms:' from the adapter's own SSE config), and the writer's provenance: sourceRevision, derivedFrom (derivation-graph edge: object:<key> or record:<family>:<id>), rightsGrantIds (policy state at write time), and a typed content-credentials state (not-inspected until Section 4.6 ships the real C2PA inspector; present:<manifestRef> supported). Lossless flatten to the stored string map.
  • The writers — ingest stamps the original as its own source revision under the covering grant; representations stamp the anchor's technical revision, object:<original key> parentage, and the deciding grant; exports stamp the project revision and EVERY grant that admitted a work. A provenance-less write (scratch) stores identity + base only — nothing fabricated.
  • Verification — lib 127/127 (flatten round-trip incl. kms + present- credentials arms, junk-identity refusal, ingest-provenance assertion); svc 79/79 vs real stack including a NEW end-to-end assertion that the MinIO object's stored metadata carries the exact-byte sha equal to the edition's content hash plus derivation/rights/credential/encryption fields; typechecks + gates green.
  • Marked [x]: YSD-3024.
  • Commit: resolve via git log --grep='stored identity metadata'.

2026-07-19 — Storage-security scenario tests; YSD-3025 marked#

  • New real-MinIO suitestorage-security.integration.spec.ts (per-run bucket, reachability-gated, hard-fails under STUDY_PG_REQUIRED): (1) cross-tenant isolation — tenant B's store can neither read, write, list, nor delete tenant A's namespace while A serves its own bytes; (2) stale signed URL — a 1-second presign serves 200, then 403 after expiry against real MinIO; (3) partial upload — a failed upload leaves NO readable object (client.exists false); (4) restore — a deleted key 404s, then a re-upload serves the exact original bytes again.
  • New lib case — interrupted delete: an injected storage failure on the first delete aborts deleteSource with records intact, tombstones empty, and bytes still claimed; the identical retry completes to full deletion.
  • Already covered, verified by reading — revoked-grant denial (rights-gate reason battery + walking-skeleton revocation path + revocation-based retention arithmetic in deletion-expiry); cross-tenant AUTH refusal (app.spec 403 tenant_mismatch); upload-failure transactional abort (transactions.spec).
  • Verification — storage-security 4/4 vs real MinIO; lib 128/128; full svc suite 83/83 (7 files); typechecks + stub scan green.
  • Marked [x]: YSD-3025 — Section 3.2 rights-aware object storage is now COMPLETE (3020–3025).
  • Commit: resolve via git log --grep='storage-security scenario'.

2026-07-19 — FTS search projection over a transactional outbox; YSD-3040 + YSD-3045 marked#

  • Stack per ADR-0074 — Postgres FTS, no external engine: migration 0008 adds study.projection_outbox and study.search_document (weighted tsvector + GIN, facets jsonb + GIN, tenant/project scope, and the work_ids each document's VISIBILITY depends on).
  • Outbox (3045) — every putRevisioned (rev-1 and supersession), tombstone, and tombstoneMany now appends an outbox row IN THE SAME TRANSACTION as the authoritative write: late is possible, silent loss is not. Failures are per-row recorded (attempts + last_error) and retried; a poisoned row never blocks the queue and is never dropped.
  • Projector + query (3040) — search-projector.ts maps the SAME six families/text fields as the authoritative live search ("the projection replaces the scan, not the rights rule"); anchor-grounded records carry the works of their cited anchors. searchProjection() evaluates LIVE grants for exactly the works each matched document depends on before returning hits or facet counts — the index accelerates matching and can never widen visibility; epistemic hits stay labeled (YSD-2108 at the projection layer). Rebuild deletes a tenant's documents, re-enqueues every current record, and drains to convergence (resumable — the queue is durable). Route POST /api/study/projection-drain + timer cadence + generated client.
  • Verification — new scratch-DB suite 6/6 vs real PG: outbox-per-write, drain to the exact expected per-family document counts, expired-grant work suppressed from hits AND facet counts (matched-but-suppressed counted), kind facet narrowing without leakage, supersession retitles + tombstone removes, injected-failure row recorded then retried to completion, rebuild converges to the byte-identical document set. Full svc suite 89/89 (8 files) incl. a generated-client drain step in the walking skeleton; contracts 233/233 with schema check stable; gates green. 3044/3046/3047/3048 stay open for the vector/graph projections, monitoring, and the full equivalence/leakage batteries.
  • Marked [x]: YSD-3040, YSD-3045.
  • Commit: resolve via git log --grep='search projection'.

2026-07-21 — YSD-9030 Character identity & role model (§9.2 character/visual-design lens)#

  • Contract (new): libs/contracts/src/study/entities/character-profile.tsCharacterProfileSchema, an enveloped study record that is the character-lens OVERLAY on the canonical StudyEntity of kind character (referenced by entityId; the identity is never re-minted — YSD-0014). New branded id CharacterProfileId (ids.ts). It adds the two things a generic entity cannot express:
    • Role across scenes/editions/buildsroleAssignments (min 1: a character models identity AND role). Each is attributed, confidence-carrying, review-stated, and SCOPED: whole-work | edition (needs editionId) | segments (needs segmentIds), with the wrong-scope fields rejected by superRefine. Narrative-function SEMANTICS stay Hathor-owned via an OPTIONAL taxonomy ref (domain + termRef) — no theory vocabulary is copied into Yemaya (YSD-0014). Casting prominence (lead…uncredited-extra) is the production-structural attribute the workspace owns. Documentary bases (source-metadata/creator-statement) must cite; model-authored roles cannot be born accepted.
    • Presentations across states/disguises/costumes/skins/transformationspresentations, each a FACET of the one identity tied to the SourceEdition (film cut OR game build/patch — YSD-2136) it occurs in and the frames that show it (keyAnchorIds, appearsInSegmentIds). presentationKind spans costume/disguise/skin/transformation/form/state/ age-stage/recast/build-variant/other.
  • Anti-appearance-merge guarantee (the heart of YSD-9030, YSD-4052/4053): a presentation attaches to the identity only via SameIdentityBasis = narrative-continuity | credited-casting | source-metadata | creator-statement | authored-assertion. Appearance similarity / visual / biometric / model / face match are DELIBERATELY ABSENT from the enum, so two look-alikes cannot be collapsed and a disguise/transformation cannot fracture one identity — the rule is STRUCTURAL, not a bypassable runtime check. FORBIDDEN_IDENTITY_SIGNALS
    • isAppearanceOnlyBasis() (case/spacing tolerant) let authoring seams reject a smuggled appearance justification in free text. Identity MERGES between two entities are not performed here at all — they route through the reviewed CrossReference identity path (YSD-6089). credited-casting requires the performer entity; authored-assertion requires stated reasoning; documentary bases require a citation; model-suggested presentations cannot be born accepted.
  • Helpers (real behavior, tested): editionsForProfile (cross-edition/ cross-build reach of one identity), presentationsInEdition, rolesInEdition (whole-work + edition-scoped roles in force for a cut/build).
  • Wiring: registered in schema-registry.ts (CharacterProfile), exported from study index.ts, canonical+negative+zodOnly examples in examples.ts. generate-study-schemas.ts published 81 schema artifacts (was 80); generate-study-goldens.ts regenerated (283 cases). Non-route contract — the generated client is untouched.
  • Verification: dedicated character-profile.spec.ts (18 tests) covers the anti-appearance-merge guarantee, every scope/basis refinement, duplicate-key rejection, and the cross-edition helpers. Full study suite 30 files / 346 tests green; tsc -p tsconfig.lib.json clean; schema --check "79 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "283 fixture cases stable"; adversarial stub-scan clean; eslint clean.
  • Marked [x]: YSD-9030.
  • Commit: resolve via git log --grep='YSD-9030'.

2026-07-21 — YSD-9270 Game-design & systems lens model (§9.10)#

  • Contract (new): libs/contracts/src/study/entities/game-system-model.tsGameSystemModelSchema, an enveloped study record modeling a game build's designed system with evidence from play. New branded id GameSystemModelId. It captures the full vocabulary the item names:
    • Core loopcoreLoop (min 1 GameCoreLoopStep): ordered steps of verb → outcome → feedback → goal, with verbKey/feedbackKey/goalKey resolving to elements of the matching kind (referential integrity enforced at the model level; a feedbackKey pointing at a verb is rejected).
    • The 11 element kindsGameSystemElement covers verb, goal, rule, resource, constraint, feedback, risk, reward, failure, recovery, progression. Each carries a description, evidence basis, evidence tier, play anchors, hidden-state/exact-input flags, and independent review provenance.
  • Tier-honesty guarantee (YSD-9270, YSD-12002/12005): every element is tiered against the shared GameStudyTierSchema ladder (my YSD-12001):
    • A video-only element CANNOT assert hidden engine state or exact inputs — those are rejected at parse; they require Tier B (instrumented-session) or C (creator-owned). So a visible inference can never be rendered as engine truth.
    • An evidence basis demands a minimum tier (MINIMUM_TIER_FOR_BASIS): telemetry needs Tier B, creator-documented needs Tier C; a telemetry basis at a video-only tier is incoherent and rejected.
    • No element may rest on a tier the study did not reach — element evidenceTier must be subsumed by the model tier via tierChain (reused from game-study-tier.ts).
    • Observed/instrumented elements must anchor at least one moment of play; model-authored elements cannot be born accepted.
  • Helpers (real behavior, tested): elementsOfKind, coreLoopVerbKeys (ordered by step), evidenceTiersInModel, assertsBeyondApparent (a quick downstream gate: a model with any hidden-state/exact-input claim is coherent only at Tier B/C).
  • Wiring: registered in schema-registry.ts (GameSystemModel), exported from study index.ts, canonical (minimal video-only + complete instrumented)
    • structural negatives + cross-field zodOnly examples in examples.ts. generate-study-schemas.ts published 82 artifacts (was 81); goldens 288. Non-route contract — the generated client is untouched.
  • Verification: dedicated game-system-model.spec.ts (12 tests) covers the full vocabulary, the Tier-A hidden-state/exact-input bar, the basis→tier minimums, tier-subsumption, core-loop referential integrity, duplicate-key and model-authored-accept rejection, and ordered helpers. Full study suite 31 files / 357 tests green; tsc -p tsconfig.lib.json clean; schema --check "80 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "288 fixture cases stable"; adversarial stub-scan clean.
  • Marked [x]: YSD-9270.
  • Commit: resolve via git log --grep='YSD-9270'.

2026-07-21 — YSD-13001 Typed study query AST (§13.1 hybrid search foundation)#

  • Contract (new): libs/contracts/src/study/query-ast.tsStudyQuery, the single typed shape every study search compiles to (NL parsing YSD-13002 targets it; executors YSD-13003–13011 consume it). Non-enveloped request contract (like GraphQueryRequest). Registered as StudyQuery.
    • All fifteen filter dimensions the item names, as a discriminated QueryFilterSchema union on dimension: source, locator, epistemic, taxonomy, text, vector, runtime, relationship, rights, permission, synthetic-media, culture, language, review, version. QUERY_FILTER_DIMENSIONS pins the set (spec asserts exactly 15).
    • Boolean composition — a real recursive AST: StudyQueryNode = filter | and | or | not via z.lazy with an explicit TS type; and/or take ≥1 clause, not is unary. z.toJSONSchema emits $defs/$ref and the published JSON Schema validates through ajv (schema-publication.spec).
  • Reuse over re-modeling (YSD-0014): the epistemic (EpistemicObjectType), review (ReviewState), synthetic-media (SyntheticMediaDisposition), rights-action (RightsAction), work-medium (WorkMedium), and entity-relation (EntityRelationKind) vocabularies are imported from the entities, so a filter can never drift from the values it filters. Vector modalities and locator kinds mirror the svc vector projection / SourceLocator discriminants.
  • No silent match-everything: every all-optional filter (source, locator, rights, permission, version, runtime) rejects an empty constraint set via superRefine; frame/time/date windows enforce start ≤ end; a relation kind is bound to an entity-relation path only. rights/permission are first-class dimensions so a query can restrict — never broaden — access (YSD-13012/13013).
  • Helpers (real, tested): collectFilters (leaf walk), dimensionsUsed, queryDepth (boolean nesting), constrainsAccess (does the query gate on rights/permission — the executor's pre-widen check).
  • Wiring: registered in schema-registry.ts, exported from index.ts, three canonical examples (single filter; a composed and/or/not tree; an AND covering all remaining dimensions) + structural negatives + cross-field zodOnly examples. generate-study-schemas.ts published 83 artifacts (was 82); goldens 290. Non-route contract — the generated client is untouched.
  • Verification: dedicated query-ast.spec.ts (11 tests) covers the fifteen dimensions, the no-empty-filter rule, window/relation cross-field rules, vocabulary reuse, vector references, recursive depth, and the access-gate helper. Full study suite 32 files / 368 tests green; tsc -p tsconfig.lib.json clean; schema --check "81 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "290 fixture cases stable"; adversarial stub-scan clean.
  • Marked [x]: YSD-13001.
  • Commit: resolve via git log --grep='YSD-13001'.

2026-07-21 — YSD-12020 Engine-neutral instrumented-session envelope (§12)#

  • Contract (new): libs/contracts/src/study/instrumented-session-envelope.tsInstrumentedSessionEnvelope, the versioned engine-neutral transport envelope every instrumented session crosses the connector boundary in (the layer above the per-session InstrumentedSession record). Non-enveloped transport contract. Registered as InstrumentedSessionEnvelope. It defines all five pieces the item bundles:
    1. Versioned engine-neutral envelopeenvelopeVersion (semver), an engine-neutral SessionEngineIdentity (unreal/unity/godot/v2/custom-runtime; a custom runtime must be named), and the honest captureClass (YSD-12023).
    2. Event schema registrySessionEventSchema[]: per stream a versioned schema with accountable ownerDomain, producerBuild, clockBasis (session-monotonic/video-clock/engine-frame/event-timestamp — YSD-12021), sequenced, and privacyClass (YSD-12024).
    3. Signed manifestSignedSessionManifest: content hash, covered streams, event count, capture class, reusing the existing StudyContentSignature.
    4. Connector capability handshakeConnectorCapabilityHandshake: protocol version, maxSupportedTier (reuses my GameStudyTier ladder), supported streams/capture-classes, feature flags, event-rate ceiling.
    5. Compatibility policyEnvelopeCompatibilityPolicy: min/max envelope version, event-schema compat mode, deprecated versions, with min ≤ max.
  • Structural safety properties: an instrumented session requires a Tier B/C connector (tierChain(maxSupportedTier) must include instrumented-session — a video-only connector cannot emit instrumented telemetry, YSD-12005); prohibited-class telemetry may never sit in the durable envelope (filtered before ingest, YSD-12025); the envelope version must fall inside the connector's own compatibility window; the handshake engine must match the envelope engine; the connector must declare support for the capture class it produced; the manifest capture class must match; and the manifest must cover every declared stream.
  • Helpers (real, tested): compareEnvelopeVersions, envelopeVersionSupported (window + deprecation), connectorSupportsCapture, clockBasesInEnvelope.
  • Wiring: registered in schema-registry.ts, exported from index.ts, canonical (full + minimal) + structural negatives + cross-field zodOnly examples. generate-study-schemas.ts published 84 artifacts (was 83); goldens 292. Non-route contract — the generated client is untouched.
  • Verification: dedicated instrumented-session-envelope.spec.ts (13 tests) covers the tier bar, prohibited-telemetry bar, version-window admission, engine agreement, capture-class support, manifest coverage, custom-runtime naming, and the compat/version helpers. Full study suite 33 files / 380 tests green; tsc -p tsconfig.lib.json clean; schema --check "82 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "292 fixture cases stable"; adversarial stub-scan clean.
  • Marked [x]: YSD-12020.
  • Commit: resolve via git log --grep='YSD-12020'.

2026-07-21 — YSD-15090 Portable study manifest (§15.5 portable & offline study)#

  • Contract (new): libs/contracts/src/study/portable-study-manifest.tsPortableStudyManifest, the self-describing export bundle a study travels in across web/desktop/mobile/imported packages. Non-enveloped export artifact. Registered as PortableStudyManifest. It carries what a study IS by reference: metadata (project/tenant/export identity + generator), sources (workId + editionIds + per-edition technical-revision pins = the "versions"), anchors, epistemicObjects (reuse EpistemicObjectType + ReviewState), links (cross-references), views (saved-graph-view / comparison-set), and a mediaPolicy.
  • "Without prohibited media" — structural (YSD-15090): the manifest has no field for raw bytes; the only embeddable media is a ManifestEmbeddedDerivative, and every one MUST name the rightsGrantId permitting it (YSD-15091 layers the recipient/destination/resolution/duration checks on that grant) and carry its own sha256 — there is no unattributed media. A references-only manifest structurally forbids any embedded derivative; prohibitedMediaExcluded is an affirmative z.literal(true) the producer cannot omit or set false.
  • Referential integrity: every anchor must reference a declared source edition; every embedded derivative must derive from a declared edition; every epistemic anchor citation must be a declared anchor; every edition-revision pin must name an edition in its source's editionIds. Version pins (manifest format version + study-contracts schema version + per-edition technical revisions) travel with the bundle so a recipient reads the exact revisions.
  • Helpers (real, tested): manifestIsReferencesOnly, manifestReferencesWork, manifestEditionRevisions, embeddedDerivativeGrants (the grant set a recipient must honor).
  • Wiring: registered in schema-registry.ts, exported from index.ts, canonical (grant-permitted + references-only) + structural negatives + cross-field zodOnly examples. generate-study-schemas.ts published 85 artifacts (was 84); goldens 294. Non-route contract — client untouched.
  • Verification: dedicated portable-study-manifest.spec.ts (9 tests) covers the no-prohibited-media bar, references-only vs embedded, rights attribution, all four referential-integrity rules, and unknown-kind rejection. Full study suite 34 files / 389 tests green; tsc -p tsconfig.lib.json clean; schema --check "83 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "294 fixture cases stable"; adversarial stub-scan clean.
  • Marked [x]: YSD-15090.
  • Commit: resolve via git log --grep='YSD-15090'.

2026-07-21 — YSD-9096 Performance affect-dynamics model (§9.6 performance lens)#

  • Contract (new): libs/contracts/src/study/entities/affect-dynamics.tsPerformanceAffectDynamics, an enveloped study record modeling the temporal shape of an INTERPRETED affective beat. New branded id AffectDynamicsId. Registered as PerformanceAffectDynamics. Models all seven phases the item names (AFFECT_PHASE_KINDS: onset, build, peak, suppression, leakage, recovery, transition), each an anchored, behavior-grounded, uncertain interpretation, plus contextualFacets (scene-context, character-goal, blocking, relationship, prior-beat, cultural-context, genre-convention).
  • Actor-safety wall (YSD-9090/YSD-9098) — structural: affect is an interpretation of the PERFORMANCE, never a claim about the performer's real emotion or a diagnosis, never an opaque quality score. Enforced by:
    • framing is a required z.literal('interpreted-not-real-emotion') the producer cannot omit or change;
    • every phase must state its observable behavioralBasis (behavior-specific);
    • intensity is a qualitative enum (subtle/moderate/pronounced) — a numeric intensity is rejected (no quality score, YSD-9095);
    • every phase carries confidence so an interpretation is never rendered as a detected fact (YSD-9090);
    • there is no field for the performer's identity or presumed mental state. Phase orders and facet kinds must be distinct; a model-authored interpretation cannot be born accepted (YSD-0019).
  • Helpers (real, tested): phasesInOrder, affectPhaseKinds, hasAffectPhase.
  • Wiring: registered in schema-registry.ts, exported from index.ts, canonical (full dread beat + minimal) + structural negatives (incl. numeric intensity, wrong framing, missing behavioral basis) + cross-field zodOnly. generate-study-schemas.ts published 86 artifacts (was 85); goldens 300. Non-route contract — client untouched.
  • Verification: dedicated affect-dynamics.spec.ts (8 tests) covers the full phase vocabulary, the framing wall, behavioral-basis requirement, qualitative-not-numeric intensity, min-phase/facet, distinctness, and the born-accepted rule. Full study suite 35 files / 397 tests green; tsc -p tsconfig.lib.json clean; schema --check "84 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "300 fixture cases stable"; adversarial stub-scan clean.
  • Marked [x]: YSD-9096.
  • Commit: resolve via git log --grep='YSD-9096'.

2026-07-21 — YSD-19123 Governed taxonomy extensions (§19 ecosystem governance)#

  • Contract (new): libs/contracts/src/study/entities/governed-taxonomy-extension.tsGovernedTaxonomyExtension, the enveloped registration/governance record for a whole extension VOCABULARY a domain, organization, or community contributes on top of the core study taxonomies (individual terms stay TaxonomyTerms; this governs their namespace). New branded id TaxonomyExtensionId. Registered as GovernedTaxonomyExtension. Carries all eight governance facets the item names: owner (kind + id + accountable contact), dotted collision-proof namespace, provenance, languageScope (BCP-47, ≥1) + cultureScope, compatibility (mode + baseline vocabulary + core version range), migration (versioning policy + breaking-change policy), moderation (status + policy + reviewer), and removal (removable + on-removal disposition + takedown contact + retention).
  • Governance enforced structurally (YSD-0021): an approved moderation status must name a human moderator and time; a non-standalone (additive/ overlay) extension must declare the baseline vocabulary it extends and a core version range; migrate-to-baseline removal requires a baseline; orphaning usages with zero retention is refused; and a model-created extension cannot be born approved (approval is a human act). extensionIsUsable combines the gates a consumer checks (approved + live) before honoring the extension's terms.
  • Helpers (real, tested): extensionIsModerated, extensionAppliesToLanguage (exact tag or primary subtag), extensionIsUsable.
  • Wiring: registered in schema-registry.ts, exported from index.ts, canonical (additive-only approved + standalone pending) + structural negatives
    • cross-field zodOnly examples. generate-study-schemas.ts published 87 artifacts (was 86); goldens 305. Non-route contract — client untouched.
  • Verification: dedicated governed-taxonomy-extension.spec.ts (9 tests) covers the eight facets, the dotted-namespace rule, moderator requirement, baseline/version-range requirement, migrate-to-baseline linkage, orphan-retention guard, model-self-approval bar, language-scope matching, and the usable gate. Full study suite 36 files / 406 tests green; tsc -p tsconfig.lib.json clean; schema --check "85 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "305 fixture cases stable"; stub-scan clean.
  • Marked [x]: YSD-19123.
  • Commit: resolve via git log --grep='YSD-19123'.

2026-07-21 — YSD-14020 Pedagogical craft concept graph (§14.2 Metis craft concept graph)#

  • Contract (new): libs/contracts/src/study/entities/pedagogical-concept.tsPedagogicalConcept, one node of the distinct pedagogical concept graph the learning system teaches against. New branded id PedagogicalConceptId. Registered as PedagogicalConcept. It does NOT re-mint the concept: the canonical concept is Metis-owned and referenced by MetisConceptRef (YSD-0014); this is the Yemaya-side pedagogical mapping. Carries every facet the item names: conceptOwner (the craft domain), definition, prerequisiteConceptIds (learning order — YSD-14021–14023), corequisiteConceptIds and relatedConceptIds, evidenceTypes (reuses the MasteryEvidenceDimension six from my YSD-14026 work), rubricDimensions (the seven YSD-14070–14076 criteria families), exerciseRefs, taxonomyVersion, and applicability (cultures + mandatory limitations + established/contested sequence state).
  • Kept distinct from the creative plane (YSD-14025): the node references a Metis learning concept, never an OriginalConcept — pedagogy and a learner's own creative work never collapse into one graph.
  • Coherent structure enforced: no self-loops (a concept is not its own prerequisite/corequisite/related); prerequisite and corequisite sets are disjoint (learned before OR alongside, not both); evidence types and rubric dimensions are distinct; applicability limitations are always stated; and a domain expert may mark a sequence contested (YSD-14024). A model-authored concept cannot be born accepted (experts curate the graph).
  • Helpers (real, tested): conceptPrerequisites, isContestedSequence, conceptEvidenceTypes.
  • Wiring: registered in schema-registry.ts, exported from index.ts, canonical (with prerequisites + minimal) + structural negatives + cross-field zodOnly examples. generate-study-schemas.ts published 88 artifacts (was 87); goldens 311. Non-route contract — client untouched.
  • Verification: dedicated pedagogical-concept.spec.ts (9 tests) covers the Metis mapping, rubric-dimension family, self-loop and prereq/coreq-disjoint rules, distinctness, mandatory limitations, contested sequences, and the born-accepted bar. Full study suite 37 files / 415 tests green; tsc -p tsconfig.lib.json clean; schema --check "86 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "311 fixture cases stable"; adversarial stub-scan clean.
  • Marked [x]: YSD-14020.
  • Commit: resolve via git log --grep='YSD-14020'.

2026-07-21 — YSD-9124 Animation style model (§9.4 animation lens)#

  • Contract (new): libs/contracts/src/study/entities/animation-style.tsAnimationStyleModel, an enveloped study record modeling a work's animation style. New branded id AnimationStyleModelId. Registered as AnimationStyleModel. Carries every dimension the item names: tradition (hand-drawn-2d/anime-limited/cutout/stop-motion/cg-3d/pixel/rotoscope/ motion-capture/mixed) + traditionContext; fullness (limited/full/hybrid) and frameHold (ones/twos/threes/mixed) for limited/full characteristics; stylizationDegree; characteristics (exaggeration, pose-economy, squash-stretch, smear, held-pose, limited-inbetween, cadence-accent — each with a degree and anchored evidence); and cadenceDescription.
  • Stance: stylization is a choice, not an error (YSD-9123) — structural: a required framing literal (stylization-is-choice-not-error) the producer cannot omit, plus a required tradition + context so limited-animation cadence, smears, and held poses are read as craft decisions rather than defects. Every characteristic and the overall read carry anchors; the read carries confidence (never a detected fact); characteristic kinds are distinct; a model-authored read cannot be born accepted.
  • Helpers (real, tested): styleCharacteristicKinds, isLimitedAnimation, hasStyleCharacteristic.
  • Wiring: registered in schema-registry.ts, exported from index.ts, canonical (limited anime + full CG) + structural negatives + cross-field zodOnly examples. generate-study-schemas.ts published 89 artifacts (was 88); goldens 317. Non-route contract — client untouched.
  • Verification: dedicated animation-style.spec.ts (8 tests) covers the style vocabulary, the framing stance, tradition+context requirement, per- characteristic anchoring, min-characteristic, distinctness, limited-vs-full without treating either as error, and the born-accepted bar. Full study suite 38 files / 423 tests green; tsc -p tsconfig.lib.json clean; schema --check "87 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "317 fixture cases stable"; adversarial stub-scan clean.
  • Marked [x]: YSD-9124.
  • Commit: resolve via git log --grep='YSD-9124'.

2026-07-21 — YSD-14070 Rubric criteria for anchor accuracy and source use (§14.3)#

  • Contract (new): libs/contracts/src/study/entities/rubric-criterion.tsPedagogicalRubricCriterion, the assessment criteria for one pedagogical rubric dimension (keyed by the seven RubricDimensions I defined in YSD-14020). New branded id PedagogicalRubricCriterionId. Registered as PedagogicalRubricCriterion (renamed from RubricCriterion to avoid a collision with the lightweight per-exercise RubricCriterion in entities/learning.ts). Carries dimension, facets (e.g. anchor accuracy AND source use for 14070), ordered qualitative levels with descriptors, worked examples, and accommodations.
  • "Not a taste score" — structural (§6.5/§6.6): a rubric assesses observable craft PROCESS and never collapses into a number. There is NO score field; .strict() rejects any smuggled score/overallScore; levels are ORDERED qualitative bands (not points); and a required framing literal (assesses-craft-not-taste-score) states the stance. Levels have distinct keys and orders; every example illustrates a declared level; a model-authored criterion cannot be born accepted (experts curate the rubric).
  • This item: the contract plus the canonical anchor-accuracy/source-use criterion (developing → proficient → exemplary, with worked examples and accommodations). YSD-14071–14076 will add canonical criteria for the other six dimensions on the same contract.
  • Helpers (real, tested): levelsInOrder, criterionFacets, assessesDimension.
  • Wiring: registered in schema-registry.ts, exported from index.ts, canonical + structural negatives (incl. the smuggled-score cases) + cross-field zodOnly. generate-study-schemas.ts published 90 artifacts (was 89, after the rename cleanup); goldens 323. Non-route contract — client untouched.
  • Verification: dedicated rubric-criterion.spec.ts (8 tests) covers the ordered levels, the no-numeric-score guarantee, min-two-levels, distinct keys/ orders, example→level coherence, min-facet, unknown-dimension, and born-accepted bar. Full study suite 39 files / 431 tests green; tsc -p tsconfig.lib.json clean; schema --check "88 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "323 fixture cases stable"; stub-scan clean.
  • Marked [x]: YSD-14070.
  • Commit: resolve via git log --grep='YSD-14070'.

2026-07-21 — YSD-14071..14076 Rubric criteria for the remaining six dimensions (§14.3)#

  • Deliverable: on the PedagogicalRubricCriterion contract shipped for YSD-14070, a real, hand-authored, validated canonical criterion for each of the remaining six rubric dimensions — each with dimension-specific facets and ordered developing/proficient/exemplary level descriptors (qualitative bands, never a score), in examples.ts via mkRubricCriterion:
    • YSD-14071 observation-vs-interpretation — separating observation from interpretation and creator statement from inference.
    • YSD-14072 craft-vocabulary — appropriate craft vocabulary and taxonomy/context awareness.
    • YSD-14073 uncertainty-limitation-cultural — uncertainty, limitation, cultural context, and causal humility (separating causation from correlation).
    • YSD-14074 comparison-quality — comparison quality, inclusion logic, dissent, and counterexample selection.
    • YSD-14075 original-transfer — original transfer, transformation from principles, and protected/overly-specific-expression avoidance.
    • YSD-14076 revision-reflection — revision quality, reflection, and the ability to explain and defend decisions.
  • Verification: every criterion validates against PedagogicalRubricCriterionSchema in schema-publication.spec.ts (zod + published JSON Schema). A new rubric-criterion.spec.ts test asserts the canonical set ships a valid criterion covering ALL seven RUBRIC_DIMENSIONS (14070 anchor-accuracy through 14076 revision-reflection), each with ≥2 ordered levels and no score. Full study suite 39 files / 432 tests green; tsc -p tsconfig.lib.json clean; schema --check "88 contracts, no drift, no breaking"; golden --check "323 stable".
  • Marked [x]: YSD-14071, YSD-14072, YSD-14073, YSD-14074, YSD-14075, YSD-14076.
  • Commit: resolve via git log --grep='YSD-1407'.

2026-07-21 — YSD-19122 Signed third-party lens SDK package (§19 ecosystem)#

  • Contract (new): libs/contracts/src/study/entities/signed-lens-package.tsSignedLensPackage, the registration/sandboxing/governance record for a lens a third (or first) party publishes. The lens's declared capability manifest is the LensDefinition (YSD-9001), referenced by lensId + lensDefinitionVersion; this record adds the eleven SDK facets the item names. New branded id LensPackageId. Registered as SignedLensPackage. Reuses LensSourceType, LensCostClass, RightsAction, and StudyContentSignature.
  • Structural governance gates (YSD-0021):
    • signedsignature is required (an unsigned package is rejected);
    • least privilegegrantedPrivileges must be a subset of requestedPrivileges (a package can never be granted more than it asked);
    • sandbox — isolation + memory/cpu/wall ceilings + a coherent network policy (none ⇒ empty egress, allowlist ⇒ non-empty egress);
    • source/rights filterspermittedSourceTypes and permittedRightsActions narrow what reaches the lens;
    • output schemasoutputSchemaRefs (≥1);
    • costcostClass + maxCostUsdPerRun; observabilityrequiredObservabilitySignals (≥1);
    • conformance — a passed/failed result must record testedAt;
    • review — an approved package names reviewer + time, and a model cannot self-approve; revocation — a revoked package records when and why. packageIsUsable = approved AND conformance passed AND not revoked AND live — the gate a runtime checks before loading the lens.
  • Helpers (real, tested): grantedPrivilegesWithinRequested, packageIsRevoked, packageIsUsable.
  • Wiring: registered in schema-registry.ts, exported from index.ts, canonical (approved + pending) + structural negatives (incl. unsigned) + cross-field zodOnly (least-privilege, sandbox, conformance/review/revocation evidence, model self-approval). generate-study-schemas.ts published 91 artifacts (was 90); goldens 328. Non-route contract — client untouched.
  • Verification: dedicated signed-lens-package.spec.ts (8 tests) covers all gates. Full study suite 40 files / 440 tests green; tsc -p tsconfig.lib.json clean; schema --check "89 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "328 fixture cases stable"; stub-scan clean.
  • Marked [x]: YSD-19122.
  • Commit: resolve via git log --grep='YSD-19122'.

2026-07-21 — YSD-16001 (accessibility architecture policy)#

  • Item: §16.1 — Define WCAG target, supported assistive-technology matrix, browser/OS combinations, desktop baseline, accessible performance fallback, ownership, exception process, and release-blocking criteria.
  • Artifact: libs/contracts/src/study/accessibility-policy.ts — a machine-readable accessibility policy following the service-level-objectives.ts (YSD-17036) precedent: policy data drives the release gate rather than living only in prose. One AccessibilityPolicy object bundles the eight named facets:
    • WCAG targetWcagTargetSchema (version ∈ 2.0/2.1/2.2, level ∈ A/AA/AAA, plus individually-committed AAA success criteria by WCAG number);
    • AT matrixAssistiveTechnologySupportSchema[] (screen-reader / magnifier / voice-control / switch-access / braille × platform × support tier; a supported tier must name tested versions);
    • browser/OSBrowserSupportSchema[] (engine chromium/gecko/webkit + label + min version + platform + tier);
    • desktop baselineDesktopBaselineSchema (a supported baseline must name platforms and bind a native a11y API: UIA / NSAccessibility / AT-SPI);
    • performance fallbackAccessiblePerformanceFallbackSchema (preservesSemanticEquivalence is z.literal(true) — a lossy fallback cannot be declared accessible, YSD-16004);
    • ownership — accountable role + contact + escalation path;
    • exception process — approver, max duration, workaround requirement, review cadence, expiry requirement;
    • release-blocking criteria — blocking levels + severities + whether approved (and unexpired) exceptions unblock.
  • Structural + cross-field guards (real, tested): matrix must fully support ≥1 screen reader; blocking levels may not exceed the WCAG target (AAA blockable only when AAA criteria are committed); an unexpired-exception gate requires an expiry-mandating process.
  • Release gate (domain logic): releaseBlockingFindings / isReleaseBlocked compute the findings that block a candidate release — unfixed, target-level, blocking-severity findings not covered by an active approved exception; exceptionCovers correctly denies permanent (null-expiry) waivers under an unexpired-only gate and honors expiry against a passed-in nowIso (no Date.now() — deterministic). Canonical STUDY_ACCESSIBILITY_POLICY targets WCAG 2.2 AA with NVDA/JAWS/VoiceOver, a Chromium/Gecko/WebKit browser matrix, a Windows/macOS/Linux desktop baseline, and a dense-visualization → structured- table fallback.
  • Wiring: registered in schema-registry.ts (AccessibilityPolicy), exported from index.ts, examples block in examples.ts (2 canonical + 8 structural negatives + 5 cross-field zodOnly). generate-study-schemas.ts published 92 artifacts (was 91); goldens 330 (was 328). Non-route contract — client untouched.
  • Verification: dedicated accessibility-policy.spec.ts (13 tests) covers policy coherence, all three cross-field guards, the const true fallback guard, and the release gate (block/allow/exception/expired/permanent-waiver/disallowed). Full study suite 41 files / 453 tests green; tsc -p tsconfig.lib.json clean; schema --check "90 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "330 fixture cases stable"; stub-scan clean.
  • Marked [x]: YSD-16001.
  • Commit: resolve via git log --grep='YSD-16001'.

2026-07-21 — YSD-17001 (performance environment matrix + methodology)#

  • Item: §17.1 — Define representative hardware, browsers, networks, media, project sizes, cache states, source types, assistive settings, and measurement methodology for every performance budget.
  • Artifact: libs/contracts/src/study/performance-environment-matrix.ts — a machine-readable PerformanceEnvironmentMatrix bundling the named condition sets (HardwareProfile[], BrowserProfile[], NetworkProfile[], MediaProfile[], ProjectSizeProfile[], cacheStates, sourceTypes, assistiveSettings) plus a MeasurementMethodology (percentiles, minimum samples, warmup runs, cold+warm requirement, statistic, outlier policy, variance handling). So the CI/release performance gates (YSD-17012) measure against a fixed, documented matrix rather than ad-hoc laptops.
  • Structural + cross-field guards (real, tested): distinct ids per profile list; baselineHardwareId must reference a declared profile whose tier is baseline; exactly one profile may be the baseline tier; methodology percentiles must be strictly increasing.
  • Operational methodology (real statistics, not just data): percentileValue (linear-interpolation / type-7 estimator), applyOutlierPolicy (none / trim-min-max / iqr-trim with the 1.5·IQR fence), hasEnoughSamples (post-warmup gate), and reportableValue (discard warmup → apply outlier policy → percentile). A budget's pass/fail is therefore reproducible from raw samples. Canonical STUDY_PERFORMANCE_MATRIX: baseline mid-range laptop, low-end/high-end tiers, Chrome/Firefox/Safari, broadband/cable/4G, UHD/HD/game media, small→stress projects, p50/p75/p95 over ≥20 post-warmup runs with IQR trimming.
  • Wiring: registered in schema-registry.ts (PerformanceEnvironmentMatrix), exported from index.ts, examples block in examples.ts (2 canonical + 9 structural negatives + 5 cross-field zodOnly). generate-study-schemas.ts published 93 artifacts (was 92); goldens 332 (was 330). Non-route contract — client untouched.
  • Verification: dedicated performance-environment-matrix.spec.ts (10 tests) covers matrix coherence, all four cross-field guards, and known-value statistics (p50/p75/p90/p0/p1 of [10..50], trim-min-max, IQR trim, warmup drop, in-report outlier trim, minimum-sample gate). Full study suite 42 files / 463 tests green; tsc -p tsconfig.lib.json clean; schema --check "91 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "332 fixture cases stable"; stub-scan clean.
  • Marked [x]: YSD-17001.
  • Commit: resolve via git log --grep='YSD-17001'.

2026-07-21 — YSD-18042 (technical-metric acceptance criteria + gate)#

  • Item: §18.2 — Define pass thresholds, confidence intervals, minimum slice sizes, abstention policy, regression budgets, and owner sign-off for every technical metric.
  • Artifact: libs/contracts/src/study/evaluation-acceptance.ts — a MetricAcceptanceCriterion (registered) keyed by the §18.2 technical metrics (TECHNICAL_METRICS, 13 metrics from YSD-18030–18041), plus the canonical map STUDY_EVALUATION_ACCEPTANCE. Each criterion carries: passThreshold, confidenceLevel (0.9/0.95/0.99) + confidenceBound (lower/upper/point), minimumSliceSize, abstention (allowed + max rate), regressionBudget (absolute/relative), and ownerSignOffRole.
  • Structural + cross-field guards (real, tested): a proportion/rate threshold must be within [0, 1]; a magnitude metric must gate on the point estimate (no interval is computed); the compared bound must be the conservative one for the direction (higher-is-better → lower/point; lower-is-better → upper/point).
  • Operational gate (real statistics, not just data): wilsonInterval (two-sided Wilson score interval, clamped to [0,1]), zForConfidence (z lookup for the supported levels), and evaluateMetricpass / fail / insufficient-data. A slice below the minimum size is insufficient-data (never a silent pass); a scored slice passes only when the decision value (interval bound or point estimate) clears the threshold, the regression stays within budget, and abstentions stay within policy. Zero-tolerance criteria: rights-decision-correctness threshold 1 (point), permission-leakage-rate threshold 0 with abstention disallowed (YSD-18038).
  • Wiring: registered in schema-registry.ts (MetricAcceptanceCriterion), exported from index.ts, examples block in examples.ts (3 canonical + 9 structural negatives + 4 cross-field zodOnly). generate-study-schemas.ts published 94 artifacts (was 93); goldens 334 (was 332). Non-route contract.
  • Verification: dedicated evaluation-acceptance.spec.ts (15 tests) covers criterion coherence over all 13 metrics, all cross-field guards, known-value Wilson intervals (8/10 → [0.4902, 0.9433]; z=1.96), and the gate (insufficient-data / lower-bound fail 0.888 / lower-bound pass / zero-leakage / magnitude / regression-budget / abstention). Full study suite 43 files / 478 tests green; tsc -p tsconfig.lib.json clean; schema --check "92 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "334 fixture cases stable"; stub-scan clean.
  • Marked [x]: YSD-18042.
  • Commit: resolve via git log --grep='YSD-18042'.

2026-07-21 — YSD-18001 (gold-set governance policy + usability gate)#

  • Item: §18.1 — Establish governance for versioned gold sets, annotation guides, adjudication, legitimate disagreement, licenses, consent, privacy, retention, access, publication, and deletion.
  • Artifact: libs/contracts/src/study/gold-set-governance.ts — a registered GoldSetGovernancePolicy bundling eight governance sub-policies: versioning (published versions immutable, label change → new version — no silent post-hoc relabeling, YSD-18013), annotationGuide (required, min annotators), adjudication (method + legitimate-disagreement preserved, YSD-18010/18060), clearance (license + consent + privacy all mandatory as z.literal(true) — a policy cannot waive them), retention (max + review cadence, withdrawal always honored), access (tiers + default + export role), publication (internal-only default, external needs approval), and deletion (store boundaries, right-to-deletion honored). Canonical STUDY_GOLD_SET_GOVERNANCE.
  • Structural + cross-field guards (real, tested): deletion boundaries must include backups (a set recoverable from a backup is not deleted); review cadence must not exceed retention; the default access tier must be a declared tier; the expert-adjudicator method requires an adjudicator.
  • Usability gate (operational): goldSetUsableForEvaluation(policy, status){ usable, reasons[] }. A gold set may gate a release only when license, consent, and privacy are cleared, the annotation guide is present with enough annotators, adjudication is resolved, the set is un-withdrawn, and it is within retention — every failing condition is named (auditable refusal, mirrors the signed-lens-package packageIsUsable pattern).
  • Wiring: registered in schema-registry.ts (GoldSetGovernancePolicy), exported from index.ts, examples block in examples.ts (2 canonical + 8 structural negatives + 4 cross-field zodOnly). generate-study-schemas.ts published 95 artifacts (was 94); goldens 336 (was 334). Non-route contract.
  • Verification: dedicated gold-set-governance.spec.ts (11 tests) covers policy coherence, all cross-field guards, mandatory-clearance literals, and the usability gate (accept / missing-consent / withdrawn / past-retention / too-few-annotators / unresolved-adjudication / multi-reason). Full study suite 44 files / 489 tests green; tsc -p tsconfig.lib.json clean; schema --check "93 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "336 fixture cases stable"; stub-scan clean.
  • Marked [x]: YSD-18001.
  • Commit: resolve via git log --grep='YSD-18001'.

2026-07-21 — YSD-18003 (expert participation governance + eligibility gate)#

  • Item: §18.1 — Define expert conflict-of-interest, compensation, attribution, confidentiality, review, and withdrawal procedures.
  • Artifact: libs/contracts/src/study/expert-participation.ts — a registered ExpertParticipationPolicy bundling six sub-policies: conflictOfInterest (declaration required, disqualifying relations, cooling-off, financial-stake threshold), compensation (model + currency + paidRegardlessOfOutcome as z.literal(true) — pay is never contingent on reaching a judgment), attribution (namingRequiresConsent + optOutAllowed literals), confidentiality (NDA, embargo, prohibited disclosures), review (expert work reviewed + appeal), and withdrawal (allowedAnytime literal + data handling). Canonical STUDY_EXPERT_PARTICIPATION.
  • Structural + cross-field guards (real, tested): integrity literals cannot be waived (outcome-contingent pay, naming without consent, no-withdrawal all rejected structurally); a cooling-off requirement needs a positive window.
  • Eligibility gate (operational): expertIsEligible(policy, declaration){ eligible, reasons[] }. An expert is barred by any declared disqualifying relation, a financial stake at/above the threshold, or a cooling-off violation; a non-listed relation (e.g. personal-relationship, when the policy does not disqualify it) does not bar. Every barring reason is named.
  • Wiring: registered in schema-registry.ts (ExpertParticipationPolicy), exported from index.ts, examples block in examples.ts (2 canonical + 9 structural negatives + 1 cross-field zodOnly). generate-study-schemas.ts published 96 artifacts (was 95); goldens 338 (was 336). Non-route contract.
  • Verification: dedicated expert-participation.spec.ts (11 tests) covers integrity literals, cross-field guards, and the eligibility gate (clean / authored-source / at-threshold stake (5000) vs below (4999) / cooling-off / non-disqualifying relation / multi-reason). Full study suite 45 files / 499 tests green; tsc -p tsconfig.lib.json clean; schema --check "94 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "338 fixture cases stable"; stub-scan clean.
  • Marked [x]: YSD-18003.
  • Commit: resolve via git log --grep='YSD-18003'.

2026-07-21 — YSD-17100 (deployment / data-residency policy + placement gate)#

  • Item: §17.5 — Define environments, region/tenant data placement, object/database/index residency, model-provider routes, signing keys, backups, and deletion boundaries.
  • Artifact: libs/contracts/src/study/deployment-residency.ts — a registered DeploymentResidencyPolicy bundling: environments (name → served regions), storeResidency (per store class: tenant-region-only vs tenant-region-or-dr, encrypted-at-rest literal), disasterRecovery (region → DR-region pairs), modelRoutes (cross-region egress + approved routes), signingKeys (per-env isolated + rotation + reference literals), backups (residency scope + retention + encrypted literal), and deletionBoundaries (store classes). Canonical STUDY_DEPLOYMENT_RESIDENCY.
  • Structural + cross-field guards (real, tested): every store class must have exactly one residency rule; deletion boundaries must include object/relational/ backup; approved cross-region routes require egress enabled; a DR region must differ from its primary.
  • Placement gate (operational): placementSatisfiesResidency(policy, placement){ compliant, violations[] }. Refuses a store/backup outside the tenant's region (honoring the per-class DR scope via drRegionFor), a tenant region the environment does not serve, and an unapproved cross-region model route. Every violation is named — a cross-region leak is caught, not trusted.
  • Wiring: registered in schema-registry.ts (DeploymentResidencyPolicy), exported from index.ts, examples block in examples.ts (2 canonical + 8 structural negatives + 4 cross-field zodOnly). generate-study-schemas.ts published 97 artifacts (was 96); goldens 340 (was 338). Non-route contract.
  • Verification: dedicated deployment-residency.spec.ts (12 tests) covers policy coherence, all cross-field guards, and the placement gate (in-region / DR backup / DR-scoped store eu-ok-apac-no / tenant-region-only refusal / env-not-served / unapproved-route refusal / approved-route accept). Full study suite 46 files / 511 tests green; tsc -p tsconfig.lib.json clean; schema --check "95 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "340 fixture cases stable"; stub-scan clean.
  • Marked [x]: YSD-17100.
  • Commit: resolve via git log --grep='YSD-17100'.

2026-07-21 — YSD-15073 (engine-neutral game-session export + adapter packages)#

  • Item: §15.4 — Define engine-neutral game-session export plus approved Unreal, Unity, Godot, and product-specific adapter packages.
  • Artifact: libs/contracts/src/study/game-session-export.ts — two registered contracts: GameSessionExport (references-only, coordinates normalized to the neutral frame — both z.literal(true), so a lossy or un-normalized export is structurally rejected; ties to YSD-15075) and GameSessionExportAdapter (per-engine adapter descriptor: engine family, version, coordinate convention, supported capture classes/streams, conformance status, approved, detached signature). Canonical approved adapters STUDY_GAME_EXPORT_ADAPTERS for Unreal, Unity, Godot, and the V2 product runtime.
  • Real coordinate math (not a label): GameCoordinateConvention declares a signed right/up/forward basis + unit scale; coordinateTransformMatrix(from, to) composes the basis change through the canonical (glTF) frame with the unit-scale ratio (M = (scaleFrom/scaleTo)·R_to^T·R_from); toNeutralFrame normalizes a point; computedHandedness derives left/right from the determinant of the basis and the schema rejects a convention whose declared handedness disagrees with its basis (a mislabeled Unreal/Unity/Godot frame cannot ship). Known values: 100cm Unreal +X (forward) → neutral (0,0,-1)m; +Z (up) → (0,1,0); +Y (right) → (1,0,0).
  • Deliverability gate (operational): gameSessionExportIsDeliverable(export, adapters){ deliverable, reasons[] } refuses an export whose adapter is unknown, unapproved, non-conformant, engine-mismatched, or missing a declared stream. Adapter guard: approved requires conformanceStatus === 'passed'.
  • Scope note: this delivers the export FORMAT and the approved adapter PACKAGE contracts (with real coordinate normalization + conformance/approval gating). Runtime round-trip translation is exercised by the downstream §15.4 tests (YSD-15074 glTF, YSD-15076 game-envelope round-trip), which consume these contracts.
  • Wiring: registered in schema-registry.ts (GameSessionExport, GameSessionExportAdapter), exported from index.ts (renamed GameCoordinateConvention* to avoid the aja domain's CoordinateConvention), examples in examples.ts (adapter: 2 canonical + 8 negatives + 3 zodOnly; export: 1 canonical + 8 negatives). generate-study-schemas.ts published 99 artifacts (was 97); goldens 344 (was 340). Non-route contracts.
  • Verification: dedicated game-session-export.spec.ts (11 tests) covers handedness derivation, Unreal→neutral normalization known values, neutral-frame identity, mislabeled-handedness rejection, the literal-true guards, and the deliverability gate (deliver / unknown-adapter / engine-mismatch / unsupported-stream / unapproved). Full study suite 47 files / 522 tests green; tsc -p tsconfig.lib.json clean; schema --check "97 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "344 fixture cases stable"; stub-scan clean.
  • Marked [x]: YSD-15073.
  • Commit: resolve via git log --grep='YSD-15073'.

2026-07-21 — YSD-16002 (semantic interaction specifications)#

  • Item: §16.1 — Create semantic interaction specifications for player, timeline, annotation, comparison, inspiration canvas, graph, matrix, concept map, notebook, practice, dialogs, and live sessions before implementation.
  • Artifact: libs/contracts/src/study/semantic-interaction-spec.ts — a registered SemanticInteractionSpec (root ARIA role, focus model, keyboard interactions, live-region announcements, required accessible names, optional navigable textual equivalent, reduced-motion behavior) plus the canonical STUDY_INTERACTION_SPECS covering all 12 surfaces. The deliverable is the specifications themselves — authored before implementation, which YSD-16003 (primitives) and the surface components will consume.
  • Structural + cross-field guards (real, tested): keyboard chords must be distinct; a visualization surface (timeline/comparison/inspiration-canvas/ graph/matrix/concept-map) must declare a navigable textual equivalent (navigable: z.literal(true), YSD-16004) and a non-visualization surface must not; a dialog must trap focus; a matrix must use grid navigation.
  • Wiring: registered in schema-registry.ts (SemanticInteractionSpec), exported from index.ts, examples in examples.ts (3 canonical + 8 structural negatives + 5 cross-field zodOnly). generate-study-schemas.ts published 100 artifacts (was 99); goldens 346 (was 344). Non-route contract.
  • Verification: dedicated semantic-interaction-spec.spec.ts (8 tests) asserts a spec for every surface with distinct bindings, a navigable textual equivalent for every visualization (and none otherwise), the dialog focus-trap / matrix grid-navigation rules, and the duplicate-binding / non-navigable rejections. Full study suite 48 files / 530 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift, no breaking changes vs baseline v0"; golden --check "346 fixture cases stable"; stub-scan clean.
  • Marked [x]: YSD-16002.
  • Commit: resolve via git log --grep='YSD-16002'.

2026-07-21 — YSD-18030 (source-identity match-accuracy metrics)#

  • Item: §18.2 — Measure SourceWork and SourceEdition/build match accuracy, ambiguous-match handling, false merge, and keep-distinct accuracy.
  • Artifact: libs/contracts/src/study/evaluation-metrics.ts — pure functions over labeled pairwise decisions: confusionFromDecisions (2x2 confusion over scored decisions) and matchAccuracyMetrics → accuracy, matchRecall, matchPrecision, falseMergeRate (gold-distinct pairs wrongly merged), keepDistinctAccuracy, and abstentionRate. An ambiguous prediction is an abstention — excluded from the confusion matrix (never counted correct or wrong) and reported separately, so a model cannot inflate accuracy by hedging. Ratios return null (not a fabricated 0/1) when their denominator is zero.
  • Delivery-order coherence: these are the raw numbers the YSD-18042 acceptance criteria + Wilson-interval gate judge; the spec includes an integration test feeding a computed result straight into evaluateMetric (perfect 200-pair slice → pass; the 8-scored small slice → insufficient-data).
  • Wiring: exported from index.ts; pure-computation module, no zod schema, so no registry/goldens/client change (published contracts stay 98, goldens 346).
  • Verification: dedicated evaluation-metrics.spec.ts (5 tests) with known values (3 TP / 1 FN / 1 FP / 3 TN + 2 abstentions → accuracy 0.75, falseMerge 0.25, keepDistinct 0.75, abstention 0.2; keepDistinct + falseMerge = 1), null-ratio edge cases (empty / all-abstained / no-gold-distinct), and the 18042 pipeline. Full study suite 49 files / 535 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift, no breaking changes"; golden --check "346 fixture cases stable"; stub-scan clean.
  • Marked [x]: YSD-18030.
  • Commit: resolve via git log --grep='YSD-18030'.

2026-07-21 — YSD-18031 (boundary detection precision/recall with tolerance)#

  • Item: §18.2 — Measure scene, shot, beat, speaker, word, sound-event, pose, action, contact, gameplay-phase, and document-boundary precision/recall with tolerance definitions.
  • Artifact: libs/contracts/src/study/evaluation-metrics.ts (extended) — BOUNDARY_KINDS + BOUNDARY_TOLERANCES (the tolerance definitions: shot 0.5s, scene 2.0s, word 0.15s, contact 0.08s, document-boundary 0 exact, …), toleranceForBoundary, boundaryDetectionMetrics(predicted, gold, tolerance) and the convenience measureBoundaries(kind, …). Matches predicted to gold one-to-one within the tolerance window (each gold pairs with its nearest unmatched prediction); unmatched predictions are false positives (over-segmentation), unmatched gold are misses. Returns precision/recall/F1, null when a denominator is zero.
  • Verification: dedicated tests (5 added) with known values — within-tolerance matching (3 TP / 1 FP / 1 FN → P=R=F1=0.75), exact zero tolerance, one-to-one crowding (two predictions near one gold → 1 TP + 1 FP, recall 1, F1 2/3), null-precision / zero-recall edges, and per-kind tolerance application (0.3s off matches a shot but not a word). Full study suite 49 files / 540 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift, no breaking changes"; stub-scan clean.
  • Marked [x]: YSD-18031.
  • Commit: resolve via git log --grep='YSD-18031'.

2026-07-21 — YSD-18034 (confidence calibration metrics)#

  • Item: §18.2 — Measure confidence calibration, reliability curves, abstention, coverage, expected calibration error, and behavior under distribution shift rather than raw confidence magnitude.
  • Artifact: libs/contracts/src/study/evaluation-metrics.ts (extended) — calibrationMetrics(predictions, numBins=10) bins predictions into equal-width confidence bins and reports Expected Calibration Error (confidence-weighted mean of |accuracy − confidence| over non-empty bins), Max Calibration Error, the reliability curve (per-bin count / avg-confidence / accuracy), coverage (scored / total), and abstention. Abstained predictions are excluded from calibration and lower coverage — measuring calibration, never rewarding raw over-confidence. Invalid confidences (outside [0,1]) and bad bin counts throw; ECE is null when nothing was scored (not a fabricated 0).
  • Verification: dedicated tests (4 added) with known values — 5@0.9(80% acc)
    • 5@0.5(60% acc) → ECE 0.1, MCE 0.1, bin[0.9,1.0) accuracy 0.8; perfect calibration (conf 1 correct / conf 0 wrong) → ECE 0; abstention lowering coverage to 0.5; null-ECE and throw-on-bad-input edges. Full study suite 49 files / 544 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift, no breaking changes"; stub-scan clean.
  • Marked [x]: YSD-18034.
  • Commit: resolve via git log --grep='YSD-18034'.

2026-07-21 — YSD-18035 (claim, citation, and epistemic quality metrics)#

  • Item: §18.2 — Measure claim-to-anchor completeness, citation restore rate, unsupported-claim rate, epistemic misclassification, and creator-intent conflation.
  • Artifact: libs/contracts/src/study/evaluation-metrics.ts (extended) — claimSupportMetrics (completeness = claims with ≥1 anchor / total, plus the unsupported-claim rate), citationRestoreRate (resolved citations / total), and epistemicLabelingMetrics (overall misclassification rate plus a separate creatorIntentConflationRate). Conflation — passing a reader's interpretation off as the creator's stated intent, or the reverse — is counted distinctly because it is a more serious error than an ordinary type confusion. All ratios return null on empty input rather than a fabricated score.
  • Verification: dedicated tests (5 added) with known values — completeness 0.75 / unsupported 0.25 over 4 claims; citation restore 0.75; misclassification 0.75 with 2 of the errors being creator-intent conflations (rate 0.5); a correct creator-statement label is not conflation; completeness feeds the YSD-18042 claim-anchor-completeness gate (120 supported → pass). Full study suite 49 files / 549 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift, no breaking changes"; stub-scan clean.
  • Marked [x]: YSD-18035.
  • Commit: resolve via git log --grep='YSD-18035'.

2026-07-21 — YSD-18036 (cross-reference quality metrics by relation type)#

  • Item: §18.2 — Measure cross-reference precision, calibration, keep-distinct accuracy, explanation sufficiency, review workload, and false identity merge by relation type.
  • Artifact: libs/contracts/src/study/evaluation-metrics.ts (extended) — crossReferenceMetrics (precision, keep-distinct accuracy, false-identity-merge count + rate, explanation-sufficiency rate, review-workload rate) and crossReferenceMetricsByRelation which groups the same metrics per relation type so a weak relation kind (e.g. same-function) is not hidden by strong ones (keep-distinct). A false identity merge is asserting same-identity when the gold is distinct — the highest-severity cross-reference error. Calibration is covered by the shared calibrationMetrics.
  • Verification: dedicated tests (3 added) with known values — overall precision 0.75, keep-distinct 2/3, false-merge rate 0.25, explanation/review 0.5; per-relation breakdown isolating the same-function false merge (precision 0.5) from the clean keep-distinct relation (precision 1, keep-distinct 1); null ratios on empty. Full study suite 49 files / 552 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift, no breaking changes"; stub-scan clean.
  • Marked [x]: YSD-18036.
  • Commit: resolve via git log --grep='YSD-18036'.

2026-07-21 — YSD-18037 (source-to-decision path & traversal metrics)#

  • Item: §18.2 — Measure source-to-decision path completeness, broken-edge rate, impact-analysis precision/recall, stale dependency detection, and permission leakage through traversal.
  • Artifact: libs/contracts/src/study/evaluation-metrics.ts (extended) — pathCompletenessMetrics (completeness = paths with zero broken edges; broken- edge rate = broken edges / all edges), setImpactMetrics (set-based precision/recall/F1 of predicted-impacted vs gold-impacted decision ids, de-duplicated), staleDependencyMetrics (stale-detection recall + precision), and traversalLeakageMetrics (leaks + zeroLeakage flag — permission leakage through graph traversal must be exactly zero, ties to YSD-18038).
  • Verification: dedicated tests (4 added) with known values — completeness 2/3 with broken-edge rate 1/9; impact P=R=F1=2/3 with duplicate collapse; stale recall/precision 0.5/0.5; leakage flagged (rate 0.5, zeroLeakage false) and a clean/empty traversal set reporting zeroLeakage true. Full study suite 49 files / 556 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift, no breaking changes"; stub-scan clean.
  • Marked [x]: YSD-18037.
  • Commit: resolve via git log --grep='YSD-18037'.

2026-07-21 — YSD-18039 (rights enforcement quality metrics)#

  • Item: §18.2 — Measure rights-decision correctness, expiry/revocation timing, blocked-action coverage, consent enforcement, and audit completeness.
  • Artifact: libs/contracts/src/study/evaluation-metrics.ts (extended) — rightsDecisionMetrics (correctness rate plus wrongAllows surfaced separately — granting access that should be denied is the unsafe error class the zero-tolerance YSD-18042 criterion blocks on), blockedActionCoverage, postRevocationBlockRate (expiry/revocation timing — post-revocation accesses correctly blocked), consentEnforcementRate, and auditCompletenessRate.
  • Verification: dedicated tests (3 added) — correctness 0.5 with wrong-allow and wrong-deny separated; coverage / revocation / consent all 0.5; audit completeness 0.75; and the zero-tolerance gate integration (200 correct → pass; a single wrong-allow among 200 → fail under the point-estimate threshold of 1). Full study suite 49 files / 559 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift, no breaking changes"; stub-scan clean.
  • Marked [x]: YSD-18039.
  • Commit: resolve via git log --grep='YSD-18039'.

2026-07-21 — YSD-18040 (deletion completion & latency across stores)#

  • Item: §18.2 — Measure deletion completion and latency across object, relational, search, vector, graph, time-series, cache, export, local/offline, and model/intermediate stores.
  • Artifact: libs/contracts/src/study/evaluation-metrics.ts (extended) — DELETION_STORE_TARGETS (the 10 stores) and deletionMetrics(outcomes, latencyPercentile): overall + per-store completion rates and a per-store latency percentile (reusing the performance module's percentileValue), plus request-level settlement — a deletion request is settled only when every one of its store outcomes completed, so a single lagging store keeps the request unsettled and visible. Ratios return null on empty input.
  • Verification: dedicated tests (3 added) — the 10 store targets are enumerated; a mixed set gives overall completion 0.75, per-store object completion 1 with p95 latency 290 (interpolated over [100,300]) and relational 0.5, and request settlement 0.5 (only the request that reached both stores); a 40-request fully-settled set feeds the YSD-18042 deletion-latency-seconds gate → pass. Full study suite 49 files / 562 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift, no breaking changes"; stub-scan clean.
  • Marked [x]: YSD-18040.
  • Commit: resolve via git log --grep='YSD-18040'.

2026-07-21 — YSD-18041 (job reliability metrics)#

  • Item: §18.2 — Measure job success, retry recovery, idempotency, latency, cost, cancellation, partial success, stale-output, and duplicate-effect rates.
  • Artifact: libs/contracts/src/study/evaluation-metrics.ts (extended) — jobReliabilityMetrics(jobs, latencyPercentile): success rate, retry-recovery rate (recovered / retried), idempotency-violation rate, latency percentile, mean cost, and cancellation / partial-success / stale-output / duplicate-effect rates. Idempotency violations and duplicate effects are surfaced as their own rates because both must be zero — a retried job must never double-charge, double-notify, or emit a second output.
  • Verification: dedicated tests (3 added) — over a 10-job set: success 0.8, retry-recovery 2/3, idempotency/duplicate/cancellation/partial/stale all 0.1, latency p95 100, mean cost 0.5; null retry-recovery when nothing retried; and the YSD-18042 job-success-rate gate (500 perfect → pass, 450 → insufficient-data under the 500-sample minimum). Full study suite 49 files / 565 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift, no breaking changes"; stub-scan clean.
  • Marked [x]: YSD-18041. §18.2 technical metrics now substantially covered (18030/18031/18034/18035/18036/18037/18039/18040/18041 done; 18032/18033 remain).
  • Commit: resolve via git log --grep='YSD-18041'.

2026-07-21 — YSD-18032 (synchronization error, drift & gap detection)#

  • Item: §18.2 — Measure timecode, subtitle, commentary, transcript, proxy, and telemetry synchronization error plus drift/gap detection.
  • Artifact: libs/contracts/src/study/evaluation-metrics.ts (extended) — syncErrorMetrics(samples, percentile) reports the absolute sync error (mean/max/percentile of |measured − reference|) and the drift rate, estimated by least-squares regression of the signed offset against reference time — a nonzero slope is clock drift, distinct from a constant offset (the absolute error stays symmetric, the drift keeps its sign). detectGaps( timestamps, maxGapSeconds) sorts and reports spacings that exceed the maximum as coverage gaps (missing subtitles/telemetry).
  • Verification: dedicated tests (4 added) — offset growing 0.01s/reference-s gives mean error 0.25, max 0.4, p95 0.385, drift slope 0.01 with intercept 0.1; single-sample drift is null; gap detection over unsorted input finds the one 3-second gap and rejects a negative window; the p95 error feeds the YSD-18042 sync-error-seconds gate (40 tight samples → pass). Full study suite 49 files / 569 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift, no breaking changes"; stub-scan clean.
  • Marked [x]: YSD-18032. §18.2 technical metrics: 10 of 12 done — only YSD-18033 (tracking/pose/transcript/OCR/source-separation/retrieval by slice) remains.
  • Commit: resolve via git log --grep='YSD-18032'.

2026-07-22 — YSD-18033 (tracking, region/mask, pose, transcript, OCR, separation & retrieval quality by slice)#

  • Item: §18.2 — Measure tracking stability, region/mask accuracy, pose error, transcript error, OCR error, source-separation quality, and retrieval relevance by slice.
  • Artifact: libs/contracts/src/study/evaluation-metrics.ts (extended, pure functions — no schema/registry/goldens) implementing the seven SOTA families:
    • Region/mask: boxIoU, maskIoU, diceCoefficient, and regionAccuracyMetrics (mean IoU/Dice + fraction clearing an IoU threshold). Empty-gold∧empty-pred scores as perfect agreement (1), the standard 0/0 segmentation convention.
    • Tracking: trackingMetrics — CLEAR-MOT accounting over a temporally ordered frame sequence: misses, false positives, identity switches (per-track predicted-id change), fragmentations (resumption of an established track), MOTA, MOTP, and a bounded trackingStability proportion.
    • Pose: poseErrorMetrics — MPJPE (mean per-joint Euclidean error over visible joints) and PCK (percentage of correct keypoints within a per-pose-scaled threshold); euclideanDistance helper.
    • Transcript/OCR: editDistanceOps (Levenshtein with S/D/I backtrace), wordErrorRate (WER), characterErrorRate (CER — edits and denominator both in Unicode code points).
    • Source separation: scaleInvariantSdr (SI-SDR in dB via projection of the estimate onto the reference; +∞ perfect, −∞ orthogonal) and separationQualityMetrics (mean over finite ratios, perfect/silent counted separately).
    • Retrieval: discountedCumulativeGain / normalizedDcg (exponential gain 2^rel−1, log2(rank+1) discount; rejects negative grades) and retrievalRelevanceMetrics (mean nDCG, MRR, precision@k).
    • By slice: generic bySlice(items, metric) groups any family's evals (or tracking frames) by an optional slice label so a hard cohort cannot be masked by an easy majority.
  • Verification: 15 known-value tests added (evaluation-metrics.spec.ts, file now 54 tests): box IoU 1/7 and mask IoU 0.4 / Dice 20/35; tracking fixture with an id-switch, a miss, an FP and a resumption → MOTA 0.625, MOTP 0.85, stability 0.75; MPJPE 5 and PCK 2/3 over joints at distance {0,5,10}; WER 0.5 (the quick brown foxthe quik brown) and CER 0.2 (hellohelo), astral CER 1/3 for 😀ab😀ax; SI-SDR ≈19.168 dB and scale invariance to +∞; nDCG 0.94881 on the graded example [3,2,3,0,1,2], MRR and precision@k; by-slice keeps a night-slice accuracy 0 visible next to a daylight-slice accuracy 1. The stability proportion feeds the YSD-18042 tracking-stability gate (60-frame perfect slice → pass; 40 < minimum 50 → insufficient-data, never a silent pass). Full study suite 49 files / 584 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift, no breaking changes"; goldens --check "346 fixture cases stable"; stub-scan clean. A read-only Codex review flagged three edge cases (mask 0/0 convention, CER Unicode denominator units, nDCG negative grades) — all fixed with dedicated tests before marking.
  • Marked [x]: YSD-18033. §18.2 technical-quality metrics now COMPLETE (all of 18030–18042). evaluation-metrics.ts holds the eleven metric families.
  • Commit: resolve via git log --grep='YSD-18033'.

2026-07-22 — YSD-18060 (inter-rater agreement vs disagreement coverage)#

  • Item: §18.3 — Measure inter-rater agreement only where a shared correct answer is meaningful, and measure disagreement coverage/context where it is not.
  • Artifact: libs/contracts/src/study/human-quality-metrics.ts (new pure-fn module, no schema/registry/goldens; exported from the study index) with the YSD-18060 epistemic partition:
    • Reliability coefficients (objective items): cohensKappa (two raters, chance-corrected), fleissKappa (fixed rater count, many items), krippendorffAlpha (nominal metric via a coincidence matrix — tolerates any rater count and missing data), and percentAgreement (raw pairwise).
    • Disagreement measures (contested items): disagreementCoverageMetricspositionCoverage (fraction of an item's declared legitimate positions actually voiced, so a minority reading is not silenced by the majority), documentationRate (disagreement recorded with rationale), and genuineDisagreementRate. Contested items are never scored as reliability error.
    • raterAgreementMetrics partitions items on hasSharedCorrectAnswer and applies agreement statistics ONLY to the objective partition and coverage/context ONLY to the contested partition — encoding the checklist's core requirement structurally. Cohen's/Fleiss' kappa are emitted only when their preconditions hold (two complete raters; a uniform rater count), never fabricated from ill-fitting data; a coefficient that is undefined (single category, no pairable data) returns null.
  • Verification: 8 known-value tests (human-quality-metrics.spec.ts): Cohen's kappa 0.4 from a 2x2 table (po 0.7, pe 0.5); Fleiss' kappa 0.55 for 3 raters over items (3a),(3b),(2a,1b) (P̄ 7/9, P̄e 41/81, κ 22/40); Krippendorff's alpha 0.53333 for the (a,a),(a,b),(b,b),(b,b) coincidence case (Do 0.25, De 30/56); perfect-agreement → 1; single-category/empty → null; uneven Fleiss counts throw; and the objective/contested partition (agreement over the 3 fact items, coverage 7/12 and documentation 0.5 over the 2 interpretive items). Full study suite 50 files / 592 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift"; goldens --check "346 fixture cases stable"; stub-scan clean. A read-only Codex review confirmed the Cohen's-kappa and Krippendorff's-alpha math (including missing-data / variable-rater handling) and flagged one robustness gap — fleissKappa would silently drop an undeclared category from its totals — now fixed to throw on undeclared categories, with a test.
  • Marked [x]: YSD-18060. First §18.3 human-centered metric; opens the human-quality-metrics.ts module for the rest of §18.3.
  • Commit: resolve via git log --grep='YSD-18060'.

2026-07-22 — YSD-18061 (learner epistemic-distinction ability)#

  • Item: §18.3 — Measure learner ability to distinguish source fact, detection, observation, interpretation, (craft) hypothesis, creator statement, and practice result.
  • Artifact: libs/contracts/src/study/human-quality-metrics.ts (extended) — epistemicDistinctionMetrics(items) scores a learner's classification of items into the seven EPISTEMIC_OBJECT_TYPES (reused from epistemic-object-types.ts, not redefined): overall accuracy, chance-corrected agreement with the gold labels (Cohen's kappa, reusing cohensKappa), macro-F1 averaged over the gold-present types (a missed type scores 0, never dropped, so misses cannot be averaged away), and a full per-type precision/recall/F1 confusion breakdown. The study-specific core is three named consequential confusions via crossGroupConfusionRate: interpretation↔creator-statement (whose claim is this — the highest-stakes conflation, mirroring YSD-18035), observation↔interpretation (the YSD-14071 rubric distinction), and evidence↔inference (source-fact/detection vs the five inferential/authored types). These named confusions are what make it a study-workspace metric rather than a renameable generic classifier score.
  • Verification: 4 known-value tests (human-quality-metrics.spec.ts, file now 12 tests) over a 9-item fixture (6 correct + obs→interp, interp→creator, creator→interp): accuracy 6/9; macro-F1 0.72381 (per-class F1s 1,1,0.667,0.4,1,0,1); interpretation P 1/3, R 1/2, F1 0.4; creator-statement F1 0 (never correctly found); observation F1 2/3; interpretation↔creator-statement conflation 2/3; observation↔interpretation 1/4; evidence↔inference 0; Cohen's kappa non-null and strictly below raw accuracy (chance-corrected); a perfect learner scores 1/1/1 with 0 conflation; an empty set is all-null. Full study suite 50 files / 596 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift"; goldens --check "346 fixture cases stable"; stub-scan clean. A read-only Codex review of the confusion-matrix, macro-F1 convention, and cross-group confusion logic found no bugs.
  • Marked [x]: YSD-18061.
  • Commit: resolve via git log --grep='YSD-18061'.

2026-07-22 — YSD-18062 (improvement across attempts)#

  • Item: §18.3 — Measure improvement in source-based analysis, contextual humility, counterexample use, original transfer, revision, and explanation across attempts.
  • Artifact: libs/contracts/src/study/human-quality-metrics.ts (extended) — IMPROVEMENT_DIMENSIONS (the six named craft axes) and improvementMetrics(scores, maxScore), which per dimension reports: Hake's normalized learning gain g = (post − pre) / (max − pre) averaged over learners whose pre-score left room to grow (a ceiling pre-score is excluded, not counted as zero improvement), the fraction of learners who improved, the mean per-learner least-squares trajectory slope (growth shape, not just endpoints), the mean raw score change, and the pre→post Cohen's d effect size (standardized mean difference, pooled SD). A learner needs at least two attempts for change to be measurable; single-attempt learners are excluded.
  • Verification: 2 known-value tests (human-quality-metrics.spec.ts, file now 14 tests) over a two-learner 0–4 rubric fixture (L1 1→2→3, L2 2→2→4): mean normalized gain 5/6 (2/3 and 1); improvement rate 1; mean slope 1; mean delta 2; Cohen's d 2/√0.5 ≈ 2.8284 (pre [1,2] mean 1.5, post [3,4] mean 3.5, pooled SD √0.5); empty dimensions all-null; single-attempt and ceiling learners correctly excluded from gain. Full study suite 50 files / 598 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift"; goldens --check "346 fixture cases stable"; stub-scan clean; eslint 0 errors. A read-only Codex review confirmed the Hake-gain, least-squares slope, sample-variance (n−1), and pooled-SD Cohen's d math with no bugs.
  • Marked [x]: YSD-18062.
  • Commit: resolve via git log --grep='YSD-18062'.

2026-07-22 — YSD-18063 (teacher-rated feedback quality)#

  • Item: §18.3 — Measure teacher-rated usefulness, specificity, actionability, contestability, cultural safety, and imitation pressure of feedback.
  • Artifact: libs/contracts/src/study/human-quality-metrics.ts (extended) — FEEDBACK_QUALITY_DIMENSIONS, FEEDBACK_DIMENSION_POLARITY (five higher-is-better; imitation-pressure is a harm signal, lower-is-better), and feedbackQualityMetrics(ratings, bar). The bar carries a floor for the positive dimensions and an imitationPressureCeiling. Per dimension it reports the mean rating, polarity, and the polarity-aware pass rate. It then aggregates per feedback item (mean teacher rating per dimension) into a composite overallAcceptabilityRate — an item is acceptable only if it was rated on all six dimensions and passes each, so a gap cannot certify by omission — and surfaces imitationPressureConcernRate on its own so a copying-pressure problem is never averaged into an otherwise strong score.
  • Verification: 3 known-value tests (human-quality-metrics.spec.ts, file now 17 tests) over a two-item fixture (floor 3, ceiling 2, 1–5 scale): F1 passes all six, F2 fails usefulness/contestability/imitation-pressure → usefulness mean 3 pass-rate 0.5, specificity pass-rate 1, cultural-safety mean 4.5, imitation-pressure mean 2.5 pass-rate 0.5 (lower-is-better), overallAcceptabilityRate 0.5, imitationPressureConcernRate 0.5; a partially rated item cannot be certified (rate 0) and an empty set is null. Full study suite 50 files / 601 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift"; goldens --check "346 fixture cases stable"; stub-scan clean; eslint 0 errors. A read-only Codex review confirmed the polarity handling, composite per-item gate, and imitation- pressure concern rate with no bugs.
  • Marked [x]: YSD-18063.
  • Commit: resolve via git log --grep='YSD-18063'.

2026-07-22 — YSD-18064 (evidence-task time and success rate)#

  • Item: §18.3 — Measure time and success rate to find, inspect, compare, and cite a useful exact moment/region/event.
  • Artifact: libs/contracts/src/study/human-quality-metrics.ts (extended) — CITATION_TASK_KINDS (find, inspect, compare, cite) and a generic taskPerformanceMetrics(attempts, kinds, percentile) (reused later for YSD-18065). Per task kind it reports attempts, successes, success rate, and the median and upper-percentile (default p90) time among the successful attempts — abandoned attempts stop at an arbitrary point, so their durations are not a comparable time-on-task and are excluded — plus an overall success rate across the reported kinds. Kinds are reported explicitly so a never-attempted task surfaces with a null rate rather than vanishing. Reuses the reviewed percentileValue (type-7 interpolation).
  • Verification: 2 known-value tests (human-quality-metrics.spec.ts, file now 19 tests): find 2/3 success with median-to-success 15 and p90 19 (over [10,20], the failed 99s ignored); inspect success 1 with median 10 and p90 14 (over [5,15]); a failed-only compare has rate 0 and null time; a never-attempted cite shows null; overall 4/6; negative durations and out-of-range percentiles throw; custom kinds and a p50 request are honored. Full study suite 50 files / 603 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift"; goldens --check "346 fixture cases stable"; stub-scan clean; eslint 0 errors.
  • Marked [x]: YSD-18064. Time-on-task over successes is the standard usability convention (abandoned-attempt times are not comparable).
  • Commit: resolve via git log --grep='YSD-18064'.

2026-07-22 — YSD-18065 (inspiration-system & decision-trace workflows)#

  • Item: §18.3 — Measure time and success rate to organize multiple works into an explainable inspiration system and trace a decision back to exact regions.
  • Artifact: libs/contracts/src/study/human-quality-metrics.ts (extended) — INSPIRATION_TASK_KINDS (organize-inspiration-system, trace-decision) and inspirationWorkflowMetrics(attempts, percentile). It reuses the shared taskPerformanceMetrics engine (YSD-18064) for per-workflow time and success rate, and adds the quality dimension the item's wording demands: per workflow, the fraction of successful attempts that also met their quality bar — an explainable organization, or a trace that reached the exact region rather than an approximate neighbourhood. Quality is scored only over successes, because an incomplete workflow has no organization to explain or trace to check; completing a task is deliberately kept separate from doing it well.
  • Verification: 2 known-value tests (human-quality-metrics.spec.ts, file now 21 tests): organize 2/3 success with median-to-success 90 and p90 114, of which only 1 of 2 was explainable (quality 0.5); trace 1.0 success with median 35, both reaching the exact region (quality 1); overall 0.8; a failed-only workflow reports null quality. Full study suite 50 files / 605 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift"; goldens --check "346 fixture cases stable"; stub-scan clean; eslint 0 errors.
  • Marked [x]: YSD-18065.
  • Commit: resolve via git log --grep='YSD-18065'.

2026-07-22 — YSD-18066 (discovery diversity)#

  • Item: §18.3 — Measure ability to discover relevant contrasts, distant analogies, bridge references, and missing perspectives — not only close visual matches.
  • Artifact: libs/contracts/src/study/human-quality-metrics.ts (extended) — DISCOVERY_KINDS (close-visual-match plus the four valuable kinds), VALUABLE_DISCOVERY_KINDS, and discoveryDiversityMetrics(discoveries, goldRelevantByKind). Per kind it reports retrieved/relevant/gold counts with recall and precision. The headline signals penalize a near-duplicate-only system even at perfect close-match recall: beyondVisualDiscoveryRate (relevant discoveries that are not close matches / all relevant), valuableKindCoverage (of the four valuable kinds, the fraction with at least one relevant discovery), and distributionEntropy (normalized Shannon entropy of the relevant-discovery kind distribution over all five kinds, 0 when concentrated on one kind, 1 when balanced).
  • Verification: 2 known-value tests (human-quality-metrics.spec.ts, file now 23 tests): a fixture of [close 4(+1 irrelevant), contrast 2, distant 2, bridge 2, missing 0] against gold [4,4,2,2,2] gives close recall 1 / precision 0.8, contrast recall 0.5, missing recall 0 with null precision, beyond-visual 0.6, valuable-kind coverage 0.75, and normalized entropy 0.827729 (pinned to a hand-computed literal, independent of the formula); an all-close-match system scores beyond-visual 0, coverage 0, entropy 0; an empty set is null. Full study suite 50 files / 607 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift"; goldens --check "346 fixture cases stable"; stub-scan clean; eslint 0 errors. A read-only Codex review confirmed the per-kind recall/precision, beyond-visual rate, valuable-kind coverage, and normalized-entropy math with no bugs.
  • Marked [x]: YSD-18066.
  • Commit: resolve via git log --grep='YSD-18066'.

2026-07-22 — YSD-18067 (accessibility critical-journey performance)#

  • Item: §18.3 — Measure critical-journey completion, error recovery, cognitive load, and satisfaction with keyboard, screen reader, magnification, captions, reduced motion, switch access, and alternative views.
  • Artifact: libs/contracts/src/study/human-quality-metrics.ts (extended) — ACCESSIBILITY_MODES (the seven modes) and accessibilityJourneyMetrics. Per mode it reports completion rate, error-recovery rate (recovered/encountered, null when no errors arose — nothing to recover from, never a failure), mean cognitive load (lower is better), and mean satisfaction. The headline equity signals — worstModeCompletionRate (the parity floor no mode should fall below) and completionParityGap (best minus worst; 0 is full parity) — surface a lagging mode that an average would hide. Rejects impossible input (recovered > encountered).
  • Verification: 3 known-value tests (human-quality-metrics.spec.ts, file now 26 tests): keyboard completion 1, recovery 0.75, load 50, satisfaction 75; screen-reader completion 0.5, recovery 0.5, load 80; overall 0.75; worst-mode 0.5 and parity gap 0.5 (screen reader lagging); a never-run mode is null; a no-error mode has null recovery; recovered-exceeds-encountered throws; empty is null. Full study suite 50 files / 610 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift"; goldens --check "346 fixture cases stable"; stub-scan clean; eslint 0 errors.
  • Marked [x]: YSD-18067. Parity floor/gap are the equity signals an average completion rate hides.
  • Commit: resolve via git log --grep='YSD-18067'.

2026-07-22 — YSD-18068 (cultural-reviewer harm assessment)#

  • Item: §18.3 — Measure cultural-reviewer assessment of stereotyping, context loss, taxonomy transfer, universalism, harassment risk, and missing counterreadings.
  • Artifact: libs/contracts/src/study/human-quality-metrics.ts (extended) — CULTURAL_REVIEW_DIMENSIONS (the six harms) and culturalReviewMetrics. Every dimension is negative (higher severity = worse). Per dimension it reports the mean severity and the flag rate (reviews at or above the concern threshold). The per-item concern gate is precautionary: an item is a concern if ANY reviewer flags ANY harm at or above threshold — a single credible reviewer surfacing a serious harm is not outvoted by others who missed it. It also reports the overall concern and clean-item rates and names the most-prevalent harm(s) (ties included) so remediation can target the worst dimension.
  • Verification: 2 known-value tests (human-quality-metrics.spec.ts, file now 28 tests): a two-item fixture (I1 missing-counterreadings 2, I2 stereotyping 3) gives stereotyping mean 1.5 / flag 0.5, context-loss flag 0, missing-counterreadings flag 0.5, overall concern 1, clean 0, and most-flagged ['stereotyping', 'missing-counterreadings'] (tie in declared order); a fully-clean item yields concern 0 / clean 1 / no most-flagged; negative severity throws; empty is null. Full study suite 50 files / 612 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift"; goldens --check "346 fixture cases stable"; stub-scan clean; eslint 0 errors.
  • Marked [x]: YSD-18068. Precautionary any-reviewer-any-harm gate — harms are not majority-voted away.
  • Commit: resolve via git log --grep='YSD-18068'.

2026-07-22 — YSD-18069 (recommendation narrowing)#

  • Item: §18.3 — Measure whether recommendations narrow projects toward one famous work, culture, style, period, medium, provider, or derivative cluster.
  • Artifact: libs/contracts/src/study/human-quality-metrics.ts (extended) — RECOMMENDATION_AXES (the seven narrowing axes) and recommendationNarrowingMetrics. Per axis it reports the recommendation count, distinct values, the top value's share (the "narrowing toward one X" signal), the Herfindahl–Hirschman index (Σpᵢ², 1 = single value, 1/n = spread), and the normalized Shannon entropy (0 concentrated, 1 evenly spread). It flags narrowingAxes — those whose top-share exceeds the threshold — so a homogenizing recommender is caught on the specific axis it collapses.
  • Verification: 2 known-value tests (human-quality-metrics.spec.ts, file now 30 tests): a four-recommendation fixture gives culture top-share 0.75, HHI 0.625, entropy 0.811278 (pinned literal); style fully concentrated (top-share 1, HHI 1, entropy 0); period an even 2-2 split (top-share 0.5, entropy 1); an untagged axis is null; narrowingAxes = ['culture', 'style'] (period's exact 0.5 does not exceed the strict threshold); empty narrows nothing; out-of-range threshold throws. Full study suite 50 files / 614 tests green; tsc -p tsconfig.lib.json clean; schema --check "98 contracts published, no drift"; goldens --check "346 fixture cases stable"; stub-scan clean; eslint 0 errors.
  • Marked [x]: YSD-18069.
  • Commit: resolve via git log --grep='YSD-18069'.

2026-07-22 — YSD-18070 (study pre-registration protocol)#

  • Item: §18.3 — Pre-register study questions, sampling, metrics, analysis, and stopping rules where practical and publish limitations rather than overclaiming learning outcomes.
  • Artifact: libs/contracts/src/study/study-preregistration.ts — a new registered zod policy contract (99th published contract), NOT a metric. This is a pre-registration protocol shaped like the §18.1 governance contracts. StudyPreRegistrationSchema fixes questions, a sampling plan, a metric plan (primary/secondary, disjoint), a pre-specified analysis plan, and stopping rules before data; requires at least one published limitation; carries the integrity literals overclaimingProhibited, analysis.prespecified, and stoppingRules.fixedBeforeData (all z.literal(true)); and cross-field refines that data collection cannot precede registration and that the status agrees with the amendment log. The operational gate resultsRespectPreRegistration(registration, claim) enforces the anti-overclaiming rule: it flags a claimed metric that was never pre-registered (HARKing), missing published limitations, a sample exceeding the pre-registered stopping cap, and a causal learning-outcome claim beyond a non-causal-capable design (only randomized-controlled and quasi-experimental are causal-capable). Registered in schema-registry + index; examples added to examples.ts (canonical + amended, nine structural negatives, four cross-field zod-only negatives); schemas and goldens regenerated.
  • Verification: dedicated spec (9 tests) covering the canonical template, each integrity literal, the data-before-registration refusal, status/amendment consistency, the primary/secondary disjointness, and every gate branch (compliant claim, HARKing metric, missing limitations, stopping-cap breach, and causal-overclaim vs the same design's permitted association claim). The schema-publication spec (7 tests) validates the new canonical examples against zod + ajv, the structural negatives failing both and the cross-field negatives failing zod only. Full study suite 51 files / 623 tests green; tsc -p tsconfig.lib.json clean; schema --check "99 contracts published, no drift, no breaking changes"; goldens --check "348 fixture cases stable"; stub-scan clean; eslint 0 errors. A read-only Codex review flagged two real issues, both fixed and retested: ISO timestamps are now validated as datetimes and compared with Date.parse (raw-string comparison misordered across precisions/offsets), and the gate now requires every pre-registered limitation to be published (not merely a non-empty array), with a partial-coverage test added.
  • Marked [x]: YSD-18070. §18.3 human-centered quality metrics now COMPLETE (18060–18070).
  • Commit: resolve via git log --grep='YSD-18070'.

2026-07-22 — YSD-18080 (privacy-safe activation metrics)#

  • Item: §18.4 — Instrument privacy-safe time to first authorized evidence anchor and first completed study card.
  • Artifact: libs/contracts/src/study/product-metrics.ts (new pure-fn module opening §18.4; exported from the study index) — activationMetrics(records, kAnonymityThreshold, percentile) reports, per milestone (first anchor, first study card), the reach rate and the median / upper-percentile time among the projects that reached it. Time is measured only over reachers (a project still working toward the milestone has no meaningful duration). Small-cohort suppression is the privacy safeguard: a cohort below the k-anonymity threshold withholds every derived value (suppressed), and even within a large enough cohort the time percentiles are withheld when fewer than k projects reached the milestone — a percentile over a handful of learners could re-identify them.
  • Verification: 4 known-value tests (product-metrics.spec.ts) over a five-project fixture (k=3): anchor reach 0.8 with median 250 and p90 370 (over [100,200,300,400]); study-card reach 0.4 shown but time withheld (only 2 reachers < k); a two-project cohort fully suppressed (all null); negative times and invalid k/percentile throw. Full study suite 52 files / 627 tests green; tsc -p tsconfig.lib.json clean; schema --check "99 contracts published, no drift"; goldens --check "348 fixture cases stable"; stub-scan clean; eslint clean.
  • Marked [x]: YSD-18080. Small-cohort suppression makes the metric privacy-safe by construction.
  • Commit: resolve via git log --grep='YSD-18080'.

2026-07-22 — YSD-18081 (weekly engagement & project composition)#

  • Item: §18.4 — Instrument weekly active study projects, returning-learner annotations, and interpretation-bearing versus source-only projects.
  • Artifact: libs/contracts/src/study/product-metrics.ts (extended) — engagementCompositionMetrics(records, kAnonymityThreshold) reports weekly active study projects, the returning-learner annotation volume across active projects and the fraction of active projects with any such annotation, and the interpretation-bearing vs source-only split (the epistemic-depth signal that a study population is moving past source capture into interpretation). Derived rates are withheld when the active cohort is below the k-anonymity threshold; the headline active-project count is still reported.
  • Verification: 2 known-value tests (product-metrics.spec.ts, file now 6 tests): four active projects with 3 returning-learner annotations, a returning-learner project rate of 0.5, an interpretation-bearing rate of 0.5 complemented by a source-only rate of 0.5 (an inactive project excluded); a two-project cohort has its rates suppressed while the count is kept; negative annotation counts throw. Full study suite 52 files / 629 tests green; tsc -p tsconfig.lib.json clean; schema --check "99 contracts published, no drift"; goldens --check "348 fixture cases stable"; stub-scan clean; eslint 0 problems.
  • Marked [x]: YSD-18081.
  • Commit: resolve via git log --grep='YSD-18081'.

2026-07-22 — YSD-18082 (progression funnel)#

  • Item: §18.4 — Instrument the proportion of projects reaching a comparison, exercise attempt, committed decision, or outcome review.
  • Artifact: libs/contracts/src/study/product-metrics.ts (extended) — STUDY_FUNNEL_STAGES (comparison, exercise-attempt, committed-decision, outcome-review) and funnelReachMetrics(projects, kAnonymityThreshold), which reports the proportion of projects reaching each milestone, independently per stage, with small-cohort suppression withholding the reach rates below the k-anonymity threshold while keeping the counts.
  • Verification: 2 known-value tests (product-metrics.spec.ts, file now 8 tests): a five-project fixture gives comparison 0.8, exercise-attempt 0.6, committed-decision 0.4, outcome-review 0.2; a two-project cohort has its reach rates suppressed (counts kept); an invalid k throws. Full study suite 52 files / 631 tests green; tsc -p tsconfig.lib.json clean; schema --check "99 contracts published, no drift"; goldens --check "348 fixture cases stable"; stub-scan clean; eslint 0 problems.
  • Marked [x]: YSD-18082.
  • Commit: resolve via git log --grep='YSD-18082'.

2026-07-22 — YSD-18083 (persona-segmented retention)#

  • Item: §18.4 — Instrument returning-learner rate at one, four, and twelve weeks segmented by persona without exposing sensitive study content.
  • Artifact: libs/contracts/src/study/product-metrics.ts (extended) — retentionMetrics(learners, kAnonymityThreshold) computes the returning-learner rate at 1, 4, and 12 weeks per persona and overall. The input carries only a coarse persona label and per-horizon return booleans — no study content — and any persona cohort below the k-anonymity threshold has its rates withheld, so a segment can never narrow onto one learner.
  • Verification: 2 known-value tests (product-metrics.spec.ts, file now 10 tests): a novice cohort of 4 gives week-1/4/12 rates 0.75/0.5/0.25; an expert cohort of 2 is suppressed (rates null); the persona-agnostic overall over 6 learners is 5/6, 4/6, 3/6; an invalid k throws. Full study suite 52 files / 633 tests green; tsc -p tsconfig.lib.json clean; schema --check "99 contracts published, no drift"; goldens --check "348 fixture cases stable"; stub-scan clean; eslint 0 problems.
  • Marked [x]: YSD-18083. Persona-only segmentation plus per-segment small-cohort suppression keeps retention reporting free of study content and re-identification.
  • Commit: resolve via git log --grep='YSD-18083'.

2026-07-22 — YSD-18084 (project transfers & principle usefulness)#

  • Item: §18.4 — Instrument the share of studies producing project transfers and learner-reported usefulness of transferred principles.
  • Artifact: libs/contracts/src/study/product-metrics.ts (extended) — transferMetrics(studies, kAnonymityThreshold) reports the share of studies producing a project transfer and the mean learner-reported usefulness of transferred principles. Both derived values apply their own small-cohort suppression: the production rate is withheld below the study threshold, and the usefulness mean below the rating threshold, so neither can expose an individual.
  • Verification: 2 known-value tests (product-metrics.spec.ts, file now 12 tests): five studies with a 0.6 transfer share and mean usefulness 4.0 (over [4,5,3,4]); a two-study cohort suppresses the rate; a three-study cohort with only two ratings keeps the rate 2/3 but withholds the usefulness mean; a negative rating throws. Full study suite 52 files / 635 tests green; tsc -p tsconfig.lib.json clean; schema --check "99 contracts published, no drift"; goldens --check "348 fixture cases stable"; stub-scan clean; eslint 0 problems.
  • Marked [x]: YSD-18084.
  • Commit: resolve via git log --grep='YSD-18084'.

2026-07-22 — YSD-18085 (product-analytics privacy policy)#

  • Item: §18.4 — Add consent, minimization, retention, deletion, role access, small-cohort suppression, and no-dark-pattern rules to product analytics.
  • Artifact: libs/contracts/src/study/product-analytics-policy.ts — a new registered zod policy contract (the 100th published contract). ProductAnalyticsPolicySchema fixes all seven rule areas: consent (required + withdrawable literals, granularity), minimization (declared-events allowlist + no-raw-content literal), retention/aggregation, deletion (honored, boundaries, max days), role-scoped access, small-cohort suppression (k-anonymity floor >= 2), and a dark-pattern ban. Cross-field refines require aggregation not to outlive retention, deletion to reach aggregates and backups, and the default tier to be declared. The gate analyticsEventPermitted(policy, request) refuses to record an event that lacks consent, was never declared (minimization), carries raw content, would expose a sub-threshold cohort, or rides a prohibited dark pattern — accumulating every violation. Registered in schema-registry + index; examples added (canonical + variant, structural and cross-field negatives); schemas and goldens regenerated (100 published, 350 goldens).
  • Verification: dedicated spec (7 tests) covering the canonical policy, the integrity literals, the suppression floor, the deletion-boundary and retention refines, and every gate branch (permit + each single refusal + an all-five-violations case); the schema-publication spec validates the new canonical examples against zod + ajv. Full study suite 53 files / 642 tests green; tsc -p tsconfig.lib.json clean; schema --check "100 contracts published, no drift, no breaking changes"; goldens --check "350 fixture cases stable"; stub-scan clean; eslint 0 errors. TRAP: the dark-pattern kind fake-urgency tripped the pre-commit stub scan on the word "fake" — renamed to false-urgency. (A Codex review was attempted but the background run was killed by a session resource limit; the gate and refines are straightforward inclusion/integer checks fully exercised by the dedicated and publication specs, and were adversarially re-read by hand.)
  • Marked [x]: YSD-18085.
  • Commit: resolve via git log --grep='YSD-18085'.

2026-07-22 — YSD-18086 (metric subordination to gates)#

  • Item: §18.4 — Keep product metrics subordinate to rights, safety, epistemic, accessibility, reliability, and quality gates; encode gates so metric pressure cannot bypass them.
  • Artifact: libs/contracts/src/study/metric-subordination-policy.ts — a new registered zod policy contract (the 101st published contract). MetricSubordinationPolicy fixes the precedence: all six GATE_CLASSES (rights, safety, epistemic, accessibility, reliability, quality) supersede product metrics, each blocking, with metricsCanOverrideGate pinned to a literal false and metricsAreSubordinate to a literal true; a cross-field refine requires every gate class to be covered exactly once. The subordination is encoded in evaluateReleaseGate(policy, gateOutcomes, metricsUrgeRelease): a release is permitted only when every superseding gate was evaluated and passed, and there is deliberately no control-flow branch by which metricsUrgeRelease can flip a blocked release to permitted — it is instead recorded as metricPressureDisregarded when it collides with a gate failure, making the subordination observable rather than silent.
  • Verification: dedicated spec (6 tests): the canonical policy, the subordination literals, the gate-coverage refine, and the gate behaviour — release only when all six pass, metric pressure not altering a clean release, the critical case where a failing rights gate blocks the release despite metric pressure (blockedBy ['rights'], metricPressureDisregarded true), and an unevaluated gate blocking. The publication spec validates the canonical example against zod + ajv. Full study suite 54 files / 648 tests green; tsc -p tsconfig.lib.json clean; schema --check "101 contracts published, no drift, no breaking changes"; goldens --check "352 fixture cases stable"; stub-scan clean; eslint 0 errors.
  • Marked [x]: YSD-18086. §18.4 product success metrics now COMPLETE (18080–18086).
  • Commit: resolve via git log --grep='YSD-18086'.

2026-07-22 — YSD-7001 (global Study nav entry)#

  • Item: §7.1 — Add the approved Study entry to Oshun Studio global navigation with authorization, feature-flag, active-route, keyboard, localization, responsive, and telemetry behavior.
  • Placement: /studio/study resolves to the Studio (admin) shell (isWebAdminPath → true), so the "global Study nav item" approved by decision YSD-0122 / ADR-0074 is a first-class entry in the Studio shell sidebar, not the customer top-level nav.
  • Artifacts:
    • apps/oshun/web/src/navigation/routes.tsstudy: '/studio/study' added to WEB_ADMIN_ROUTE_PATHS (extends WebAdminRoute).
    • apps/oshun/web/src/navigation/shells.ts — new study item in WEB_ADMIN_SIDEBAR_ITEMS (label "Study", matchPrefix /studio/study, so resolveWebAdminNavRoute returns study and breadcrumbs read "Admin › Study"); pure isStudioStudyNavVisible({authStatus, featureFlags}) resolver + STUDIO_STUDY_NAV_FEATURE_FLAG = 'studio.study' (authorization = authenticated only; feature-flag = studio.study: false kill switch per YSD-1014).
    • apps/oshun/web/src/analytics/shellNavigationTelemetry.ts — new resolveStudioSurfaceFromRoute + trackStudioSurfaceView emitting studio_surface_viewed (the customer route-transition effect never fires in the admin shell, so Studio surfaces were previously untracked).
    • apps/oshun/web/src/config/runtime-context.tsxuseOptionalRuntimeConfig (non-throwing accessor so shared chrome can read flags without a provider).
    • apps/oshun/web/src/components/ShellLayout.tsxScanSearch icon + completed ADMIN_NAV_ICONS Record (also fixes the previously-missing taraWorkbench key); filters the study item by isStudioStudyNavVisible; a Studio surface-view telemetry effect. active-route (aria-current), keyboard, responsive (collapsible Sidebar) and localization (translatable label rendered by the RTL/locale-aware Sidebar) are the same production behaviors every Studio nav sibling inherits.
  • Verification:
    • npx vitest run src/navigation/shells.test.ts src/navigation/routes.test.ts src/analytics/shellNavigationTelemetry.test.ts src/components/__tests__/ShellLayout.test.tsx — 86 tests green (new: study route resolution + breadcrumb, visibility resolver auth/flag matrix, studio-surface resolver + telemetry event, and a ShellLayout integration test asserting the Study item is present in the sidebar nav on /studio/study and that studio_surface_viewed fires with surface: 'study').
    • npx tsc --noEmit — full app typecheck clean (exit 0).
    • npx eslint on all five touched source files — 0 errors.
    • Note: the accessibility-axe.tsx "ShellLayout has no duplicate IDs" test times out on this box; confirmed pre-existing (fails identically on a stashed baseline), unrelated to this change.
  • Marked [x]: YSD-7001.
  • Commit: resolve via git log --grep='YSD-7001'.

2026-07-23 — YSD-7062 (cross-reference review + create action surface)#

  • Decision/Rationale: YSD-7063 already delivered the read-only relationship review card (type, endpoints, direction, rationale, author/model, confidence, anchors, context, rights, review state, limitations) and both the card and SuggestedRelationsPanel explicitly deferred the decision verbs to this item. The backend already supports every verb — createCrossReference, the generic POST /reviews review path (reviewRecord, which enforces the closed legal transition table and the "models suggest, humans decide" acceptance authority), and the dedicated POST /cross-references/:id/keep-distinct — so YSD-7062 is a UI action surface, not new domain logic. The seven verbs map exactly onto that backend: create → createCrossReference (born suggested/provisional); accept/reject/correct/ contest → reviewRecord transitions from the record's current state; merge where authorized → accepting an identity edge (the only way sameness is ever recorded — a deliberate, reasoned human act, never inferred, YSD-2155); keep-distinct → an identity edge is never plain-rejected but kept distinct through the dedicated endpoint that records the rationale + audit.
  • Artifacts:
    • apps/oshun/web/src/components/studio/CrossReferenceActions.tsx — new. The decision surface for one suggested/contested edge. Offers exactly the human review verbs the record's current state legally permits (LEGAL_REVIEW_TRANSITIONS), pins subjectRevision so a stale decision fails closed with a 409, requires a reason (and corrections for a corrected), and routes an identity edge's accept/reject to merge/keep-distinct with explicit warnings. Honest failure mapping (401/403/ 404/409 → reviewer-facing text); onReviewed lets the host drop a decided edge from the queue. noValidate so the accessible in-form validation (not the native popup) governs.
    • apps/oshun/web/src/components/studio/CreateCrossReferenceForm.tsx — new. The create verb: draw a new manual typed edge between two addressable study records (relation, both endpoints + pinned revision, rationale, context/ cultural bounds, rights-gating territory). Born suggested (provisional) so it re-enters the same review queue; warns that identity edges never merge on creation.
    • apps/oshun/web/src/components/studio/SuggestedRelationsPanel.tsx — pairs each review card with CrossReferenceActions, hosts the create form behind a toggle, drops decided edges from the pre-decision queue, and feeds a newly created suggested edge back into it.
  • Verification:
    • npx vitest run (from apps/oshun/web) on CrossReferenceActions.spec.tsx, CreateCrossReferenceForm.spec.tsx, SuggestedRelationsPanel.spec.tsx, CrossReferenceReviewCard.spec.tsx24 tests green. Coverage: legal-verb surfacing per state; accept pins revision + fromState; reason-required and corrections-required guards (no request sent); identity merge via /reviews with the sameness warning; identity keep-distinct via the dedicated endpoint; 403 → "not authorized", 409 → "changed since you loaded it"; create success body shape + rights/validation failures; queue drops a decided edge; create toggle.
    • npx tsc --noEmit -p tsconfig.typecheck.json — my three source files are clean (the only errors are pre-existing, in libs/isis/ai-providers, from missing @oshun/ai exports — unrelated).
    • npx eslint on the three source files — 0 errors.
    • Adversarial stub scan over the three source files — 0 hits.
  • Marked [x]: YSD-7062.
  • Commit: resolve via git log --grep='YSD-7062'.

2026-07-23 — YSD-7067 (undo/redo for draft view and annotation actions)#

  • Decision/Rationale: The item asks for undo/redo of draft view and annotation actions "while retaining auditable version history for accepted records" (YSD-0013). The two guarantees are kept apart by construction: undo/ redo operates only on ephemeral, local working state, never on accepted scholarly records (observations, interpretation claims, reviewed cross-references, creative decisions), which are immutable and change only through versioned supersession and their own review events. So an undo can never erase a committed record or a review decision. The workbench's two purely-local draft surfaces are the concrete targets: the pinboard (a draft annotation action — pin/remove/clear selections of the source) and the compare tray (a draft view action — stage/remove/clear moments for later comparison). Server-committed actions (observation authoring, cross-reference review) are deliberately not undoable here — they are the "accepted records" side the clause protects.
  • Artifacts:
    • apps/oshun/web/src/components/studio/studyDraftHistory.ts — new. A reusable bounded past/present/future core (initDraftHistory, recordDraft, undoDraft, redoDraft, canUndoDraft, canRedoDraft) plus a useDraftHistory hook. A fresh edit truncates the redo branch; the past is bounded (DRAFT_HISTORY_LIMIT) so a long session can't grow memory without limit; end-of-stack undo/redo are identity no-ops so callers can skip a persist; reset adopts a hydrated baseline without re-persisting.
    • apps/oshun/web/src/components/studio/PinnedSelectionsPanel.tsxuseStudySelectionPins is now history-backed (pin/remove/clear are recorded; undo/redo/canUndo/canRedo/lastAction exposed) while keeping the synchronous pin() return via a ref. Adds accessible Undo/Redo controls (disabled, not hidden, when unavailable) and a role="status" announcement of the last draft action (toward YSD-7069).
    • apps/oshun/web/src/components/studio/CompareTray.tsx — staging moves to useDraftHistory with localStorage persistence via onCommit; adds the same Undo/Redo controls + announcement.
  • Verification:
    • npx vitest run (from apps/oshun/web) on studyDraftHistory.spec.ts, PinnedSelectionsPanel.spec.tsx, CompareTray.spec.tsx24 tests green. Coverage: core init/record/undo/redo, redo-branch truncation on divergence, past bounding, immutability, end-of-stack no-ops; pinboard undo/redo of pin/clear with per-step persistence and the divergence rule; compare-tray undo/redo of stage/clear with persistence. The pre-existing YSD-7060/7061 tests still pass unchanged.
    • npx tsc --noEmit -p tsconfig.typecheck.json — my three source files clean (only pre-existing libs/isis/ai-providers errors remain, unrelated).
    • npx eslint on the three source files — 0 errors. Adversarial stub scan — 0 hits.
  • Marked [x]: YSD-7067.
  • Commit: resolve via git log --grep='YSD-7067'.

2026-07-23 — YSD-8045 (review machine detections without mutating the original)#

  • Decision/Rationale: The crux is "without mutating the original detection." The generic review path (POST /reviewsreviewRecord, family detection) already satisfies it: applyReviewEvent runs superseding(envelope), minting a NEW revision and appending an immutable review event (YSD-0013), so the processor's original reading is preserved as an earlier revision and is never edited in place. Detections reach the client through readAnalysis(runId) ({ run, detections, stale, staleEditions }) and runs through listAnalyses(projectId). So YSD-8045 is a UI surface over an existing, fully-supported backend. Every legal review verb from the record's current state is offered — a suggested detection can be accepted/corrected/contested/ rejected, and an already-accepted one contested or superseded (the fifth verb the item names) — and detections are kept in their own machine section, never merged into the human epistemic layers (YSD-9005).
  • Artifacts:
    • apps/oshun/web/src/components/studio/studyReviewText.ts — new. Canonical reviewFailureMessage (401/403/404/409 → honest reviewer text) shared by the cross-reference and detection review surfaces (YSD-0014); CrossReferenceActions now imports it instead of its own copy.
    • apps/oshun/web/src/components/studio/DetectionReviewActions.tsx — new. Per- detection review verbs from LEGAL_REVIEW_TRANSITIONS (INCLUDING superseded, unlike the cross-ref surface), each via client.reviewRecord({family:'detection'}) with a revision pin, reason (+ corrections for a correct), a supersede preservation note, and honest failure mapping. The done state states the original reading is preserved.
    • apps/oshun/web/src/components/studio/DetectionReviewPanel.tsx — new. Self- loads listAnalyses (keeps only succeeded/partially-succeeded runs), lets the reviewer pick a run, readAnalysis for its detections, and lists each with feature, processor (+ model or "no model (deterministic)"), honest confidence, anchor count, limitations, review state, and the review actions. Honest loading/denied/no-runs/no-detections/stale states; reflects a completed review in place so the next action uses the new state + revision.
    • apps/oshun/web/src/components/studio/StudyWorkbench.tsx — docks a project- scoped "Detections" lower panel.
  • Verification:
    • npx vitest run src/components/studio/__tests__/ (from apps/oshun/web) — 431 tests green across 47 files (new: DetectionReviewActions 6, DetectionReviewPanel 6; CrossReferenceActions still green after the shared- helper refactor; StudyWorkbench wiring intact). Coverage: legal verbs per state, accept/correct/contest/reject/supersede body shapes + revision pin, reason/corrections guards, 403/409 mapping, run listing + non-reviewable-run exclusion, denied/empty/no-runs states, in-place state reflection.
    • npx tsc --noEmit -p tsconfig.typecheck.json — my new source files clean (only pre-existing libs/isis/ai-providers errors remain, unrelated).
    • npx eslint on all five touched source files — 0 errors. Adversarial stub scan over the three new source files — 0 hits.
  • Marked [x]: YSD-8045.
  • Commit: resolve via git log --grep='YSD-8045'.

2026-07-23 — YSD-8046 (entity linking, candidate identity, keep-distinct, no biometrics)#

  • Decision/Rationale: The entity identity backend is complete — listEntities, relateEntities, mergeEntity (source merged INTO target, sets mergedIntoEntityId, preserved as history via superseding), keepEntitiesDistinct (adds to keptDistinctFromEntityIds, and the store REFUSES a merge of a kept-distinct pair, fail-closed), addEntityAlias, resolveEntityCanonical — with client methods for all of them, but no UI. YSD-8046 is that UI. The "without biometric matching" guarantee is structural: IdentityLabelOriginSchema admits only human-entry or trusted source-metadata, never a model/biometric match (YSD-4052), and cross-work identity is a reviewed edge, never implicit (YSD-2155). The panel surfaces the label origin so every identity's basis is inspectable, and a merge is a deliberate reasoned act, never inferred.
  • Artifacts:
    • apps/oshun/web/src/components/studio/EntityLinkingPanel.tsx — new. Lists a project's entities with kind, human/source label origin ("never biometric"), aliases, typed relationships, merged-into history, and kept-distinct count. Per-entity actions: Link (relateEntities, typed relation + note), Same-as/merge (mergeEntity, deliberate, with an explicit "same identity / stays on record" note), Keep distinct (keepEntitiesDistinct), Add alias (addEntityAlias). Target picker excludes self and lists the other entities; merged entities show no further actions. Honest failure text, including the kept-distinct merge refusal. Patches the affected entity in place from the response.
    • apps/oshun/web/src/components/studio/StudyWorkbench.tsx — docks a project- scoped "Entities" lower panel.
  • Verification:
    • npx vitest run src/components/studio/__tests__/ (from apps/oshun/web) — 439 tests green across 48 files (new: EntityLinkingPanel 8; StudyWorkbench wiring intact). Coverage: list + identity note + label origin; denied; link body/path; deliberate merge with the same-identity note + merged-into result; keep-distinct body/path; alias without a target; target-and-note validation; the backend kept-distinct merge refusal surfaced.
    • npx tsc --noEmit -p tsconfig.typecheck.json — my new file clean (only the pre-existing libs/isis/ai-providers errors remain, unrelated).
    • npx eslint on both touched source files — 0 errors. Adversarial stub scan over the new source file — 0 hits.
  • Marked [x]: YSD-8046.
  • Commit: resolve via git log --grep='YSD-8046'.

2026-07-23 — YSD-14002 (question lifecycle transitions with rationale, evidence, history)#

  • Decision/Rationale: StudyQuestion is a first-class object with a closed lifecycle — LEGAL_QUESTION_TRANSITIONS: open ⇄ under-investigation, either may resolve to answered/abandoned, and answered/abandoned may reopen on new evidence. The backend (transitionStudyQuestion, listStudyQuestions) enforces the legal moves, requires a rationale note, requires an answer to cite ≥1 evidence anchor (YSD-2112), and records the full resolutionHistory — but there was no UI. YSD-14002 is that UI.
  • Artifacts:
    • apps/oshun/web/src/components/studio/QuestionLifecyclePanel.tsx — new. Lists a project's questions with state, scope, and linked-evidence count; shows the full resolution history (from → to: rationale); offers exactly the legal transitions from the current state (Start investigating / Answer / Abandon / Reopen), each requiring a rationale; an answer cites the question's linked evidence and is refused with no evidence linked. A lifecycle filter serves the unanswered/answered/abandoned views. Patches the question in place from the response; honest denied/error/empty states.
    • apps/oshun/web/src/components/studio/StudyWorkbench.tsx — docks a project- scoped "Questions" lower panel.
  • Verification:
    • npx vitest run src/components/studio/__tests__/ (from apps/oshun/web) — 446 tests green across 49 files (new: QuestionLifecyclePanel 7; StudyWorkbench wiring intact). Coverage: legal transitions per state, under-investigation transition body/path + history render, answer refused with no evidence, answer cites linked evidence, rationale required, lifecycle filter, denied.
    • npx tsc --noEmit -p tsconfig.typecheck.json — my new file clean (only the pre-existing libs/isis/ai-providers errors remain, unrelated).
    • npx eslint on both touched source files — 0 errors. Adversarial stub scan over the new source file — 0 hits.
  • Marked [x]: YSD-14002.
  • Commit: resolve via git log --grep='YSD-14002'.

2026-07-23 — YSD-16021 Player accessible names, values, states, relationships, shortcut help, timecode, buffer, errors, change announcements#

  • Decision: The professional transport (YSD-8002) exposed rich visual controls but was silent to assistive technology: the custom speed/shuttle/ reverse/loop/frame/word/event/scrub actions have no native control that speaks them, there was no buffer state, no relationship wiring between the controls and the media element, and no discoverable keyboard-shortcut surface. YSD-16021 is the media-player semantics counterpart to the keyboard-operation item (YSD-16020); it is completed here at the player level. (Timeline accessible names/states are already carried by segment-selection announcements and shape cues; buffer/timecode/transport-shortcuts are inherently player concerns.)
  • Artifacts:
    • apps/oshun/web/src/components/studio/SourceMediaCanvas.tsx:
      • Change announcements — adopts the shared live-region primitive (useLiveAnnouncer, YSD-16003; first workbench consumer). A polite region announces each resulting state: Speed N×, Shuttle forward/reverse N× / Shuttle stopped, Reverse on/off, Loop in … loop A to B / Loop cleared, Frame forward/back, <timecode>, Word/Event at <timecode> (and an honest No word/event ahead), Seeked to <timecode>.
      • Buffer stateonProgress computes the furthest buffered fraction of the known duration; a canvas-buffer readout shows N% buffered (with — waiting for data while stalled) and honest absence (buffer state unavailable) when the browser reports no ranges. waiting/ stalledplaying transitions are announced (Buffering — waiting for dataPlayback resumed).
      • Relationships — the media element gets a stable useId; the transport and frame-controls groups carry aria-controls pointing at it.
      • Shortcut help — a discoverable <details> panel lists every key; the transport is aria-describedby that list and carries aria-keyshortcuts.
      • Values/states — the shuttle indicator gains an aria-label that tracks its state; reverse aria-pressed, speed <select> value, loop text, and the buffer readout supply the rest. Timecode and role="alert" errors were already present and remain part of the player semantics.
  • Verification:
    • npx vitest run src/components/studio/__tests__/SourceMediaCanvas.spec.tsx (from apps/oshun/web) — 41 tests green (32 pre-existing + 9 new for 16021: relationships, discoverable shortcut help, shuttle accessible value, speed/shuttle/reverse/frame change announcements, honest→percentage buffer readout, buffering wait/resume announcements).
    • npx vitest run on StudyWorkbench.spec.tsx, StudyWorkbench.deeplink.spec.tsx, StudyWorkspaceSources.spec.tsx — 40 tests green (no regression in surfaces that embed the canvas).
    • npx tsc --noEmit -p tsconfig.json — touched files clean.
    • npx eslint SourceMediaCanvas.tsx — 0 errors (one pre-existing <img> warning, unrelated). Adversarial stub scan over the file — 0 hits.
  • Marked [x]: YSD-16021.
  • Commit: resolve via git log --grep='YSD-16021'.

2026-07-23 — YSD-8024 Timeline virtualization, progressive detail, snapping, range selection, pan/zoom, track filtering/grouping, density presets, large-session performance#

  • Decision: The multitrack timeline (YSD-7034) mapped every item as a fraction of the whole source span (start / maxSeconds) and painted them all, with only a per-lane count throttle. YSD-8024 adds the navigation and scale layer as a new pure geometry module plus an additive wiring: the viewport defaults to the full range, so at { 0, total } the position of a second is exactly s / total — the un-zoomed timeline renders byte-for-byte as before, and every pre-existing position test (left: '10%', '50%') still holds. Zoom, pan, density, filtering, grouping, and range selection are opt-in controls layered on top. The canonical time base is never mutated — the viewport only decides which slice is on screen and where a second lands within it.
  • Artifacts:
    • apps/oshun/web/src/components/studio/timeline-viewport.ts (new, pure): fullViewport/clampViewport/zoomViewport (anchor-preserving)/panViewport/ zoomToRange; positionPercent/widthPercent (default-viewport identity with the old math); virtualize (cull to window, count hidden before/after); snap (nearest boundary within tolerance, earlier-wins ties); range helpers (normalizeRange/clampRange/rangeDurationSeconds/isMeaningfulRange); densityMetrics (comfortable/compact/condensed → row height, per-lane cap, label visibility); detailLevel + axisTicks (nice 1/2/5×10ⁿ major ticks, minor ticks interleaved only at the detail zoom); trackFamilies/ filterByFamily (empty selection = show all)/groupByFamily.
    • apps/oshun/web/src/components/studio/SourceTimeline.tsx: routes all position math through the viewport; adds session state for viewport, density, family filter, and range selection; a navigation control bar (zoom in/out/fit, pan, keyboard +/−/arrows, viewport status with detail level, density selector, track-family checkboxes, snapped range in/out with zoom-to-selection/clear); a progressive-detail axis ruler; virtualizes each lane against the window and reports off-window items separately from the perf throttle; groups lanes under track-family headings. Removed the now-unused STACK_ROW_HEIGHT / DEFAULT_MAX_ITEMS_PER_LANE constants (density metrics supply them).
  • Verification:
    • npx vitest run timeline-viewport.spec.ts30 pure-geometry tests (position identity, clamp, zoom/pan/fit, virtualization counts, snapping, range, density ordering, detail levels + tick storm guard, family filtering/grouping).
    • npx vitest run SourceTimeline.spec.tsx27 tests (17 pre-existing green: no regression from the default full-range viewport; 10 new: nav presence / first-viewing withholding, zoom-around-centre, pan+fit, virtualization off-window report, boundary snapping, condensed label hiding, family filter without blanking, family grouping headers, progressive-detail ticks).
    • StudyWorkbench.spec.tsx + .deeplink.spec.tsx — 29 green (embedding surface unaffected). npx tsc --noEmit --incremental false — touched files clean. eslint 0 errors. Adversarial stub scan — 0 hits.
    • NOTE: PinnedSelectionsPanel.spec.tsx has ONE pre-existing, wall-clock- dependent failure (viewable in US vs access window elapsed since pinned) in the rights-window-lapsed computation — unrelated to this change (that panel imports neither timeline module; last touched by commits 14768f550f/d6791ae5c4) and left for a dedicated fix.
  • Marked [x]: YSD-8024.
  • Commit: resolve via git log --grep='YSD-8024'.

2026-07-23 — YSD-13190 Require rationale, alternatives, transformation, intended effect, owner, and source/principle paths before committing a creative decision#

  • Decision: The commit gate already enforced human authorship (a decision carries a human ownerUserId), a rationale, ≥1 principle/source path, and ≥1 approval. Three of the six requirements were missing: alternatives could be empty at commit, and there were no dedicated fields for the transformation (the abstraction step that keeps a decision from copying its source) or the intended audience effect (the intent YSD-13251 later compares outcomes against). These are added and required only at commit — a proposal may still be half-formed. Reused the existing assertDecisionCommittable gate so the failure is a clean domain error, mirrored by the contract superRefine.
  • Artifacts:
    • libs/contracts/src/study/entities/creative.ts: CreativeDecisionSchema gains optional transformation and intendedEffect; the superRefine now also refuses a committed decision with no alternative, no transformation, or no intended effect.
    • libs/yemaya/study-workspace/src/policies/inspiration-coordination.ts: new IncompleteDecisionError (with a precise missing reason); assertDecisionCommittable now enforces alternatives/transformation/intendedEffect alongside the evidence trail.
    • libs/yemaya/study-workspace/src/use-cases/manage-creative-decisions.ts: propose and update commands accept and thread transformation/intendedEffect.
    • libs/contracts/src/study/service-routes.ts: Create/UpdateCreativeDecisionBodySchema accept the two fields. apps/yemaya/svc-study-workspace/src/routes/study-routes.ts: the create/update handlers pass them, and errorResponse now maps both IncompleteDecisionError and the previously-unmapped UntraceableDecisionError to 409 (they had fallen through to 500).
    • Regenerated schema artifacts: schemas/study/CreativeDecision.schema.json, openapi.json, openapi.components.json. The generated client is unchanged (it references body schemas by name, so the new optional fields flow through; generate-study-client.ts --check passes).
  • Verification:
    • npx vitest run in libs/yemaya/study-workspace1740 tests green (new: 4 use-case gate tests — commit refused when an alternative/transformation/ intended-effect is missing, with the record left proposed, plus a fill-in- then-commit path; 5 pre-existing commit fixtures updated to carry the new fields).
    • npx vitest run entities6.spec.ts (contracts) — committed-fixture updated + a new superRefine test; src/study/entities + admin-bulk-operations — 213 green.
    • svc-study-workspace integration — the propose→approve→commit and transfer journeys now send the fields and pass; the sole remaining failure is the pre-existing shared-DB technicalFingerprint ingest-facts flake (unrelated).
    • tsc on my touched files clean (sibling-spec branded-type errors are pre-existing); adversarial stub scan clean.
  • Marked [x]: YSD-13190.
  • Commit: resolve via git log --grep='YSD-13190'.

2026-08-04 — YSD-19101 live co-study: the room finally exists#

  • Finding: §8.4 was fully written and entirely unreachable. planRoomSync, assertNoImposedAccommodation, readDrift, planRejoin, mayDriveTransport, projectPresence, presenceLeaks, screenComment and applyModeration were imported by zero files under apps/yemaya/svc-study-workspace/src and apps/oshun/web/src. The cause was one missing table: startLiveSession enqueued a study.live_session.started event and returned a reply. There was nothing to join, no roster to read back, and no playhead to synchronize to.
  • The sharp end. Crystallization took messages and consents as REQUEST FIELDS, so the person turning the talk into a claim also supplied the talk. Every rule in crystallizationIssues then held — against a document that person wrote. "Carry every dissent" is not a rule when omitting the dissent from the body makes it cease to exist; "credit every contributor" is not a rule when the contributors are whoever the body says spoke. The refusals looked like they meant something, which is worse than having none.
  • Two real leaks found by writing the seam:
    • ProjectedPresence.participantId carried the RAW USER ID into a pseudonymous room. The label read "Participant 2" and the payload beside it read guest-1; a surface keying its list by it puts the name in the DOM, and presenceLeaks did not catch it because it only checked displayName. Fixed in presence-privacy.ts: the projection emits the pseudonym, and the leak check now tests both identifiers.
    • RoomSyncPlan.participants[].timelineAffecting NAMES the accommodation that detached somebody — the exact disclosure roomVisibleNote exists to avoid — and the route shipped the whole plan to every participant. New roomVisibleSyncPlan(plan, viewerUserId) empties it for everybody but its owner, and every use case returning a plan projects it (the host included: a host is another person in the room).
  • Artifacts:
    • libs/contracts/src/study/entities/live-room.ts: LiveRoomRecord (roster, per-participant accommodations, standing, leftAtRoomMs, contribution consent, transport, control grants, identity mode, lifecycle) and LiveRoomMessageRecord (stance, body, anchors, atRoomPositionMs, moderation state + flags + decisions). Ids in ids.ts.
    • apps/yemaya/svc-study-workspace/migrations/0048_study_live_room.sql: two tables, RLS, revision immutability, and enforce_live_room_promises() — a room may not be repointed at another edition (the roster was cleared to watch ONE work) and not-recorded may not become recorded-with-consent (the people in it agreed to a room that was not being recorded). The reverse is allowed: stopping a recording takes nothing from anyone.
    • libs/yemaya/study-workspace/src/use-cases/manage-live-room.ts (1278 lines): join/rejoin, leave, own-settings, transport, transport control, drift, presence, post message, moderate, consent, list, read. Rights are RE-RESOLVED through decideSelection on every act that shows a frame — the room keeps playbackGrantId as a record and never re-uses it, so a seminar that runs past the end of a licence stops.
    • live-co-study.ts: startLiveSession now persists the room in the same transaction as the event; crystallizeLiveDiscussion reads the transcript and the consents from the store, and a claim built from a moderator-pruned transcript carries moderatedOutCount as a limitation.
    • 12 routes under /api/study/live-sessions/... plus GET /api/study/projects/:projectId/live-sessions (only rooms the caller is on the roster of). errorResponse maps the six standing errors to 403, ImposedAccommodationError to 403, LiveRoomClosedError to 409, ForeignAnchorError/ModerationError/CrystallizationError to 422 — the last had been falling through to 500.
    • apps/oshun/web/src/components/studio/StudyLiveRoomPanel.tsx, mounted in /studio/study.
  • Verification:
    • libs/yemaya/study-workspace: live-room.spec.ts 50 new tests, live-co-study.spec.ts rewritten to post real messages — 20; full lib suite green.
    • apps/yemaya/svc-study-workspace: live-co-study.route.spec.ts 25 (13 new).
    • apps/oshun/web: StudyLiveRoomPanel.spec.tsx 11.
    • tsc --noEmit clean on contracts, lib and service (the index.ts TS2308 re-export ambiguities are pre-existing). Adversarial stub scan clean.
  • Marked [x]: YSD-19101.
  • Commit: resolve via git log --grep='YSD-19101'.

2026-08-04 — YSD-19102 (partial): a principle gets a table, and the trail must resolve#

  • Finding, proved with a probe. CreativePrinciple had a contract, an id type, and three records already pointing at principle ids (CreativeDecision.principlePathIds, OriginalConcept.informedByPrincipleIds, and the /principles/:principleId/track-record route) — and no table, no store method, no use case and no route. git grep CreativePrinciple over libs/yemaya/study-workspace/src and apps/yemaya/svc-study-workspace/src returned exactly one file: a policy spec. readPrincipleTrackRecord never loads the principle; it assembles a record out of the decisions that MENTION one, because there was none to load.
  • Why that mattered. CreativeDecisionSchema refuses to commit a derived decision carrying neither a principle nor a source path — "a decision from nowhere cannot claim the study trail" (YSD-22013). With nowhere for a principle to exist, any UUID satisfied it. A throwaway spec wrote principlePathIds: ['00000000-0000-4000-8000-00000000dead'] and it was accepted without complaint. The rule was real and the thing it ranged over was not — the same shape as the YSD-19101 crystallization bug, one layer up.
  • The scale of it, measured by closing it. Adding the resolution check broke thirteen spec files at once, including walking-skeleton.integration.spec.ts, which passed randomUUID() as a principle path in three places. Every one of those tests was green against a fabricated evidence trail. All are now fixed: real principles where the project has evidence to abstract from, and origin: 'unsupported-original-exploration' in the one fixture whose project has no source works at all — which is what that fixture always was.
  • Artifacts:
    • apps/yemaya/svc-study-workspace/migrations/0049_study_creative_principle.sql — RLS, revision immutability, a (project_id, review_state) index because "the accepted principles of this project" is the read a transfer gate makes.
    • libs/yemaya/study-workspace/src/use-cases/manage-creative-principles.ts — derive (supporting AND contradicting evidence resolved against the project), review by a second hand with a reason, read, list. assertPrinciplePathResolves is exported and called from both places a principle path is written (decision create/update, concept create/update): a rule enforced in one of the two places it applies is a rule with a documented way round it.
    • libs/yemaya/study-workspace/src/use-cases/trace-artifact-graph.tsbuildProjectArtifactGraph derives the typed dependency edges from the records themselves (the existing impact read walks AUTHORED cross-reference edges, which say what somebody drew, not what the records stand on); reviewChangeImpact runs computeChangeImpact — which had zero callers — and reports which impacted artifacts the project had already stood behind; traceDecisionProvenance walks source → principle → decision.
    • Two refusals worth keeping: an unresolved edge is REPORTED, never dropped (a traversal that skipped it returns a shorter path and calls it complete), and the abstracted and direct routes are never merged, because a decision that reached its sources through an abstraction and one that cited the frames directly have made different claims about their own provenance (YSD-2041).
    • 6 routes: derive / review / read / list principles, change-impact, provenance. PrincipleEvidenceError and UnresolvedPrinciplePathError map to 422 unresolved_evidence_path; PrincipleReviewerError to 403.
  • Verification:
    • libs/yemaya/study-workspace: creative-principles.spec.ts 18, artifact-graph.spec.ts 13; full lib suite 786 files / 12629.
    • apps/yemaya/svc-study-workspace: creative-principles.route.spec.ts 11; full route suite 16 files / 234, including the walking skeleton against real Postgres.
    • tsc --noEmit clean on contracts, lib and service; adversarial stub scan clean; generator drift checks pass.
  • NOT marked. YSD-19102 names five things and one is still unbuilt: the cross-work matrix has no cells. policies/comparison-matrix.ts enforces seven distinct cell states (unknown, unavailable and forbidden are three different things) and a cross-media alignment honesty rule, and ComparisonSetSchema stores members, alignments and a matrix layout with nowhere to put a cell — so comparisonCellIssues and describeCellStateLegend are enforced by nobody. Concept graphs and coverage/consistency were already reachable via /projects/:projectId/concept-system-map.
  • Commit: resolve via git log --grep='YSD-19102'.

2026-08-04 — YSD-19102 completed: the matrix gets cells#

  • Finding. ComparisonSetSchema stores members, alignments, sync markers and offers a matrix layout — with nowhere to put a cell. So policies/comparison-matrix.ts (seven distinct cell states) and read-models/matrix-operations.ts (nine exported operations: axisIssues, sortMatrixRows, facetMatrixRows, filterMatrixRows, clusterMatrixRows, savedViewIssues, assertSavedView, applySavedView, assertAxis) were enforced by nobody. A matrix layout was a declared shape with no contents.
  • Why the states are the point. unknown is undetermined, unavailable is "no data exists", forbidden is "a value exists and rights withhold it". A table that renders all three as an empty box tells a reader that a withheld value and an unmeasured one are the same thing — and leaks in the other direction, because a viewer who learns nothing is there has learnt something.
  • Artifacts:
    • libs/contracts/src/study/entities/comparison-matrix.ts — axes, the seven cell states, saved views. Two record-level refusals: a cell whose rowKey or colKey is not on an axis (it exists in the store and appears in no table), and two cells at one position (which renders as whichever the reader reached first).
    • apps/yemaya/svc-study-workspace/migrations/0050_study_comparison_matrix.sql — cells live in the payload, unlike 0048's transcript, because a matrix is authored and read WHOLE: every operation takes the entire row set.
    • libs/yemaya/study-workspace/src/use-cases/manage-comparison-matrix.ts — author / record cells / read (applying a saved view, sorting, faceting) / list. A matrix takes its corpus boundary from a comparison set that exists and is this project's, because a scope stated twice can disagree with itself. Cell anchors resolve against the project. The legend is sent with every read: a surface that had to invent the distinction is how three absences become one blank box.
    • 4 routes; ComparisonMatrixError/MatrixOperationError → 422 matrix_refused carrying every failing rule.
  • Verification:
    • comparison-matrix-storage.spec.ts 16, comparison-matrix.route.spec.ts 9; service route suite 17 files / 243. Migration 0050 applied and verified against real Postgres.
    • Adversarial stub scan clean; lint clean; generator drift checks pass.
  • Marked [x]: YSD-19102 — all five clauses now reachable: concept graphs and coverage/consistency via /projects/:projectId/concept-system-map (pre-existing), cross-work matrices via the four new matrix routes, source-to-principle-to-decision paths via /creative-decisions/:decisionId/provenance, and change-impact review via /projects/:projectId/change-impact.
  • Commit: resolve via git log --grep='YSD-19102'.

2026-08-08 — YSD-19041 hybrid search: the vocabulary nobody could address, and the eleven filters that evaporated#

  • The vein: §13.1's taxonomy executor (search/taxonomy-search.ts, 554 lines) was complete and correct — subsumption with cycle protection, deprecation redirects followed in both directions, absolute facet discipline, refusal of terms from an ungoverned extension, a three-valued answer separating "does not carry the term" from "has never been through tagging" — and it had one importer, the barrel. It had never decided anything about a real record, because two things did not exist. A governed TaxonomyTerm is identified by UUID and the query AST asks for domain/facet/termRef; indexEntryFromTaxonomyTerm takes that mapping as an argument it calls "the mapping the caller already maintains", and no caller maintained one. And nothing anywhere had ever put a term ON a record, so every corpus was permanently in the third state. hybridSearch refused the whole dimension as unexecutable — honestly, and permanently.
  • Second half of the same vein: toScanQuery translated exactly two of a source filter's thirteen constraints (workIds, media) and threw the other eleven away. A query naming creators, a release window, a genre, a platform or a build came back wider than the question with nothing saying so — the precise failure the file's own header calls "not partial — wrong".
  • Artifacts:
    • libs/contracts/src/study/entities/taxonomy-vocabulary.tsTaxonomyTermRegistration (the term PLUS the address a query reaches it by; the registration OWNS the term rather than pointing at it, because a governance row and a search row can drift), TaxonomyAssignment (origin, attribution, and a withdrawal that is stored rather than deleted), TaxonomyTaggingPass (the record that separates "considered and nothing applied" from "never reached").
    • apps/yemaya/svc-study-workspace/migrations/0059_study_taxonomy.sql — four tables. The address is COLUMNS, and the unique index over (tenant, domain, facet, term_ref) is where facet discipline actually lives. A trigger fixes the address at revision 1: re-addressing a term would hand every record already tagged with the old concept to whatever query now spells the new address. GovernedTaxonomyExtension (YSD-19123) — until now another complete contract with nowhere to live — gets its table here, because the extension gate must be READ at query time: an extension suspended after its terms were registered has to stop deciding immediately.
    • use-cases/register-taxonomy-extension.ts, use-cases/register-taxonomy-term.ts, use-cases/assign-taxonomy-term.ts — the address is checked against the STORED vocabulary; a relation pointing at an unregistered term is refused (an unwalkable edge returns a quietly smaller result set, not an error); the whole vocabulary is re-indexed as it WOULD be before the write, so a registration cannot break every future query; a retired term still matches but takes no new usages, and the refusal carries the migration rule.
    • use-cases/search-study-records.tsvisibleRecordIds, the positive record-level allowlist the rights gate produces while walking. Re-deriving that rule at a second retriever is how two gates come to disagree.
    • use-cases/hybrid-search.ts — the taxonomy and metadata retrievers, fused by reciprocal rank with the scan and the vector index, plus UncomposableQueryError: retrievers compose by UNION, which is right for a disjunction (unknown OR match is match) and wrong for a conjunction (unknown AND match is unknown), so a mixed and/not is refused rather than answered wider than asked.
    • 7 routes under /api/study/taxonomy/* and /api/study/projects/:projectId/taxonomy/*; conflicts → 409, unregistered → 422, a retired term → 422 carrying the replacement and the rule.
  • Zero leakage: the taxonomy retriever builds its records ONLY from visibleRecordIds, before evaluation rather than after. Filtering hits afterwards would be too late — executeTaxonomySearch also returns indeterminate record ids, and a bare id from a withheld work still tells a reader that a record exists they may not see, and how many.
  • Left refused by name, deliberately: culture (no stored record carries a culture or tradition, so answering "undecidable for every record" would be a sentence about the deployment dressed as one about the corpus), and the rights/permission filter dimensions (both narrow within what a reader may already see, YSD-13012, and neither has an executor).
  • Verification:
    • acceptance-taxonomy-search.spec.ts 28, taxonomy.route.spec.ts 8; study-workspace suite 810 files / 13 110; service route suite green; contracts suite green.
    • Adversarial stub scan clean; lint clean; generator drift checks pass; all six tools/yemaya-study/check-*.mjs gates pass.
  • Marked [x]: YSD-19041 — all five retrievers now run: metadata (all thirteen source constraints, language and version from the edition that declares them), taxonomy (over a real vocabulary and real tags), text, semantic and visual; with rights, epistemic labelling, version pinning (including the vocabulary versions in force), per-dimension explanations, and zero-leakage proven against a rights-withheld work.
  • Commit: resolve via git log --grep='YSD-19041'.

2026-08-09 — YSD-22054 requirement scope: the axis a green portfolio check cannot see#

  • Item: "Confirm tests cover the full requirement scope, including failure, permission, rights, expiry, deletion, accessibility, version, partial, offline, and scale behavior where applicable."
  • Why the existing gate did not answer it. check-test-portfolio.mjs (YSD-18101) proves every module is under a test — coverage along the axis of MODULES. A module fully covered by a spec that only ever drives the happy path passes it. The item names ten BEHAVIOURS, so the axis is (module × behaviour), and nothing measured it. Grepping the library for the machinery first: requirementScope, scopeCoverage, SCOPE_DIMENSIONS, behaviourDimensionzero hits.
  • Artifact: tools/yemaya-study/check-requirement-scope.mjs + check-requirement-scope.test.mjs (28 rule tests, one planted hole per rule and a planted PASS per rule) + docs/proposals/yemaya-study-workspace/requirement-scope-exemptions.json.
  • Neither side of the check is supplied by the person writing the tests:
    • Applicability is read off the implementation. A dimension applies to a module when the module DECLARES a state belonging to it — an as const register member, a string-literal union alternative. Nobody can decide expiry "does not apply here" while the code carries an 'expired'; the only way to make it inapplicable is to delete the state.
    • Coverage is decided by EXECUTION. For a state produced by code, the question is whether the suite ran one of the lines that produces it, read from a V8 coverage report. The tempting textual rule is wrong in this codebase and provably so: policies/still-image.ts:95 pushes { severity: 'error' } and still-image.spec.ts drives it with width: 0 while asserting valid === false, never writing the word.
    • Where nothing produces a state — it lives only in a register or a type — the weaker assertion rule applies, and the report says which rule judged each row, because a reader who cannot tell them apart cannot tell how strong the answer is. enumerated is a third, weaker-still standing for a state a spec covers by iterating its register (for (const family of METRIC_FAMILIES)).
    • A missing coverage report is a failure, not a pass.
  • Five design corrections, each forced by a real false report on this library:
    1. Tells match hyphen-delimited TOKENS, never substrings — 'shuttle' read as a TTL, 'immigration-status' and 'streak-loss-aversion' as versions.
    2. Register lines are excluded from execution evidence: they run at import, so keying on them would mark every state in the program as reached.
    3. A producer counts when it sits on one import chain with the declarer, in EITHER direction — ports.ts declares the event union its use case emits, and stk-build-attestation.ts re-declares the verdict union of the signing policy it calls.
    4. A spec exercising a CALLER speaks for the module — every study adapter is built that way, and reading only direct importers reported all ten as having an untested unavailability state their suites assert on every run.
    5. Comments are stripped before literals are read: an apostrophe in prose ("the learner's own queue") pairs with the next quote and silently shifts every literal after it.
  • What it found, and what was done about it. 757 applicable (module × dimension) pairs. Zero unreached — every code-produced state in the ten dimensions is executed by the suite, which is a real finding about this suite. The gaps were all on the assertion side, and each was closed with a domain test rather than a restatement of the register:
    • accessibility-capabilities — the image/graph/audio/timeline alternative contracts and the unsupportedunavailable distinction (nothing is unsupported today, and asserting the absence is what makes the day one appears visible);
    • live-playback-sync — each non-timeline accommodation, one by one, stays frame-locked (transcript panel, keyboard-only, reduced motion, larger text);
    • feature-flags — a lapsed licence drops licensed-source without taking public-domain-open-license with it;
    • persona-onboardingtwo-cleared-sources and a-transcript are the near-misses that read like small asks and are not day-one reachable;
    • template-gallery — a pinning template refused into a project with no right to quote; api-group-authorization — an AUTHENTICATED operator route is still outside the project model, and a rights segment the manifest does not use yet is still classified; error-taxonomy — 429 "slow down" apart from 503 "the thing behind us is down"; peer-feedback-workflow — a deferral owes nobody a reason and a decline does; collaboration-safety — blocking a participant and a session lapsing are recordable acts; teaching-pack-publication — a pack comes down for being WRONG, not only for rights; lens-orchestrationpartial is terminal and reachable only from running; adapter-compatibilityunsupported and unavailable are one bucket to a consumer and two facts to an author; outcome-impact-reviews — the review still opens when the reviewer cannot reach the evidence; source-to-decision-trace — a rejected and a superseded step stay in the trace; query-planner — a REJECTED search may not claim results; semantic-search — a hit matched on a caption says so; relational-chain-backup — a grant that expired since the backup blocks a restore exactly as a revocation does; completion-budgets — the deletion budget measures propagation and is not a window of exposure.
  • One real defect fixed, not papered over. continuous-reevaluation.ts documented four actions and its resolver could only ever return three: 'expire' was unreachable, with rights-expired collapsed into revoke-access. The program distinguishes the two everywhere else (provider-availability, comparison-declarations, synthesis-discipline) and carries a rights-renewed event precisely because an expiry is curable by renewing and a revocation is not. The branch now exists, ordered so a withdrawal outranks a lapse when both hold.
  • Exemptions — two, and both are words that are not behaviours: revision-reflection (a rubric dimension matched by the token revision) and missing (a column heading in renderEvidenceMatrix's table). An exemption names ONE state in ONE module with a reason and a decider, and the checker refuses one whose state no longer exists.
  • Verification: study-workspace suite green with coverage (834 files / 13 5xx tests); check-requirement-scope.mjs passes over the fresh report; 28 rule tests pass; the existing check-test-portfolio.mjs and the other program gates still pass.
  • Marked [x]: YSD-22054.
  • Commit: resolve via git log --grep='22054'.

2026-08-09 — YSD-22055 decision enforcement: a section heading is not a mechanism#

  • The axis the existing green check cannot see. check-decision-log.mjs (YSD-0139) passes on all eighteen Section 0.2 EXT decisions, and what it proves is real: each exists, is approved, by a named person, on a date, with an outcome recorded, and each artifact carries a section headed ## Machine-enforced outcome. None of that reaches YSD-22055's second clause. A heading is a promise. An artifact may say "feature flags encode the segment" and the flag may not exist; it may name an architecture test nobody wrote; it may describe a registry whose members drifted from the approved list two releases ago — and the heading would still be there. The one-word greps for the missing machinery (decisionEnforcement, enforcementAssertion, approvedOutcome, outcomeAssertion) each returned zero files.
  • The doctrine was already in the repository, applied to other people. institutional-policy.ts reports each declared clause of an institution's policy as enforced, unenforceable, or violated, on the principle that a declared clause is a promise; what makes it a control is a mechanism in the running deployment that carries it. YSD-22055 is that test turned on ourselves.
  • What was built. decisions/decision-enforcement.json + tools/yemaya-study/check-decision-enforcement.mjs + check-decision-enforcement.test.mjs (36 rule tests). 45 assertions across all 18 approved decisions, in four kinds — an exported register holds exactly/includes/excludes/orders the approved values; a source file does or does not match a pattern; a path is present or absent; an existing repository checker exits zero.
  • The anti-invention control, which is the load-bearing part. Every enforcement carries a quote, and the check FAILS when that quote is absent from the decision's own artifact. Whoever writes the registry may only encode what the approver actually approved — the expectation cannot be quietly widened, narrowed, or invented, which is the S8.1 rule that the checked party may not supply both sides. It works: the first run rejected "contracts model members/roles from day one" for YSD-0137, because that is the decision-log's paraphrase and not a sentence in ysd-0137.md.
  • The same rule from the other direction. A decision that is NOT approved may not carry enforcements at all. Enforcing a proposed default as though it were approved is precisely the failure the item's second clause names.
  • Assertions are evaluated, not delegated. Binding an outcome to the NAME of a test suite would replace one promise with another, so the checker reads the code itself. Comments are stripped before any literal is read (an apostrophe in prose pairs with the next quote and shifts every literal after it), and both register spellings count — [...] as const and : readonly T[] = [...] — as do Record object literals, whose keys are read without leaking the strings inside their values.
  • Every live assertion was mutation-tested against the real repository, not only against synthetic fixtures: drop each approved member (all occurrences — removing one of two leaves the second and the mutant survives), append an extra, insert an excluded one, reverse an order, delete every match of a present-pattern, plant a match for an absent-pattern, hide a path, reveal a forbidden path, fail a command. All 45 flip. Two needed record-shaped mutations (RELATION_SPECS keys are bare identifiers, so replacing string literals changes nothing) and two absent-patterns were verified by planting: automationAvailable: true into a first-release lens, ELASTICSEARCH_URL into the service config. Both fail as they should.
  • One open conformance question, and the item stays unchecked because of it. YSD-0124's recorded outcome is "local/owned files only; providers in a later phase". Provider deep-link registration is a live, ungated route today (POST /works/:workId/provider-sources), and acquisitionCapabilities reports source.provider-deep-link as supported with no wiring condition. The link-only property the decision attached to providers IS enforced — no representation the workspace generates derives from a provider link — but the phase gate is not, and no machine can rule on whether a phase has arrived. Recorded as an openConformanceQuestion the checker prints on every run and whose evidence pointers it keeps resolving (an observation pointing at nothing rots the same way an enforcement pointing at nothing does). It does not fail the build: the control that says "not yet confirmed" is the unchecked checkbox, not a red build on every branch.
  • One suspicion investigated and withdrawn. YSD-0127 approves V2 as the first instrumented owned title while the shipped connector is SuperTuxKart, so this looked like a second divergence. It is not: acceptance-owned-title-end-to-end.spec.ts runs V2 end to end (connectorId: 'oshun.v2.connector', engineFamily: 'v2'), and STK is the separate open-source seed corpus of YSD-14172/14173.
  • Verification: 36 rule tests pass; the check passes with 45 assertions; the seven sibling program checkers (decision-log, traceability, evidence-index, capability-inventory, reuse-ledger, architecture, stub-scan) all still pass. Wired into both the ci.yml lint job and the study-workspace.yml governance job, rules included.
  • Marked [x]: none. YSD-22055 stays [ ] pending the decision owner's ruling on YSD-0124.
  • Commit: resolve via git log --grep='22055'.

2026-08-09 — YSD-22057 release-gate evidence: every gate knew which build, none knew which machine#

Item. Confirm every mandatory release gate has fresh production-like evidence for the exact release candidate and representative datasets.

The finding. release-refusal.ts is a careful and complete gate: it refuses evidence that is missing, indirect, narrower than the requirement, produced against another candidate, or excepted without an authorized signature. It carried one qualifier on a piece of evidence — againstRevision — and this item names four. There was no field in which "production-like" could be false and no field in which "representative dataset" could be false, so a gate cleared on a unit test over hand-authored records exactly as it would have cleared on a production observation. The programme already knew better in two places and had never joined them up: search-corpora.ts refuses a corpus before reading its latency ("a fast wrong number is the one that gets believed"), capacity.integration.spec.ts refuses an in-memory capacity report as not release-grade ("a hash map has none of them"), and test-evidence-bundle.ts pins hardware-profile and dataset-version per artifact kind. All three are about performance. The nineteen gates that decide whether the product ships — rights, privacy, security, accessibility, epistemic integrity — asked nothing about where their evidence came from.

A judge with nothing to judge. The second half of the finding, and the one that made the first invisible: the only corpus of GateEvidence in the programme was the one release-refusal.spec.ts constructed for itself. atlasPanelGateEvidence produced a single record for a single gate. Nothing assembled the other eighteen, so the decision function had never been shown the repository it was written to judge.

What was built.

  • GateEvidence.conditions, a required discriminated union: an execution names the environment it ran in and the corpus it ran over, and anything else must say not-an-execution and why. Required and discriminated on purpose — an optional field would have left every existing record silently conforming, which is the state this item exists to end.
  • Seven environments, each defined by what it stands in for rather than by a rung on a ladder, because the two mid-tier harnesses substitute opposite halves: the 31 route suites drive the real HTTP app and its auth over doubled stores, and the *.integration.spec.ts suites drive real PostgreSQL and MinIO with no boundary in front of them. Neither is "more integrated"; an ordinal score could not have expressed what a gate needs.
  • Three new fields on each of the nineteen gates, every one read off the gate's own sentence: mustNotSubstitute (Gate 6 is about the store and the boundary, Gate 10 is about a renderer), datasetFloor (Gate 11's sentence is the only one that says "representative data", and it gets representative), and humanSettleableScope — empty for sixteen gates, and for Gate 8 exactly the three dimensions PANEL_GATE_SCOPE already claimed. A spec now holds those two lists to each other.
  • Three refusal grounds: not-production-like, unrepresentative-dataset, and judgement-offered-for-a-measurement. The third closes the hole the second would otherwise have had — without it, every record could escape the conditions test by declaring itself unrunnable.
  • Admissibility is judged before the scope arithmetic and inadmissible evidence is withheld from the coverage union, so a gate reports the conditions problem AND what is now uncovered. Same two-refusal shape a deferred pointer already produced.

The anchor. docs/proposals/yemaya-study-workspace/release-gate-evidence.json records, gate by gate, the evidence that actually exists, and gate-evidence-register.spec.ts runs the whole register through the real programReleaseDecision. Nothing in the JSON says where a run happened: environmentFor derives it from the reference path, because a register that graded its own conditions would be the self-attestation the programme refuses everywhere else. Each slices claim carries a quotation that must appear verbatim in the file it cites, so a corpus cannot be credited with material nobody can point at.

The answer it produced, which is a refusal. All nineteen gates refuse. Eighteen refuse as not-production-like, fifteen of those ending in no-evidence because nothing was left once the inadmissible was withdrawn. Three (Gates 12, 14, 19) reach the corpus test and fail it. And the inversion worth the whole exercise: Gate 10 is the only gate that clears the conditions test, and it is the only gate of the nineteen with no release-gate-*.spec.ts. Before this register, grepping YSD-18139 found release-refusal.ts and its own specs — the judge and nothing it could judge — because the gate's subject is a rendered document and the harness that renders one lives in another project. Its evidence is two real browser journeys, it clears seven of its eight named parts, and it is short of exactly graph-and-canvas-alternative: one work item rather than "accessibility is not done". Gates 1, 3 and 18 likewise come back narrower-than-requirement rather than unevidenced, because their e2e entries survived.

Two open questions the register records rather than resolves. No corpus anywhere in the tree has a provenance other than authored-for-the-test, which is the same gap YSD-19005 (licence the gold-set slices) and YSD-18002 (recruit the cohort) already carry, reached from the other end. And no evidence exists from a deployed or production environment because there is no deployment — which is YSD-18163, also open. The dataset and environment axes independently rediscover two items the checklist already leaves unchecked; that agreement is the reason to believe the axes rather than a coincidence to explain away.

Corrections made while building. Seven gates had their datasetFloor set too high on the first pass and were lowered after reading what the real refusals said: a contrast ratio is a fact about pixels and a deletion is a fact about stores, so neither needs licensed material. Representativeness is now required exactly where the claim is about the material — Gates 8, 9, 11 and 17.

Verification: npx vitest run over the whole library — 13,572 passed, 12 skipped, 833 files. npx tsc --noEmit -p tsconfig.lib.json clean.

Marked [x]: none. YSD-22057 stays [ ]: the confirmation now runs, and what it confirms is that the evidence does not meet the bar. Marking it would be recording the mechanism as the answer.

Commit: resolve via git log --grep='22057'.

2026-08-09 — YSD-22058 operational drills: the programme rehearsed only the procedures nobody runs#

Item. Confirm rollout, rollback, recovery, support, incident, expiry, deletion, backup/restore, provider change, model replacement, and tenant closure procedures have been exercised.

The finding. operational-drills.ts is a demanding evaluator. It refuses a drill that was not measured, extrapolates a small one to full scale and uses the extrapolation only to fail, requires per-kind verifications that separate "the procedure ran" from "the procedure worked", and fails a drill outright when the operator had to improvise a step, on the ground that the document is what is under test. It knew six kinds — restore, deletion, provider-failover, model-replacement, signing-key-rotation, rollback — against the eleven this item names.

The six are not an arbitrary subset. Every one of them is a procedure for undoing or repairing something. Not one runs when nothing is wrong. The missing ones are exactly the procedures that run constantly: rollout, which happens on every release and which rollback exists only to undo; expiry, which fires on a clock with nobody present; support, whose counterparty is a person outside the system; incident, which had eleven runbooks in a document and no drill kind; recovery, which is the objective every continuity plan in the same directory states and which no drill kind could measure; and tenant closure, which had a continuity scenario and no way to exercise it. A taxonomy of things that go wrong left out the things that go right, and "we do it all the time" is a statement about frequency rather than about whether the documented procedure has ever been followed and measured.

The hardest rule in the file had nothing to fail against. undocumentedSteps counts departures from a document and no field named the document. A drill following nothing at all improvises every step and records a count of zero, so the check that exists to catch a wrong plan passed most loudly when there was no plan.

The strictest judge sat next to the weakest claim. ContinuityPlan.lastDrilledAtMs was an integer the plan's own author typed, and it alone separated verified — which this module defines as the difference between a measurement and a hope — from unverified. The evaluator that decides what a drill has to be in order to count was in the same directory and there was no reference between them. Worse, four of the eight continuity scenarios (regional outage, connector revocation, tenant export, tenant closure) had no drill kind at all, so their plans could reach verified on a date that no drill could have produced even in principle.

What was built.

  • Twelve drill kinds, and NAMED_PROCEDURES mapping the item's own eleven words onto them — backup/restore to restore, provider change to provider-failover. recovery and backup/restore stay separate because restoring data and restoring service are different procedures with different objectives. Required verifications for each new kind, each naming something a smooth run leaves untrue: a rollout that halted on its own signal rather than because somebody was watching, an expiry proven by moving the clock rather than by backdating a row.
  • DrillRecord.procedure: the document followed, by reference and quotation, or an explicit null that makes the run inconclusive. Plus operatorRole, and environment in the seven-valued YSD-22057 vocabulary in place of a boolean — a reader needs to know which harness it was, not only that it was not production.
  • procedureExerciseStanding / programProcedureExercise, whose ordering is their whole content: only a drill can rise above mechanism-exercised-not-procedure. A named procedure nothing accounts for throws rather than being skipped, because an answer about eleven procedures cannot be computed from ten.
  • The join: plans cite a drill by id, SCENARIO_DRILL_KINDS is total over the eight scenarios, and verified now means evaluateDrill returned validating.

The anchor. procedure-exercise-register.json records, procedure by procedure, the document an operator would follow and the runs that exist, and procedure-exercise-register.spec.ts puts the register through the real programProcedureExercise. The eleven names are parsed out of this checklist's own sentence, so rewording the item breaks the check. Every quotation must appear verbatim in the file it cites — including the ones inside a nearestGuidance shortfall, because a claim about what a document does not cover is a claim about a document. Environments are derived by the same environmentFor the release-gate register uses, now lifted into release/evidence-environment.ts so one rule serves both.

The answer it produced, which is a refusal. All eleven. Nine have no document an operator could follow at all — and no runbook in docs/runbooks names this workspace: the object-storage runbook's scope table lists yemaya avatars, lilith voice audio and aphrodite recordings and not one study bucket, and the provider-failover runbook's scope list names assistant, Sophia, Isis, messaging and payment. The two that are documented — incident and deletion — share incident-runbooks.md, written for YSD-4100's security incident classes. The programme wrote down what to do when it is attacked and never wrote down what to do on an ordinary bad day.

Both documented ones come back mechanism-exercised-not-procedure, which is the category this item needed and did not have. The deletion saga is driven to completion and through resumption against real PostgreSQL and object storage, the expiry sweep crosses the lapse boundary with the read surfaces checked on the far side, a real restore lands in an isolated environment with the chain and tombstones intact, and migration 0006 applies over seeded volume with a live writer. Calling that nothing would be false; calling it a drill would be false too. It shows the mechanism works when invoked, and a procedure exists to answer whether a person who has not done it before can make it work from the document, under time, on the day it matters.

A claim corrected while building. The register first said nothing in the programme constructs a ContinuityPlan outside its own module's spec. That was wrong: chaos-recovery-coverage.spec.ts holds two real ones — a regional failover with three owned steps, two audiences and a 60-minute objective, and a key compromise. The accurate finding is narrower and better: two plans exist, for two of the eight scenarios, and both are fixtures written to exercise the assessor rather than documents an operator would open during an outage.

Verification: the continuity, reliability and release suites green; npx tsc --noEmit -p tsconfig.lib.json clean; architecture, test-portfolio, release-integrity, stub-scan, evidence-index and doc-conformance checkers all still pass.

Marked [x]: none. YSD-22058 stays [ ]. The confirmation now runs and it answers no, on all eleven. Marking it would record the mechanism as the answer.

Commit: resolve via git log --grep='22058'.

2026-08-09 — YSD-18163 launch-readiness rehearsal: every fault was answered and none was announced#

Item. Run a production-like launch-readiness rehearsal including ingest, model outage, permission change, source expiry, incident, deletion, restore, rollback, support, and status communication.

The finding, first half: a rehearsal is a chain and every mechanism here is a single link. Six of the ten legs — permission change, source expiry, incident, deletion, restore, rollback — are procedures YSD-22058 already asks about one at a time, and running them again separately would add nothing. What this item adds is the word including: the ten are legs of one run, on one person's work, in one sitting. The failures worth finding live in the joins. A restore that runs after a deletion has to not resurrect it. A rollback that runs after a permission change has to not lose it. An expiry sweep that runs after both has to see the world they left behind.

Nothing in the programme could express that. drillCoverage takes the most recent drill per kind, which is a deliberate statement that the kinds are independent. chaos-scenarios.spec.ts injects each fault "at the boundaries the modules take as input", one at a time, each from its own fixture. Both are right about the question they ask, and neither can reach this one. Note also which leg the item puts first: ingest is the only leg that is not a problem, and it is there because it supplies the thing every later leg acts on. Without it there is nothing to expire, delete, restore or apologise for, and ten legs each with their own fixture are ten drills sharing a title.

The finding, second half, and it is the one that matters: the tenth leg has no mechanism at all, and the absence is systematic. Nine of the ten legs are things that happen to somebody. The register asks, leg by leg, how that somebody comes to know. Three have an answer, and all three are the same kind of answer — a refusal at the boundary carrying a real reason: uploads_unavailable to the person mid-upload, rights_denied with the gate's reasons to whoever next tries, and a 410 with a lapsedNotice naming when access lapsed and what bound it. Those are good and this workspace has many of them. They reach the person who was typing. They say nothing to the learner who was away, nothing about scope, nothing about duration, and nothing at all once the request stops being made.

This workspace tells you when you ask. It has no way to tell you when you did not. That is CHANNEL_KINDS, and the unprompted half does not exist:

  • The product ships a customer-facing status channelConsumerShellStatusBanner in libs/oshun/shell-core, four severities, three placements, dismissal and suppression rules, accessibility roles, and an audience list that names scholar and teacher, this workspace's own users — mounted in the customer shell on every page. The study workspace has never published to it: nothing under libs/yemaya or apps/yemaya imports @oshun/shell-core, and the only file in either tree that so much as names the banner type is the module whose docblock exists to record that nothing does.
  • The eleven incident runbooks give Detection, Immediate response, Containment & eradication, Recovery and the owning mechanism. Every step is addressed to the responder. Not one has a step where the people whose work is affected are told anything — which is why YSD-22058's incident drill requires that "everyone the plan names was told inside the window it gives" against a plan that names nobody.
  • YSD-5124's availability model, the code that decides what a learner may still do while analysis is queued, degraded or failed, returns four booleans and no sentence, so even a caller could not tell anybody why. It has no caller: neither workspaceAvailability, analysisResultsAvailable nor isManualActionAvailable is referenced by any route, component or read model in the monorepo outside its own module and spec.
  • alerting.ts routes eight conditions to page, ticket and notice, and all three are destinations inside operations.

The programme built the response to every fault and never the announcement of one.

What was built. continuity/launch-rehearsal.ts:

  • REHEARSAL_LEGS and LEG_PREREQUISITES, with restore depending on deletion — the join a set of drills cannot test — and every leg after the first depending on ingest.
  • The chain itself. A leg names the effect ids of earlier legs it observed, not the ones it could have; carrying in an effect no leg produced, or one that had not finished being produced when the leg began, throws rather than scoring low, because a fabricated chain is not a weak one.
  • The subject. A leg acts on the run's subject or on something an earlier leg produced from it (a restored copy legitimately has its own id); anything else is "a drill that ran during the rehearsal rather than a leg of it".
  • The witness — what the person whose work it is could still do, which surfaces they lost, and the impact — which is the axis no other mechanism in this programme has, and the closing read, because nobody had ever established what a user is left holding after the worst day.
  • announcementsFor, which checks each announceable leg against six ways of being silent while appearing not to be: saying nothing; saying it once it was over; saying it only in advance, so people were told what might happen and never that it had; saying it over the surface the fault had taken away; saying something milder than what happened; and saying it to a room the affected person was not in. The window is thirty minutes, borrowed from docs/runbooks/v6-capacity-management.md's SEV-1 cadence rather than invented, because the study workspace states none — it has no communication step to attach one to.
  • A verdict ladder that names the most basic thing missing: incompletedisjointunwitnessedunclosedsilentsubstitutedrehearsed. Making a disjoint run speak changes nothing while its legs are still ten separate drills.
  • lossLeftUnannounced, which is the one rule that cannot be per-leg. A run can satisfy every announcement check leg by leg and still end with the closing read naming work that did not come back, because the loss is only visible once all the legs are over. Whoever was witnessed during the day has to be told, without asking, in terms at least as severe as what happened, and after the last leg ended — a work-lost notice published mid-day described a loss the restore and the rollback were still expected to undo, and accepting it would let the day's worst finding be laundered by the announcement of the deletion that started it.
  • legStanding / programLaunchRehearsal, keyed to a named release candidate: a rehearsal is evidence about the build it ran on, and one from two releases ago is rehearsed-other-build rather than a pass. A named leg absent from the input throws, as YSD-22058's eleven do. A leg is never graded better than the run it was a leg of: a chained leg inside a run that came back substituted, silent or unclosed is rehearsed-in-a-flawed-run, because a per-leg grade computed without regard to the day around it is how a rehearsal driven entirely over doubles reports ten rehearsed legs and a complete programme.

Five holes found by adversarial review and closed, over two passes — the second pass confirming the first four and finding the fifth. All five had the same shape: a weak record reading as a strong one. (1) A leg inside a rehearsal that was itself substituted, silent or unclosed still counted as rehearsed, so complete could be true for a day nobody was told about; that is what rehearsed-in-a-flawed-run now catches. (2) A closing read reporting the subject gone still graded rehearsed. (3) A notice published before its leg began produced a negative silence and no issue, so a scheduled-maintenance banner could stand in for the incident that followed. (4) The "responsive leg with nothing to be about" finding was recorded and then ignored by the verdict; it is now a disjointness signal (LegFinding.grounded), which is also what makes the prose about support and status communication coming last true of the code. (5) Once (2) was closed, the loss could still be laundered by any earlier unprompted work-lost notice — the announcement of the deletion standing in for the finding that it never came back — so the notice must now postdate the last leg.

The anchor. launch-rehearsal-register.json records, leg by leg, the runs that drive its machinery and every channel the product has, and launch-rehearsal-register.spec.ts puts it through the real programLaunchRehearsal. The ten names are parsed out of this checklist's own sentence and round-tripped. Every quotation must appear verbatim in the file it cites, including the ones inside a nearestChannel shortfall. Environments are derived by the same environmentFor the release-gate and procedure-exercise registers use. candidateRevision is held equal to release-gate-evidence.json's, because two registers naming different candidates have quietly become two questions.

Two absences are scanned rather than asserted. That the workspace has never published to the status channel, and that the availability model has no caller, are claims about what is not there — the kind that rot silently. The spec walks every TypeScript file under libs/yemaya and apps/yemaya and asserts the exact file sets, with both identifiers assembled at run time so the spec's own text cannot satisfy the search it performs. The runbook scan is the same shape. Wire any of them up and this file fails.

The answer it produced, which is a refusal. All ten. Eight are exercised-alone: real runs, most against real PostgreSQL and real object storage on every change, each starting from its own fixture, so what they show is that the leg works by itself and never that it works after the legs that change what it means. Support and status communication are unexercised — nothing in the programme performs them. Zero legs are announced; three are answerable-only; seven tell the affected person nothing at all. And the rehearsal is not merely unrun but unrunnable: two of its ten legs have nothing to run.

Verification: launch-rehearsal.spec.ts and launch-rehearsal-register.spec.ts green, the continuity directory 124 tests, including one that drives every verdict the ladder names so that no rung is a rule nothing can reach; the full library suite 13,662 passed / 12 skipped across 838 files, exit 0; npx tsc --noEmit -p tsconfig.lib.json clean; architecture, test-portfolio, release-integrity, stub-scan, evidence-index, doc-conformance, traceability and decision-enforcement checkers pass. The requirement-scope coverage report was regenerated and that checker passes too — the partial dimension moved from 195/196 to 196/196 evidenced, the one addition being this module's incomplete verdict, now driven by a test that executed it rather than declared in a comment.

Marked [x]: none. YSD-18163 stays [ ]. The rehearsal now has a shape that would recognise it, and no rehearsal has been run — nor could one be, while two of its legs have no mechanism and no environment above service-integrated exists. Marking it would record the shape as the run.

Commit: resolve via git log --grep='18163'.


2026-08-15 — YSD-19025 seeded content: curated, admissible, and unreachable#

Item. EXT Seed exemplar studies, selected Blender open-movie production material, selected open-source game footage, walkthroughs, and templates.

The finding. All five classes existed. Each had a register, each had a judge, each judge was green against the real registers, and seededWorkspaceCoverage reported on the set as a whole — including the two things no per-class judge can see, an empty class and a walkthrough over material an exemplar has already published an account of. Nothing called any of it. seededWorkspaceCoverage and renderSeededWorkspace were imported by one spec and by no application; EXPERT_EXEMPLAR_STUDIES had no importer outside its own module but a spec; and SEED_CLASS_SPECS named each register as a STRING that nothing dereferences. The content a first release ships with was curated, admissible and unreachable, which is content the release does not ship whatever the registers say. The EXT decision this item carried had already been settled by its owner (decisions/ysd-19025.md — bundle Sintel, 1.18 GB, accepted on accessibility rather than size), so what remained was not a decision and not a licence. It was a caller.

What was built. onboarding/shipped-seed.ts composes the five registers and judges every member on the way through — exemplar admissibility recomputed against the catalogue it is handed, so a study admissible against the shipped corpus is not admissible against one that dropped its sources. GET /api/study/onboarding/seed (manifest route seededWorkspace, generated client method, group onboarding, classified read by the existing path-derived matrix) serves it with no project and no tenant read, because this is what the RELEASE carries and a learner with no project yet is exactly who needs it. StudySeedGallery renders it on /studio/study, above the sources library.

Three things the composition adds that no register could. A refusal travels with the gallery, in the judge's own words: a shelf that printed only its survivors would read identically whether the register held seven sound templates or seventy of which seven survived. The walkthrough/exemplar collision is told to BOTH sides and neither is dropped — dropping the walkthrough hides material the release paid for, dropping the exemplar hides a finished study, and saying nothing lets the learner meet the spoiler first, which is the only one of the four outcomes that is actually wrong; the caveat names THAT an account exists and never what it found, so it restores the ordering without being the leak it warns about. And the cold start reports what the install CARRIES beside what the first pass fetches, because two absences read alone say nothing about whether a fresh install has any material at all.

The run, recorded. scripts/record-seed-run.ts reads the route from a running deployment — svc-study-workspace against the dev-compose Postgres and MinIO — and writes seeded-workspace-run.json: status 200, 23,998 bytes, sha256:4c080ba47bad770015439b435e49e30fde14d6b2f05126537de8e15f33193171, 34 members on five shelves (7 studies, 14 production parts, 4 game parts, 2 walkthroughs, 7 templates), nothing withheld, carriedByInstall [sintel-render-1080p], fetchedOnFirstRun [ed-render-480p, stk-grand-prix-recording]. The digest is taken over the response bytes before anything parses them; a digest over a re-serialization would pin the recorder's JSON writer and not the service's answer. A non-200 or a contract violation is recorded as one and exits non-zero.

Verification. shipped-seed.spec.ts 12 tests, every control driven by doctoring the shipped registers — a template broken into a provenance contradiction moves from the shelf to withheld carrying the judge's message; a citation added to a shipped study builds the collision the seed deliberately does not contain and both entries gain their caveat while the shipped seed gains neither; the bundled part flipped back to remote-fetch empties carriedByInstall. seeded-workspace.route.spec.ts 6 tests through the generated client, including that the served body equals the in-process composition and that the tenant gate still refuses a foreign token (403). StudySeedGallery.spec.tsx 7 tests, fixtures parsed through the published response schema before they are served. studio-study-seeded-content.spec.ts 3 chromium tests against the real stack (web → /api/study proxy → service), including an axe scan of the panel. Library suite green; contracts client-smoke/openapi/route-fuzz green; api-group-authorization green; npx tsc --noEmit clean for the library, the service and the web app.

Marked [x]: YSD-19025.

Closing it took its dependent gate's only dependency with it. YSD-19028 was a dependent-gate entry naming one item. What that dependency was hiding is that the machine half of the Phase 1 exit is finished and green: phase-one-exit.ts judges all eight fault kinds, and its integration suite drives a study of each medium through the real HTTP API into the study schema on Postgres, reads it back with assembleQuestionLoop, and gets ok: true with deferredBlockers naming only mastery-evidence — Phase 3's, by YSD-14106. Run here with STUDY_PG_REQUIRED=1 so a down stack would have failed rather than skipped: 5 passed. It stays [ ], on the subject of its own sentence: the only studies that gate has ever judged were driven by a test over footage ffmpeg synthesized in the same run, and the item says prove a LEARNER can. Its register entry is now participants and says so.

Commit: resolve via git log --grep='19025'.


2026-08-15 — YSD-22054 requirement scope: the instrument was reading comments as code#

Item. Confirm tests cover the full requirement scope, including failure, permission, rights, expiry, deletion, accessibility, version, partial, offline, and scale behavior where applicable. Already [x]; this is a repair of its instrument and of the CI lane that runs it, and it appends rather than amends.

How it surfaced. The YSD-19025 pass regenerated the coverage report the check reads — the first fresh one since 2026-08-09 — and the check went from stale-and-failing to fresh-and-failing with six unscoped behaviours. Five of the six were the instrument, in three shapes, and every one of them is the same mistake: the source was read as code without the comments taken out.

  • producerSites named extension-observability-binding.ts:14 as the only place 'error-rate' is produced, and then reported that no test had executed it. Line 14 is a sentence of JSDoc quoting a publisher's declaration. No test executes a comment, so the strongest accusation this checker can make — produced but never reached — was being levelled at prose.
  • classifyLines ended the top-level type union of human-correction.ts at line 50, because a JSDoc sentence ends "(YSD-19120, the causal-comparison family)." and the closing parenthesis of an English aside took the bracket depth negative. Every union member after it read as CODE, so twelve declared states became productions no test could ever have executed.
  • enumeratesRegister knew three spellings of traversal and not the fourth. A spec asserting toEqual([...GOVERNED_SUBJECTS].sort()), or supplying artefacts: [...PHASE_ONE_ARTEFACTS] as a fixture, traverses the whole register and NAMES no member — which is exactly what makes it worth writing, because a spec that spells its members out goes stale the moment the register gains one. Under the old rule the better test read as no test at all.

The fix, with its controls. linesWithoutComments blanks comments in place so line numbers still match the coverage report, and classifyLines, producerSites and declaredStates all read it. A type block now ends at its semicolon or at the next top-level declaration rather than at the first blank line, so documentation between union members no longer closes it. Six rule tests pin each fix beside the control that keeps it from blinding the checker: a state produced inside a string that contains a comment marker still counts; an unterminated type alias still stops at the next top-level declaration, so it cannot swallow the file; a bare mention of a register is still not a traversal, and spreading a different register says nothing about this one; and with the comment blanked, a state nothing supplies is still reported.

The check that the repair did not weaken the gate. The applicable-state count per dimension is identical before and after — 129 / 74 / 105 / 76 / 54 / 130 / 77 / 223 / 7 / 26 — so no state left the checked set; only their standings moved, each into a rule that names why. The 28 planted-hole tests that existed before still pass unchanged.

The sixth finding was real and got a test. PersistenceKind has three members and nothing had ever supplied the third. phaseOneExit refuses anything that is not authoritative-database, so 'unknown' — the observer saying they could not tell where the study ran — is refused, and a gate whose whole qualifier is "on production persistence" must refuse it rather than give it the benefit of the doubt. Asserted now, message and all.

A CI lane was red and the redness was invisible. The workflow produces the coverage report by running the library suite with v8 coverage and no timeout override. Under instrumentation camera-motion.spec.ts's vocabulary sweep takes about thirteen seconds against a five-second default, so it failed there and nowhere else — and vitest writes NO coverage report from a failed run, so the requirement-scope step downstream reported a MISSING report rather than the timeout that caused it. The test now carries an explicit 60 s timeout with the reason written beside it.

Verification. The exact CI command (npx vitest run --coverage.enabled --coverage.provider=v8 --coverage.reporter=json --coverage.include='src/**' --coverage.reportsDirectory=.coverage) now completes: 888 files, 15,217 tests passed, 28 skipped, 0 failed, and writes the report. Against that report: check-requirement-scope.mjs passes — 858 declared states across 10 of 10 named behaviours, 569 by execution, 271 by assertion, 16 by enumeration, 2 exempt — and node --test tools/yemaya-study/check-requirement-scope.test.mjs passes 34. The rest of the battery (architecture, doc-conformance, evidence-index, open-item accountability, release-integrity, stub-scan, traceability) passes.

Marked [x]: none. YSD-22054 was already marked; its evidence-index entry was incomplete with an empty evidence list and a note describing a different concern, which is corrected to proven with the instrument, its rule tests, the exemptions register and the CI workflow as evidence.

Commit: resolve via git log --grep='22054'.


2026-08-15 — YSD-22052/22053 evidence index: the disagreement nobody read backwards#

Items. Enumerate implementation evidence for every checklist item and classify each, both already [x]. This is a repair of four entries and of the rule that should have caught them, and it appends rather than amends.

The finding. check-evidence-index.mjs has always refused an entry classified proven against an item the checklist leaves unticked — two records disagreeing, and the note says which one is usually wrong. The same disagreement read the OTHER way was never checked, and it decays differently: the entry is written while the item is open, the item closes months later, and nobody comes back to it. Four entries were in exactly that state, and three of them still carried the sweep boilerplate — "blocked on a human/external act (recruitment, licensing, participant evaluation, or an owner product decision). Nothing in the tree can close it." — the same sentence the open-item accountability register was built to replace.

  • YSD-22054 was incomplete with an EMPTY evidence list and a note about a different item, while its instrument was gating CI on every push.
  • YSD-10107 and YSD-12102 closed on 2026-08-09, each by building the evaluation instrument its constituencies run through, and each entry still said the item was blocked on people. The instruments are honest about the people: all six atlas templates report unevaluated on all four dimensions with every constituency named silent, atlasPanelGateEvidence covers nothing of Gate 8, and releaseRefusals refuses rather than clears. The panel itself is YSD-18002's and is still open, which is where that work is owned.
  • YSD-19120 closed on 2026-08-14 with the project-aware family, the last of the six it names; the entry still said higher-quality analysis "needs models the box does not have", true of the sweep that wrote it and false of the item.
  • YSD-19128 closed on 2026-08-13/14; the entry still said the phase-exit gate was "blocked by the open items in its own phase".

The repair. All four are proven now, each against the modules and specs that closed it, with a note that says what closed it and — for the two evaluation instruments — what it deliberately does NOT claim. And the checker gained the mirror of its own rule: a classification that states a deficiency (contradicted, incomplete, missing) against a TICKED item is refused, with a message that names both repairs, because which record is wrong is not decidable from inside the checker. Positive-controlled by planting the fault on a healthy entry (YSD-22056) and watching it fail, then restoring.

What the counts say now. 1305 items indexed: 1286 proven, 16 incomplete, 3 contradicted — and the nineteen non-proven entries are exactly the nineteen items the checklist leaves open. The two records agree, item for item, for the first time.

Marked [x]: none.

Commit: resolve via git log --grep='22053'.


2026-08-15 — YSD-19143 shadow validation: the record that could not go stale, and nothing checked#

Item. Run parallel/shadow validation and compare data, rights, playback, annotations, search, and exports before cutover. Already [x]; this gives its record a reader and re-cites its evidence, and it appends rather than amends.

How it surfaced. A sweep of every register in this directory for a reader: grep each file name across tools/, libs/, apps/ and .github/. shadow-validation-verdict.json came back with zero — its only mention anywhere is a sentence in DEVELOPMENT.md saying how to regenerate it.

The finding, which is about a claim rather than a defect. The verdict file says of itself: "Admissibility is DERIVED from the code … so a legacy change flips a verdict here rather than leaving this file stale." That is true of how the verdicts are COMPUTED — every one is derived, none asserted — and it was not true of the file, because deriving them again is something a person had to remember to do. Re-run on 2026-08-15: every status and reason still matches, so the record was accurate; what it did not have was any way for its inaccuracy to be noticed on the day it began.

The reader. shadow-validate.ts --check re-derives the six verdicts and compares them to the record: status and reason exactly, and detail word for word after ONE normalisation — the legacy service mints its ids from the clock (vid_<millis>_<random>) and the data verdict quotes one to show the two id spaces are disjoint. Normalising that number keeps the whole sentence under comparison, rather than dropping detail from the check, which is where a check like this usually goes to die. The file's own summary counts (comparableDimensions, divergedDimensions) are checked against its rows, and a dimension that stopped being derived, or appeared, is drift in both directions. Two rule tests pin it, and the end-to-end control is a planted flip of the data verdict — refused, then restored. Both run in the study-workspace CI lane.

The evidence was pointing at the wrong artefacts. This entry cited three §19.8 migration modules — compatibility-redirect.ts, deprecation-notice.ts, duplicate-path-removal.ts — which are the neighbouring module family of the subsection, and exactly the indirect evidence YSD-22053 says to treat as incomplete. Nothing in them is a shadow validation. The harness, its tests, the verdict and the CI lane are cited now.

What the verdict actually says, unchanged: three of six dimensions have no admissible legacy baseline — rights (the legacy types carry no rights, licence, grant, territory or expiry concept), playback (the Aja adapter records the technical-metadata path as unavailable-simulated-backing, so agreement with it would be the defect), exports (nothing in the legacy service serialises). Of the three comparable, data agrees and annotations and search diverge, both because the new model is more honest than the old: a legacy annotation carries seconds and no rate, and the legacy index has no indeterminate state, so a record the workspace refuses to decide arrives as a plain absence.

Verification. --check passes (6 dimensions, 3 comparable, 2 diverged); npx tsx --test tools/yemaya-study/shadow-validate.test.mjs passes 10; the planted-flip control fails as it should; evidence-index, doc-conformance and the rest of the battery pass.

Marked [x]: none.

Commit: resolve via git log --grep='19143'.


2026-08-15 — YSD-0101/0103: two schemas nothing read, one of them cited in the checker's own header#

Items. Machine-readable capability inventory and reuse-versus-wrap-versus- replace ledger, both already [x]. This makes their $schema files load-bearing, and it appends rather than amends.

How it surfaced. The same reader sweep that found shadow-validation-verdict.json unread: capability-inventory.schema.json and reuse-ledger.schema.json both came back with zero readers.

The finding. check-capability-inventory.mjs opened by saying it fails when "the inventory does not validate against its JSON Schema (dependency-free structural validation implemented below)". The validation was implemented below; the schema was never read. The required-field lists were typed out a second time in JavaScript, and the two copies had already come apart:

  • the schema requires appsPath on every domain entry and the hand copy did not list it, so an entry omitting the key satisfied the checker and violated the schema it claimed to be checked against;
  • additionalProperties: false is on every object in the schema and was enforced nowhere, so a stray or misspelled field passed;
  • the schema is stronger in three more ways the copy never had — integer with minimum: 0 on the counts, minLength floors under every free-text field, and contributesTo required on dataAuthority.

check-reuse-ledger.mjs never mentioned its schema at all, while re-implementing the status vocabulary, the disposition vocabulary, the twenty-character rationale floor and the conditional that makes an ownerDecision block mandatory for a partial or fixture-backed entry — every one of which the schema already states, the last as an if/then.

Both registers pass the schema as they stand, so the drift was latent rather than live. Latent is what this programme's registers are for: the day it stops being latent is the day nobody is looking.

The validator, and the refusal that makes it safe. tools/yemaya-study/json-schema-subset.mjs is dependency-free (the repo root deliberately has no ajv) and implements exactly the keywords the two schemas use. A partial validator that silently ignores what it cannot read is the same failure one level up — the caller believes the document was checked — so it THROWS on any keyword outside its supported set rather than returning "no problems". Adding a keyword to either schema therefore extends the validator or fails loudly, and can never quietly narrow what is checked.

What the checkers kept. Everything the schema cannot say: agreement with domains.json on owner, paths and status; paths that exist in this working tree; the fourteen named domains and no others; the ledger's own decisionPolicy naming partial and fixture-backed; the §5.2 table parsed out of the proposal; and the pending-decision count YSD-0139's gate consumes. The duplicated shape checks are gone.

Verification. Seven rule tests, including the refusal in three positions (top level, nested definition, and a format neither schema uses), and a planted violation of the two rules the hand copies had lost — a domain entry with appsPath deleted and one carrying an unknown field — both reported against the REAL inventory. Both checkers pass; the battery passes; the tests run in CI's lint job beside the two checks and are listed in DEVELOPMENT.md's block, which check-doc-conformance holds equal to that job.

Marked [x]: none.

Commit: resolve via git log --grep='0103'.


2026-08-16 — the nineteen open items, re-derived one at a time#

Why. The accountability register's own $comment says a machineSliceRemaining: false "is worth exactly the pass that last looked". Every one of the nineteen still said false. This is that pass: each entry's claim re-derived from the machinery it names, rather than read.

Seventeen held, and four things did not.

1. A study surface this session added was accounted for by nothing. check-accessibility-evidence.ts refused StudySeedGallery.tsx — "belongs to no surface and is not declined; until each is placed, a change to it stales nothing and its accessibility is nobody's evidence". It is a seeded-content surface now. The checker is in CI's lint job and my earlier battery had run only the .mjs checkers, so the tsx one went unrun: a battery is a list, and a list that grew a member in another language has a hole in it.

2. The modalities lane claimed a reach its own selector could not have. Its surfacesByFlow.contrast names 21 surfaces, recorded in August 2026 from a measurement of axe's evaluated-node list "on /studio/study" — a PAGE-WIDE scan. The lane's own scans pass include: ['[data-study-app]'], and that route renders data-study-app as ONE SIBLING among the panels: page.tsx composes home, sources, manual-mode, the workspace shell and the rest beside it, and the marker appears in exactly one component. Two independent measurements agree — a walk of the import graph from StudyWorkspaceApp.tsx reaches 17 files spanning 3 of the 21 claimed surfaces, and reading page.tsx shows ten of them rendered as siblings outright. The reduced-motion walk had the same shape ([data-study-app] *).

The repair is the instrument, not the claim. Narrowing the register would have described a scan nobody wanted; the recorded measurement is what the lane should have been doing. The contrast scans are page-wide now and the motion walk covers every element the route composes. The widened lane passes — 15 tests, 6.4 minutes, against the real stack — so the register's 21-surface claim is true for the first time, and the new surface joins it for contrast and zoom.

3. check-test-portfolio was red on a module the web app imports. accessibility/client.ts is the browser-safe entry point — it exists because the main barrel reaches node:crypto, so importing policy from it would pull a Node builtin into a browser bundle — and no spec reached it by any import path. Its header states the rule that makes it an entry point ("everything reachable from here must be pure") and nothing checked that either. The spec now walks the real relative-import graph from it and fails on the first node: specifier, naming the file that pulled it in; planting node:crypto in region-text-alternatives.ts fails it, as the control. It also calls the policy rather than only naming it: a bounding box a shape fills a quarter of overstates it, one it fills 95% of does not, and a box of no area cannot overstate anything.

4. The program-completion gate understated its own dependencies by fifteen. YSD-22061's first clause is "every in-scope item is proven" and its dependsOn named three. A reader saw an item three closes from done that is eighteen. It names the whole open set now, which the checker holds live: an item closing without this entry being edited becomes a gate waiting on something already done, and it refuses that.

What held, with what it was checked against. YSD-18011's six relation cases and YSD-18012's six path conditions are the item's words, exported (RELATION_JUDGEMENTS × RELATION_GRANULARITIES, PATH_CONDITIONS); YSD-18010's six live in GOLD_CASE_KINDS, ANNOTATOR_EXPERIENCE, ANNOTATION_ANSWER_MODELS and bounded-readings.ts's counter-reading stance — all six exist and every one of those items says INCLUDE, which is corpus content over cleared material. YSD-19108's floor is RELIABILITY_MIN_STUDIES = 10 and an empty population is refused with three faults, not passed. YSD-19064's four named lens gates exist. YSD-22058's eleven procedures all carry a runbook and eight carry an exercised mechanism. YSD-18163's ten legs all carry a mechanism and three carry a channel. YSD-16065 resolves eight journeys over zero recorded sessions. YSD-22057 is the sharpest of them: environmentFor derives an evidence environment from the file path, and the ladder tops out at browser for anything the repository can run — no lane here can produce deployed or production evidence, so the eighteen gates refused not-production-like are refused structurally and not for want of citing something. The three dependent gates besides YSD-22061 name exactly the open items of their own sections.

Marked [x]: none. Nineteen still open, and every machineSliceRemaining: false has now been looked at rather than inherited.

Commit: resolve via git log --grep='18107'.


2026-08-16 — YSD-22052: a citation that resolves to a directory#

Item. Enumerate implementation evidence for every checklist item, already [x]. Appends rather than amends.

The finding. check-evidence-index.mjs requires every proven pointer to resolve on disk, and existsSync is true of a directory. Thirty-one citations across twenty-four entries pointed at three SOURCE DIRECTORIES — src/onboarding, src/scale/, src/rollout/ — which is the module family an item shares with its neighbours rather than the artifact that proves it: exactly the indirect evidence YSD-22053 says to treat as incomplete, wearing a resolving pointer's clothes.

It was not harmless. Twenty of the twenty-four resolve to a module in the cited directory that names the item in its own text. Four do not, and three of those are implemented outside the directory they cited: YSD-14177's empty/partial/no-model/no-permission/source-expired states are policies/surface-states.ts; YSD-17100's residency definitions are libs/contracts/src/study/deployment-residency.ts; YSD-17106's disaster-recovery documentation is continuity/disaster-recovery.ts; YSD-17107's drills are continuity/operational-drills.ts. A directory citation cannot be wrong about which file, which is why it stayed.

The repair. All twenty-four now cite the implementing module, and every replacement was checked twice before it was written: the path resolves to a file, and that file names the item id in its own text. The rule is enforced now — a citation must resolve to a FILE — with one sanctioned exception, migration directories, taken from the evidence log's own immutable-link contract (YSD-0112): a migration is pinned as "the repo path of the migration directory plus the SHA that introduced it", because migration files are append-only by repo policy and the directory is therefore the stable artifact. Positive-controlled by planting a directory citation on YSD-17050 and watching it fail.

Marked [x]: none.

Commit: resolve via git log --grep='22052'.


2026-08-19 — YSD-18107: what the seven unmounted panels are waiting for, measured rather than described#

Item. Maintain accessibility automation and manual evidence for every critical flow, [ ], and its remaining machine slice: the seven finished panels no route reaches.

The finding. The slice was described in prose on 2026-08-17 as a choice that is "not uniform" — "for some it is a BFF projection over a store that exists, and for at least one it is the domain itself" — with EvidencePack cited as real "across contracts, the persistence registry and a Veritas API client". Re-measured against the import graph, the persistence registry and the Prisma schema, that was wrong three ways.

There are two EvidencePack contracts. The contract under contracts/common declares the object all three evidence panels cite — slug, excerpts, retrievalTrace, consumers. libs/contracts/src/veritas/index.ts declares a different object of the same name — storyId, claimIds, evidenceItems — and it is the Veritas one the API client serves at /veritas/evidence-packs. The reach cited belonged to the other contract.

A table with no writer is not a store. The common contract's table, v1_cross_cutting_evidence_pack, is in schema.prisma and in the 20260527184417_v1_initial migration. Nothing outside the generated Prisma client has ever named its accessor crossCuttingEvidencePack.

No panel imports the contract it cites. All seven declare their own view types, copied by hand. EvidencePackKind, EvidencePackStatus, EvidencePackVerificationStatus and CanonicalCitationVerifierKind all match their contracts member for member today, and nothing keeps them matching: a contract change cannot break a panel and typecheck cannot report the drift.

The measurement is now a rule. accessibility/unmounted-backing.ts re-derives the three premises every run — the projection's producers, the contract objects' consumers, the table's callers — and fails when a write-off has stopped being true or a declared cost no longer matches the repository. The view type is matched by NAME (a superset: it cannot miss a producer) and the contract by IMPORT (four files name AuthoringJob in prose and import nothing), and the question is asked of the named OBJECTS rather than the module, because common/citation.ts holds CanonicalCitation, which the panel writes, beside CitationTrail, which the registry registers.

And one copy had already drifted. All thirty string-literal unions the seven panels declare were held against every z.enum in the cited contracts and in the modules those contracts import. Twenty-three are exact copies — two of them from a module the panel's header never names, and one from an enum the contract does not export (factCheckStatus is declared inline inside an object in authoring-job.ts; a vocabulary that cannot be imported is one that gets retyped). Five name vocabularies libs/contracts does not define at all, and all five sit on EvidenceExportBundlePanel, so a producer for it must canonicalize export formats, redaction levels, audit audiences and ten scope flags before it can assemble anything. And GroundedReportGroundingLevel declares FOUR levels where GroundedReport.groundingSummary.level carries SIX: weakly_grounded and unknown are unrepresentable in the panel that promotes the report. The panel's readiness evaluator branches on that field three times and mirrors the contract's own three refinements exactly, so the logic is right and the type cannot hold two of the six states — harmless while nothing produces the view, wrong on the first day something does. Recorded as a hand measurement with its date and method and deliberately not made a rule: a gate over seven files this decision may retire is a gate written for a world that may not exist.

Measured: 3 producer-over-an-empty-store, 1 store-first (SourceSet, which libs/oshun/evidence-sophia validates and hashes with no table to hold it), 3 domain-first.

Why it became EXT. Eight vocabulary probes over the proposal return zero hits: it asks for none of the seven. Every one shipped under a V1-SOP item in docs/releases/v1/specs/todos.md marked [x] on an acceptance that named the component, its tests and a clean typecheck, and never a route. Mounting is V1-SOP work in V1-SOP's code; retiring moves files another initiative marked done; and declining is unavailable, because the map's established decline reason — "belongs to X, which owns its own accessibility evidence" — would be false: this is the only accessibility surface map in the repository. Drafted as decisions/ysd-18107.md with three pending controls, actor reclassified participants → decision-owner, and YSD-16005 now depends on it rather than claiming a machine slice it had already said was not its own.

Marked [x]: none.

Commit: resolve via git log --grep='18107'.


2026-08-19 — YSD-19006: the Phase 0 exit gate judged two of its four clauses over nothing, and passed#

Item. Phase 0 exit: prove teams can implement independently without competing identities, evidence models, rights decisions, or duplicate domain engines, [ ]. Its accountability entry read, in full, "Closes when its Phase-0 contents close", with actor dependent-gate — the register's own class for an item that owes nothing of its own.

The finding. It owes something of its own. policies/phase-zero-exit.ts judges all four of the item's clauses, purely and correctly, and only two of them are judged over anything. evidenceModelFaults reads AUTHORSHIP_VOCABULARIES; competingDecisionAuthorities reads DECISION_AUTHORITIES. The other two inputs default to empty — labellings to {}, computations to [] — so collapsed-distinction, duplicate-domain-engine and copied-theory, three of the five blocker kinds, cannot be reported by a call that supplies neither. No register of the workspace's computations exists anywhere in the tree: WorkspaceComputation is a shape with a judge and no corpus.

Every phase-zero primitive has exactly one caller — its own spec. That spec's workspace-wide case was named "passes over the workspace as it stands", handed in ONE labelling (markdown-export) out of the twenty-three surfaces the accessibility map declares and ZERO computations, and asserted blockers: []. The module is scrupulous about the difference — it returns examined.labelledSurfaces precisely so a pass cannot be read wider than it is — and nothing read the field. Phases 1, 2 and 5 each got a runner for exactly this reason; phase zero never did, and unlike theirs its inputs are repository facts rather than a deployment's, so it needs no stack.

A gate that passes vacuously where its three siblings refuse. phaseOneExit([]) faults on every required medium; phaseFiveExit returns no-study-started rather than a rate; the phase-two reader withholds a verdict it cannot take over the whole run. phaseZeroExit() returned passed: true. unexamined-population is now a blocker kind, emitted once per empty input and naming what that silences, so the default call refuses.

And a frozen count wearing a rule's name. The spec case "covers every vocabulary the tree declares, not a sample of them" read no tree: it asserted AUTHORSHIP_VOCABULARIES.length >= 14, beside a comment recording that sixteen vocabularies existed when the gate was written — a number counted once, by hand, that does not match the fourteen entries beside it. The satisfies Record<…> on each entry is real enforcement and binds only the entries already present, so a vocabulary added next week is the one nobody reduced and nothing would say so. It now collects the types every authorKind field takes across the library and asserts set EQUALITY with the register, in both directions — a stale entry is a finding too, and a sweep that broke would leave every entry unaccounted for rather than passing an empty comparison. Measured today: thirteen types on both sides, equal. The types are read off the FIELD rather than off a *AuthorKind name, and an indexed access is skipped with its reason written down.

Marked [x]: none. The item is reclassified dependent-gate → implementation with its machine slice named: the two missing corpora and a runner.

Commit: resolve via git log --grep='19006'.


2026-08-19 — YSD-19065: the Phase 3 gate and its read model were both finished, and nothing joined them#

Item. Phase 3 exit: prove the primary filmmaking and performance-learning journey is coherent from exact source evidence through original practice, [ ]. Its accountability entry read, in full, "Closes when its Phase-3 contents close", with actor dependent-gate.

The finding. It was modelled differently from its two identically-shaped siblings. YSD-19028 (Phase 1) and YSD-19108 (Phase 5) are participants with a store landing and a runner as the join, each built because "an integration spec that drives the API, writes three studies and grades them proves the reader works and is not a join: it can only ever answer for its own fixtures, and the item is about somebody else's work." Phase 3 had both halves — phaseThreeExit judging eleven fault kinds across four qualifiers, and read-models/study-to-practice-arc.ts assembling one subject's arc behind one question with every hop a real join — and the only thing carrying one to the other was phase-three-exit.integration.spec.ts, over two studies it drove through the HTTP routes itself in the same run.

The runner is built, modelled on the Phase 1 and Phase 5 tools, and it was verified against the dev stack over two projects and four questions from an earlier end-to-end run — records it did not create. With the notebook store wired it reports evidence-unattributable on both subjects and three standing workspace faults: art-direction, character and visual-design shipped in YSD-19060's scope and REWATCH_FOCUSES maps no focus to any of them, so evidence for those craft dimensions arrives with nothing declaring which subject it belongs to.

Two selection rules are the tool's own, and the second is a correction it needed. The gate grades the FIRST journey it is handed per subject, the same discipline phaseOneExit applies to media, so journeys are presented in the store's own total order and nothing ranks, scores or picks a best one. And a study whose evidence is entirely UNSTAMPED is kept — in both subjects. The first draft dropped it, on the reasoning that an empty subject station means the study is not that subject's journey; that made evidence-unattributable unreachable and reported subject-unstudied instead, which says "nobody studied this" where the truth is "somebody did and nothing says what of". It is not hypothetical: both observations in the dev deployment are unstamped, and under the first rule the gate was told neither subject had been studied at all.

Marked [x]: none. The item is reclassified dependent-gate → participants with the runner recorded as the join. What remains is the subject of its sentence: a journey somebody actually walked.

Commit: resolve via git log --grep='19065'.


2026-08-20 — YSD-19006: the gate's two empty populations, and the badge nothing called#

Item. Phase 0 exit: prove teams can implement independently without competing identities, evidence models, rights decisions, or duplicate domain engines, [ ]. Its 2026-08-19 entry named the machine slice exactly: "TWO REGISTERS AND A RUNNER, and the registers are the work."

Both registers and the runner are built. policies/workspace-computations.ts declares every workspace computation read against Hathor's four owned theories. Each of the four EVIDENCE_BINDINGS is recorded as held with the field, guard or returned member it was read off, or absent with what is missing, in a Record<EvidenceBinding, BindingReading> — so a declaration that skips a binding fails the build rather than reading as a denial. policies/record-labellings.ts declares every labelling of an epistemic record, holding the real exported function rather than a copy of what it does, and naming the exact expression the surface renders through so a surface that stops calling it is a finding rather than a silent pass. tools/yemaya-study/run-phase-zero-exit.ts carries both to the gate. It is the fourth phase runner and the only one that needs no deployment: its subject is the source, so it sweeps the tree — walked, never git greped, because git grep reads the index and an untracked file is invisible to it.

The first scoping pass was wrong, in the direction a keyword pass is always wrong. The 2026-08-19 note said "nothing in the study workspace computes cuts-per-minute, average shot duration, a tension curve or an MDA record". THREE modules compute average shot duration. It had scoped off ANALYSIS_METHODS, which lists what a deployment can RUN rather than what the library computes, and a source pass misses them too unless it knows that the separator is a property of the phrase: this tree writes shot-duration, and a search for shot duration returns shot-duration-distribution.ts not at all. The tells now use [-_ ]? between words, and the register's own worst case is a test: unfindable-declaration fires when a pattern cannot find a module the register already knows about, because a sweep that cannot find what is written down cannot find what is not.

The labelling half found that epistemicBadge had no product call site at all. Ten <EpistemicTypeBadge> sites across the studio, and not one passed authorKind — so every one took the component's TYPE branch, which the component itself documents as "correct for a section heading or a legend entry … and wrong for a record". Four surfaces, all fixed here:

  • the PDF printed [interpretation] — the stored identifier, author dropped — while the Markdown badge beside it printed Interpretation and would have printed Interpretation · Model suggestion for a machine's reading. Two notations of one claim that did not say the same thing, in the artifact furthest from a correction;
  • the analytical CSV's epistemicType cell was the bare type. A standing column now carries the layer, so the type column stays machine-filterable and the distinction lives beside it;
  • LensOutputItem dropped author on the way out of the read model, so no lens renderer could have badged a machine's reading as a suggestion even if it had wanted to — the rule the presentation layer cannot reach, one level further down than the time it was found in the surfaces;
  • the epistemic record trail and the lens inspector badged records with their section's type.

The gate runs, and refuses. Three blockers on the first run, two after the fix below. policies/pacing-metrics.ts reported a shot rhythm — a mean and a coefficient of variation over { startMs, endMs } shots, with a second private coefficientOfVariation beside the one in shot-duration-distribution.ts to drift away from. That is not a description of a measurement; it is Hathor's design-time calculation ("over shots nobody anchored to a source") reimplemented. computePacing now takes ShotRecords and reports computeShotDistribution's own distribution WHOLE — projecting it back to a count and a mean would put the uncertainty and the exclusions straight back in the bin they were taken out of.

That fix needed the gate's model widened, and the gap was real. WorkspaceComputation.delegatesToAdapter says "the owning domain computes this"; a module that instead calls the workspace's own measurement had no way to say so, so duplicate-domain-engine was unclosable by construction — two modules touching one capability were a duplicate whatever either of them did, including the one thing that fixes it. delegatesToModuleId says it, and it is not an escape hatch: the register refuses a delegation to a module it does not declare, to one sitting next to a different capability, or to one that delegates onward, because written on both of two modules it would clear the fault while leaving two implementations standing.

What remains is one computation and it needs an instrument that does not exist. suggestions/project-shot-scale.ts#scoreSourceRelative reports a source's own shot-duration mean and standard deviation as the reference a shot's mark is taken against, and prints them in its statement. It cannot be closed by editing the module: its producer reads cut times out of detectShotBoundariesInVideo, and nothing in the estate models how uncertain a detected boundary is — so an uncertaintyMs written there would be a number nobody measured, and delegating to computeShotDistribution needs the same field and is blocked on the same thing. Worth recording beside it: computeShotDistribution itself has no product caller, so the binding it enforces has never been satisfied by real material either.

Marked [x]: none. The item still depends on YSD-19005, which is licensing rather than repository work. machineSliceRemaining stays true and now names the boundary-uncertainty measurement rather than the two registers.

Verified. 15,432 library tests, 1,371 studio tests, tsc -p tsconfig.lib.json clean, the web typecheck ratchet clean with 0 new errors, the stub scan at 0 findings, and the runner's own test file wired into CI as the completeness gate — a module somebody adds that computes one of Hathor's four quantities, or a surface that labels an epistemic record, fails there rather than being quietly absent from a corpus that still looks whole.

Commit: resolve via git log --grep='19006'.

Addendum, same day — the remaining blocker was scoped, and it is three different kinds of work. Closing suggestions/project-shot-scale.ts#scoreSourceRelative means giving it the three bindings it lacks and then delegating, and they do not cost the same:

  • carried-uncertainty is buildable, and it is a projection of a measurement rather than a number nobody took. adaptive-shot-detection.ts already distinguishes its two boundary kinds — a hard cut is a peak at one frame, so the change happened inside the interval since the previous frame; a gradual transition is a RUN placed at its excess-weighted centroid, so the boundary is uncertain across the run's extent. Both figures are in the signal the detector already holds. It ripples through detectShotBoundaries' two callers, the release-gate comparison, ProjectShot and readProjectShotUnits;
  • accounted-exclusions is cheap and half-done: the unit reader already discards spans below minimumShotSeconds and manage-analysis.ts already writes discardedShots onto the detection. They reach the RECORD and not the COMPUTATION, so the norm cannot say what was left out of it;
  • required-qualitative-context is a product ruling and not a field. qualitativeContext occurs in exactly two modules in the whole estate and no route, command or analysis request carries a person's sentence into either. That is also why computeShotDistribution has no product caller: the discipline it enforces has no path that can meet it. Whether an automated run must collect a human reading before it may report a tempo has to be decided before it can be built, and deciding it closes the same hole under YSD-9183.

Recorded on the accountability entry rather than started, because the first part touches a persisted detection payload on a live analysis path and the third cannot be started at all until somebody rules.