fix(experiment): устранен повторный gate принятия опоры

- Зачем:
  - канонический Ryzen-прогон не должен повторно запрашивать уже данное решение владельца до порога 30%.
- Что:
  - восстановлен короткий контракт approval и удалена лишняя схема owner-package.
  - разрешена парная nullable-опора для диагностик без ослабления обязательных gates.
  - NUL в путях отклоняется обезличенной ошибкой до загрузки моделей.
- Проверка:
  - uv run pytest -q: 457 passed, 2 skipped.
  - обязательный набор эксперимента: 110 passed; целевой Ruff пройден.
  - независимые code и task/taste review закрыты.
This commit is contained in:
Dmitry Dementiev committed 2026-09-04 17:48:36 +03:00
1 parent 0cd105927b
commit d71aec4ab2
3 files changed
+99 -1272

No files matched your search

@@ -186,20 +186,25 @@ mono/16 kHz float32. Публичные документы получают то
после единственного закрепленного преобразования, а ни один путь, ID или после единственного закрепленного преобразования, а ни один путь, ID или
интервал не попадает в stdout режима `--validate-only`. интервал не попадает в stdout режима `--validate-only`.
### Шаг 2. Создать ASR-sidecar и опорную разметку ### Шаг 2. Подготовить ASR-sidecar и доступную опорную разметку
Действующий ASR выполняется один раз для каждой полной записи до измерений. Действующий ASR выполняется один раз для каждой полной записи до измерений.
Sidecar хранит канонические слова с временной привязкой и нормализованный текст; Sidecar хранит канонические слова с временной привязкой и нормализованный текст;
fragment-sidecar получается детерминированным срезом той же версии. Затем fragment-sidecar получается детерминированным срезом той же версии. Затем
человек прослушивает каждый фрагмент целиком, фиксирует ожидаемое число человек прослушивает каждый фрагмент целиком и фиксирует ожидаемое число
голосовых кластеров для фрагментов и полных записей и создает опорную разметку, голосовых кластеров для фрагментов и полных записей. Опорная разметка фрагмента
достаточную для mapped purity и выбора мест прослушивания. Опорная разметка необязательна: `reference_path` и `reference_sha256` либо заполнены вместе, либо
остается приближенной и не объявляется истиной границ. оба равны `null`. При наличии разметки стенд считает mapped purity и выбирает
места прослушивания; сама разметка остается приближенной и не объявляется
истиной границ.
Критерий завершения: для каждого фрагмента и каждой полной записи есть Критерий завершения: для каждого фрагмента и каждой полной записи есть
подтвержденное человеком `expected_cluster_count` в диапазоне `2..4`, один подтвержденное человеком `expected_cluster_count` в диапазоне `2..4`, один
неизменяемый sidecar и SHA-256; для фрагментов также есть опорная разметка. неизменяемый sidecar и SHA-256. Отсутствие опорной разметки не блокирует
Числа старых контролей не копируются. измерение и не ослабляет остальные gates. Для канонического Ryzen-прогона
используются подтвержденные владельцем числа `3 / 2 / 2`; их привязывают к
источникам по хешам, а не по порядку нейтральных имен. Повторное прослушивание
для подсчета голосов не требуется.
### Шаг 3. Пройти пользовательский gate ### Шаг 3. Пройти пользовательский gate
@@ -216,6 +221,10 @@ fragment-sidecar получается детерминированным сре
Критерий завершения: в закрытом manifest есть timestamped decision `approved`. Критерий завершения: в закрытом manifest есть timestamped decision `approved`.
Без него исход `needs-user`; стенд не загружает модели. Без него исход `needs-user`; стенд не загружает модели.
В каноническом Ryzen-прогоне решением `approved` служит датированный handoff
владельца в комментарии к задаче #46. Нового подтверждения до достижения
30%-го порога не требуется.
## Измеряемая величина и A/B ## Измеряемая величина и A/B
Время W0 - wall вокруг одного `diarizer.process(samples)`. Время кандидата - Время W0 - wall вокруг одного `diarizer.process(samples)`. Время кандидата -
@@ -333,9 +342,10 @@ hashes и один pair ID; рассчитаны ускорение, CPU и RSS
- каноническая нумерация меток детерминирована на повторном расчете метрик; - каноническая нумерация меток детерминирована на повторном расчете метрик;
- все inputs, параметры и output artifacts имеют SHA-256. - все inputs, параметры и output artifacts имеют SHA-256.
Purity и число слов без говорящего сравниваются с новым `W0-FRESH`. Ухудшение Purity для фрагментов с опорной разметкой и число слов без говорящего
не дает автоматического `fail`, но обязательно добавляет все затронутые места в сравниваются с новым `W0-FRESH`. Ухудшение не дает автоматического `fail`, но
слуховой gate. Старые абсолютные значения не участвуют. обязательно добавляет все затронутые места в слуховой gate. Старые абсолютные
значения не участвуют.
Открытый диагностикой слуховой gate не прерывает шаги 5-6. Стенд накапливает Открытый диагностикой слуховой gate не прерывает шаги 5-6. Стенд накапливает
затронутые места и добавляет их в материал шага 7. Состояние `needs-user` из-за затронутые места и добавляет их в материал шага 7. Состояние `needs-user` из-за
@@ -353,7 +363,7 @@ Purity и число слов без говорящего сравниваютс
- каждая итоговая speaker label принадлежит `1..4`; - каждая итоговая speaker label принадлежит `1..4`;
- число и суммарная длительность интервалов по каждому каналу; - число и суммарная длительность интервалов по каждому каналу;
- число коротких интервалов, overlap ratio, unmatched words, малые/остаточные - число коротких интервалов, overlap ratio, unmatched words, малые/остаточные
кластеры и mapped purity относительно свежей опорной разметки; кластеры и mapped purity для фрагментов с опорной разметкой;
- wall отдельно для model inference и segment post-processing. - wall отдельно для model inference и segment post-processing.
Любой неверный shape/value/label или эффективная конфигурация, отличная от Любой неверный shape/value/label или эффективная конфигурация, отличная от
@@ -390,7 +400,7 @@ runtime/model/build artifact или закрытый вход не попада
- versioned manifest со schema, clean local-transcriber source commit, - versioned manifest со schema, clean local-transcriber source commit,
source/model/runtime/build hashes и расписанием; source/model/runtime/build hashes и расписанием;
- исходные ссылки, подготовленные WAV и recipe преобразования; - исходные ссылки, подготовленные WAV и recipe преобразования;
- ASR-sidecar и опорную разметку; - ASR-sidecar и имеющуюся опорную разметку;
- raw per-fragment results, intervals, word assignments и probability - raw per-fragment results, intervals, word assignments и probability
diagnostics; diagnostics;
- machine/power/runtime guards и stderr/stdout logs; - machine/power/runtime guards и stderr/stdout logs;
@@ -458,8 +468,9 @@ privacy scan. Реализация стенда фиксируется чист
## Критерии завершения эксперимента ## Критерии завершения эксперимента
- [ ] Свежая опора подтверждена владельцем и не наследует старые контрольные - [ ] Свежая опора собрана в новом закрытом хранилище; подтвержденные входы,
значения. доступная разметка и числа голосов связаны по хешам, а старые manifest,
расписание и результаты не наследуются.
- [ ] Runtime, модель, сборка и оба рецепта закреплены и проверены до моделей. - [ ] Runtime, модель, сборка и оба рецепта закреплены и проверены до моделей.
- [ ] Clean local-transcriber source commit сохранен в закрытом хранилище и - [ ] Clean local-transcriber source commit сохранен в закрытом хранилище и
публичном отчете, а `--validate-only` проверяет его до моделей. публичном отчете, а `--validate-only` проверяет его до моделей.
@@ -5,13 +5,11 @@ from __future__ import annotations
import argparse import argparse
import copy import copy
import ctypes import ctypes
import errno
import hashlib import hashlib
import json import json
import math import math
import os import os
import re import re
import stat
import statistics import statistics
import subprocess import subprocess
import sys import sys
@@ -46,51 +44,10 @@ TOP_LEVEL_KEYS = {
"schedule", "schedule",
"rss_sample_interval_ms", "rss_sample_interval_ms",
} }
FRESH_BASIS_KEYS = {"marker", "owner_package", "approval"} FRESH_BASIS_KEYS = {"marker", "approval"}
OWNER_PACKAGE_LINK_KEYS = {"path", "sha256", "generated_at"} APPROVAL_KEYS = {"decision", "timestamp"}
APPROVAL_LINK_KEYS = {
"path",
"sha256",
"actor",
"status",
"decision",
"timestamp",
}
OWNER_PACKAGE_SCHEMA = "local-transcriber.owner-listening-package.v1"
OWNER_PACKAGE_KEYS = {"schema", "generated_at", "references"}
OWNER_PACKAGE_REFERENCE_KEYS = {
"neutral_alias",
"canonical_fragment_audio_sha256",
"final_reference_sha256",
"full_recording_sha256",
}
REFERENCE_ACCEPTANCE_SCHEMA = "local-transcriber.reference-owner-acceptance.v1"
REFERENCE_ACCEPTANCE_KEYS = {
"schema",
"actor",
"status",
"decision",
"timestamp",
"owner_package_sha256",
"references",
"step3",
}
REFERENCE_ACCEPTANCE_ROW_KEYS = OWNER_PACKAGE_REFERENCE_KEYS | {
"fragment_expected_cluster_count",
"full_recording_expected_cluster_count",
"fragment_review_complete",
"full_recording_count_review_complete",
"reference_accepted",
}
STEP3_KEYS = {
"recording_use_approved",
"inputs_and_counts_approved",
"full_recordings_available",
"final_hearing_gate_committed",
}
SOURCE_KEYS = {"commit"} SOURCE_KEYS = {"commit"}
INPUT_KEYS = { INPUT_KEYS = {
"neutral_alias",
"source_path", "source_path",
"source_sha256", "source_sha256",
"source_size_bytes", "source_size_bytes",
@@ -355,294 +312,45 @@ def build_full_recording_schedule(
return result return result
def _aware_datetime(value: Any) -> datetime: def _resolve(root: Path, value: Any) -> Path:
if not isinstance(value, str):
raise PreflightError("approval-timestamp")
try:
parsed = datetime.fromisoformat(value)
except ValueError as exc:
raise PreflightError("approval-timestamp") from exc
if parsed.tzinfo is None or parsed.utcoffset() is None:
raise PreflightError("approval-timestamp")
return parsed
def _safe_relative_parts(value: Any) -> tuple[str, ...]:
if not isinstance(value, str) or not value: if not isinstance(value, str) or not value:
raise PreflightError("unsafe-relative-path") raise PreflightError("unsafe-relative-path")
pure = PurePosixPath(value) pure = PurePosixPath(value)
if ( if pure.is_absolute() or ".." in pure.parts or "\\" in value or "\x00" in value:
pure.is_absolute()
or not pure.parts
or ".." in pure.parts
or "\\" in value
or "\x00" in value
):
raise PreflightError("path-escape") raise PreflightError("path-escape")
return pure.parts base = root.resolve()
resolved = (base / Path(value)).resolve()
if not resolved.is_relative_to(base):
def _same_file_state(before: os.stat_result, after: os.stat_result) -> bool: raise PreflightError("path-escape")
return ( return resolved
before.st_dev,
before.st_ino,
before.st_mode,
before.st_nlink,
before.st_size,
before.st_mtime_ns,
before.st_ctime_ns,
) == (
after.st_dev,
after.st_ino,
after.st_mode,
after.st_nlink,
after.st_size,
after.st_mtime_ns,
after.st_ctime_ns,
)
@dataclass(frozen=True)
class _BoundFile:
sha256: str
size_bytes: int
payload: bytes | None
def _read_bound_regular_file(
root: Path, path_value: Any, *, capture_payload: bool
) -> _BoundFile:
"""Читает файл относительно доверенного неизменяемого root dirfd."""
parts = _safe_relative_parts(path_value)
directory_flags = (
os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC
)
opened_fds: list[int] = []
fd_states: list[tuple[int, os.stat_result]] = []
namespace_entries: list[tuple[int, str, os.stat_result]] = []
try:
directory_fd = os.open(root, directory_flags)
opened_fds.append(directory_fd)
root_state = os.fstat(directory_fd)
if not stat.S_ISDIR(root_state.st_mode):
raise PreflightError("file-binding")
fd_states.append((directory_fd, root_state))
for part in parts[:-1]:
parent_fd = directory_fd
directory_fd = os.open(part, directory_flags, dir_fd=parent_fd)
opened_fds.append(directory_fd)
directory_state = os.fstat(directory_fd)
if not stat.S_ISDIR(directory_state.st_mode):
raise PreflightError("file-binding")
fd_states.append((directory_fd, directory_state))
namespace_entries.append((parent_fd, part, directory_state))
file_fd = os.open(
parts[-1],
os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC,
dir_fd=directory_fd,
)
opened_fds.append(file_fd)
file_state = os.fstat(file_fd)
if not stat.S_ISREG(file_state.st_mode) or file_state.st_nlink != 1:
raise PreflightError("file-binding")
fd_states.append((file_fd, file_state))
namespace_entries.append((directory_fd, parts[-1], file_state))
digest = hashlib.sha256()
size_bytes = 0
chunks: list[bytes] | None = [] if capture_payload else None
while chunk := os.read(file_fd, 64 * 1024):
digest.update(chunk)
size_bytes += len(chunk)
if chunks is not None:
chunks.append(chunk)
for file_descriptor, original_state in fd_states:
if not _same_file_state(original_state, os.fstat(file_descriptor)):
raise PreflightError("file-binding")
for parent_fd, name, original_state in namespace_entries:
current_state = os.stat(name, dir_fd=parent_fd, follow_symlinks=False)
if not _same_file_state(original_state, current_state):
raise PreflightError("file-binding")
if size_bytes != file_state.st_size:
raise PreflightError("file-binding")
return _BoundFile(
sha256=digest.hexdigest(),
size_bytes=size_bytes,
payload=b"".join(chunks) if chunks is not None else None,
)
except PreflightError:
raise
except ValueError as exc:
raise PreflightError("path-escape") from exc
except OSError as exc:
reason = (
"path-escape"
if exc.errno in {errno.ELOOP, errno.ENOTDIR}
else "file-binding"
)
raise PreflightError(reason) from exc
finally:
for opened_fd in reversed(opened_fds):
try:
os.close(opened_fd)
except OSError:
pass
def _verify(root: Path, path_value: Any, expected_hash: Any, size: Any | None) -> None: def _verify(root: Path, path_value: Any, expected_hash: Any, size: Any | None) -> None:
path = _resolve(root, path_value)
expected = _sha(expected_hash, "invalid-sha256") expected = _sha(expected_hash, "invalid-sha256")
expected_size = None if size is None else _positive_int(size, "invalid-size") if not path.is_file():
bound = _read_bound_regular_file(root, path_value, capture_payload=False) raise PreflightError("file-unavailable")
if expected_size is not None and bound.size_bytes != expected_size: if size is not None and path.stat().st_size != _positive_int(size, "invalid-size"):
raise PreflightError("size-mismatch") raise PreflightError("size-mismatch")
if bound.sha256 != expected: if file_sha256(path) != expected:
raise PreflightError("hash-mismatch") raise PreflightError("hash-mismatch")
def _strict_json_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]: def _validate_approval(value: Any) -> bool:
value: dict[str, Any] = {} if value is None:
for key, item in pairs:
if key in value:
raise PreflightError("owner-acceptance-record")
value[key] = item
return value
def _reject_json_constant(_value: str) -> None:
raise PreflightError("owner-acceptance-record")
def _bound_json(
root: Path, link: Mapping[str, Any], link_keys: set[str]
) -> Mapping[str, Any]:
value = _exact(link, link_keys, "owner-acceptance-link")
expected_sha256 = _sha(value["sha256"], "owner-acceptance-hash")
bound = _read_bound_regular_file(root, value["path"], capture_payload=True)
if bound.sha256 != expected_sha256:
raise PreflightError("owner-acceptance-hash")
if bound.payload is None:
raise PreflightError("owner-acceptance-record")
try:
loaded = json.loads(
bound.payload,
object_pairs_hook=_strict_json_object,
parse_constant=_reject_json_constant,
)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise PreflightError("owner-acceptance-record") from exc
if not isinstance(loaded, Mapping):
raise PreflightError("owner-acceptance-record")
return loaded
def _validate_owner_package_row(row: Any) -> None:
value = _exact(row, OWNER_PACKAGE_REFERENCE_KEYS, "owner-package-reference")
alias = value["neutral_alias"]
if (
not isinstance(alias, str)
or re.fullmatch(r"[a-z][a-z0-9-]{0,63}", alias) is None
):
raise PreflightError("owner-package-reference")
for key in OWNER_PACKAGE_REFERENCE_KEYS - {"neutral_alias"}:
_sha(value[key], "owner-package-reference")
def _validate_acceptance_row(row: Any) -> None:
value = _exact(
row,
REFERENCE_ACCEPTANCE_ROW_KEYS,
"owner-acceptance-reference",
)
_validate_owner_package_row(
{key: value[key] for key in OWNER_PACKAGE_REFERENCE_KEYS}
)
for key in (
"fragment_expected_cluster_count",
"full_recording_expected_cluster_count",
):
count = value[key]
if type(count) is not int or not 2 <= count <= 4:
raise PreflightError("owner-acceptance-reference")
for key in (
"fragment_review_complete",
"full_recording_count_review_complete",
"reference_accepted",
):
if value[key] is not True:
raise PreflightError("owner-acceptance-reference")
def _validate_reference_owner_acceptance(
*,
root: Path,
basis: Mapping[str, Any],
expected_package_rows: Sequence[Mapping[str, Any]],
expected_acceptance_rows: Sequence[Mapping[str, Any]],
) -> bool:
try:
package_link = _exact(
basis["owner_package"],
OWNER_PACKAGE_LINK_KEYS,
"owner-package-link",
)
package = _exact(
_bound_json(root, package_link, OWNER_PACKAGE_LINK_KEYS),
OWNER_PACKAGE_KEYS,
"owner-package-schema",
)
if package["schema"] != OWNER_PACKAGE_SCHEMA:
raise PreflightError("owner-package-schema")
package_generated_at = _aware_datetime(package["generated_at"])
if package["generated_at"] != package_link["generated_at"]:
raise PreflightError("owner-package-timestamp")
package_rows = package["references"]
if not isinstance(package_rows, list) or len(package_rows) != 3:
raise PreflightError("owner-package-references")
for row in package_rows:
_validate_owner_package_row(row)
if package_rows != list(expected_package_rows):
raise PreflightError("owner-package-binding")
approval_link = _exact(
basis["approval"], APPROVAL_LINK_KEYS, "owner-acceptance-link"
)
acceptance = _exact(
_bound_json(root, approval_link, APPROVAL_LINK_KEYS),
REFERENCE_ACCEPTANCE_KEYS,
"owner-acceptance-schema",
)
if acceptance["schema"] != REFERENCE_ACCEPTANCE_SCHEMA:
raise PreflightError("owner-acceptance-schema")
for key in ("actor", "status", "decision", "timestamp"):
if acceptance[key] != approval_link[key]:
raise PreflightError("owner-acceptance-link-mismatch")
if (
acceptance["actor"] != "owner"
or acceptance["status"] != "approved"
or acceptance["decision"] != "approved"
):
raise PreflightError("owner-acceptance-decision")
accepted_at = _aware_datetime(acceptance["timestamp"])
if accepted_at <= package_generated_at:
raise PreflightError("owner-acceptance-timestamp")
if (
_sha(acceptance["owner_package_sha256"], "owner-package-binding")
!= package_link["sha256"]
):
raise PreflightError("owner-package-binding")
acceptance_rows = acceptance["references"]
if not isinstance(acceptance_rows, list) or len(acceptance_rows) != 3:
raise PreflightError("owner-acceptance-references")
for row in acceptance_rows:
_validate_acceptance_row(row)
if acceptance_rows != list(expected_acceptance_rows):
raise PreflightError("owner-acceptance-binding")
step3 = _exact(acceptance["step3"], STEP3_KEYS, "owner-acceptance-step3")
if any(step3[key] is not True for key in STEP3_KEYS):
raise PreflightError("owner-acceptance-step3")
except (KeyError, OSError, TypeError, PreflightError):
return False return False
approval = _exact(value, APPROVAL_KEYS, "approval-schema")
if approval["decision"] != "approved":
return False
timestamp = approval["timestamp"]
if not isinstance(timestamp, str):
raise PreflightError("approval-timestamp")
try:
parsed = datetime.fromisoformat(timestamp)
except ValueError as exc:
raise PreflightError("approval-timestamp") from exc
if parsed.tzinfo is None:
raise PreflightError("approval-timestamp")
return True return True
@@ -749,12 +457,10 @@ def validate_manifest(
raise PreflightError("legacy-identity") raise PreflightError("legacy-identity")
raise PreflightError("legacy-root") raise PreflightError("legacy-root")
basis = value["fresh_basis"] basis = _exact(value["fresh_basis"], FRESH_BASIS_KEYS, "fresh-basis-schema")
if not isinstance(basis, Mapping): if basis["marker"] != FRESH_BASIS_MARKER:
raise PreflightError("fresh-basis-schema")
if basis.get("marker") != FRESH_BASIS_MARKER:
raise PreflightError("fresh-basis-marker") raise PreflightError("fresh-basis-marker")
basis_schema_valid = set(basis) == FRESH_BASIS_KEYS approved = _validate_approval(basis["approval"])
source = _exact(value["source"], SOURCE_KEYS, "source-schema") source = _exact(value["source"], SOURCE_KEYS, "source-schema")
source_commit = _commit(source["commit"], "source-commit") source_commit = _commit(source["commit"], "source-commit")
if set(environment) != {"commit", "clean"}: if set(environment) != {"commit", "clean"}:
@@ -780,18 +486,8 @@ def validate_manifest(
raise PreflightError("source-hashes-not-unique") raise PreflightError("source-hashes-not-unique")
fragment_hashes = [] fragment_hashes = []
fragment_sidecars = [] fragment_sidecars = []
package_rows = []
acceptance_rows = []
aliases = []
for item in inputs: for item in inputs:
entry = _exact(item, INPUT_KEYS, "input-schema") entry = _exact(item, INPUT_KEYS, "input-schema")
alias = entry["neutral_alias"]
if (
not isinstance(alias, str)
or re.fullmatch(r"[a-z][a-z0-9-]{0,63}", alias) is None
):
raise PreflightError("neutral-alias")
aliases.append(alias)
if verify_files: if verify_files:
_verify( _verify(
root, root,
@@ -813,53 +509,29 @@ def validate_manifest(
media_hash = _sha(record["sha256"], "record-hash") media_hash = _sha(record["sha256"], "record-hash")
sidecar_hash = _sha(record["sidecar_sha256"], "sidecar-hash") sidecar_hash = _sha(record["sidecar_sha256"], "sidecar-hash")
if role == "fragment": if role == "fragment":
_sha(record["reference_sha256"], "reference-hash") reference_path = record["reference_path"]
reference_hash = record["reference_sha256"]
if (reference_path is None) != (reference_hash is None):
raise PreflightError("reference-binding")
if reference_hash is not None:
_sha(reference_hash, "reference-hash")
fragment_hashes.append(media_hash) fragment_hashes.append(media_hash)
fragment_sidecars.append(sidecar_hash) fragment_sidecars.append(sidecar_hash)
if verify_files: if verify_files:
_verify(root, record["path"], media_hash, record["size_bytes"]) _verify(root, record["path"], media_hash, record["size_bytes"])
_verify(root, record["sidecar_path"], sidecar_hash, None) _verify(root, record["sidecar_path"], sidecar_hash, None)
if role == "fragment": if role == "fragment" and reference_hash is not None:
_verify( _verify(
root, root,
record["reference_path"], reference_path,
record["reference_sha256"], reference_hash,
None, None,
) )
package_row = {
"neutral_alias": alias,
"canonical_fragment_audio_sha256": entry["fragment"]["sha256"],
"final_reference_sha256": entry["fragment"]["reference_sha256"],
"full_recording_sha256": entry["full_recording"]["sha256"],
}
package_rows.append(package_row)
acceptance_rows.append(
{
**package_row,
"fragment_expected_cluster_count": entry["fragment"][
"expected_cluster_count"
],
"full_recording_expected_cluster_count": entry["full_recording"][
"expected_cluster_count"
],
"fragment_review_complete": True,
"full_recording_count_review_complete": True,
"reference_accepted": True,
}
)
if len(set(aliases)) != 3:
raise PreflightError("neutral-alias")
_validate_schedule(value["schedule"], fragment_hashes, fragment_sidecars) _validate_schedule(value["schedule"], fragment_hashes, fragment_sidecars)
_positive_int(value["rss_sample_interval_ms"], "rss-sample-interval") _positive_int(value["rss_sample_interval_ms"], "rss-sample-interval")
approved = basis_schema_valid and _validate_reference_owner_acceptance(
root=root,
basis=basis,
expected_package_rows=package_rows,
expected_acceptance_rows=acceptance_rows,
)
return PreflightOutcome( return PreflightOutcome(
state="valid" if approved else "needs-user", state="valid" if approved else "needs-user",
reason=None if approved else "reference-owner-acceptance", reason=None if approved else "owner-approval-required",
fragment_count=3, fragment_count=3,
full_recording_count=3, full_recording_count=3,
source_commit_sha256=hashlib.sha256(source_commit.encode("ascii")).hexdigest(), source_commit_sha256=hashlib.sha256(source_commit.encode("ascii")).hexdigest(),
File diff suppressed because it is too large. Load diff