제품 결과와 harness·process·artifact 실패가 하나의 성공 값으로 덮이지 않도록 durable evidence와 모든 소비자 계약을 함께 마이그레이션한다.
544 lines
23 KiB
Python
544 lines
23 KiB
Python
"""Deterministic, fail-closed Markdown reporting for benchmark evidence.
|
|
|
|
This module is intentionally a reader of the immutable run tree. It does not
|
|
retry execution or scoring, derive values that producers did not record, or
|
|
write anything except the idempotent ``report.md`` artifact after every input
|
|
has passed its owning strict loader.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import stat
|
|
from dataclasses import dataclass
|
|
from pathlib import Path, PurePosixPath
|
|
from typing import Any
|
|
|
|
from scripts.agent_benchmark import scoring as _scoring
|
|
from scripts.agent_benchmark.attempts import (
|
|
Attempt,
|
|
AttemptStateError,
|
|
RunIdentity,
|
|
RunStore,
|
|
_connectivity_result_from_payload,
|
|
)
|
|
from scripts.agent_benchmark.manifest import Manifest, MatrixCell
|
|
from scripts.agent_benchmark.measurement import (
|
|
METRIC_NAMES,
|
|
TIMELINE_NAMES,
|
|
AttemptMeasurement,
|
|
MeasurementError,
|
|
Observation,
|
|
load_measurement,
|
|
)
|
|
from scripts.agent_benchmark.web_validation import (
|
|
WebValidation,
|
|
WebValidationError,
|
|
load_web_validation,
|
|
)
|
|
from scripts.agent_benchmark.rubric import validate_worksheet
|
|
|
|
REPORT_FILENAME = "report.md"
|
|
_MAX_REPORT_BYTES = 2 * 1024 * 1024
|
|
|
|
|
|
class ReportError(Exception):
|
|
"""The immutable evidence cannot safely be projected into a report."""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CategoryProjection:
|
|
id: str
|
|
score: int
|
|
max_score: int
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ScoreProjection:
|
|
status: str
|
|
categories: tuple[CategoryProjection, ...]
|
|
total: int | None
|
|
rank: int | None
|
|
reasons: tuple[str, ...]
|
|
score_id: str | None
|
|
evaluator: tuple[str, str, str, str] | None
|
|
raw_paths: tuple[str, ...]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AttemptProjection:
|
|
cell: MatrixCell
|
|
attempt: Attempt
|
|
measurement: AttemptMeasurement | None
|
|
web: WebValidation | None
|
|
score: ScoreProjection
|
|
raw_paths: tuple[str, ...]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ReportProjection:
|
|
run: RunIdentity
|
|
manifest: Manifest
|
|
preflights: tuple[dict[str, Any], ...]
|
|
attempts: tuple[AttemptProjection, ...]
|
|
|
|
|
|
def _markdown(value: object) -> str:
|
|
"""Escape one table cell without allowing a value to alter Markdown shape."""
|
|
return str(value).replace("\\", "\\\\").replace("|", "\\|").replace(
|
|
"\r", " ").replace("\n", " ")
|
|
|
|
|
|
def _relative_path(value: str) -> str:
|
|
if not isinstance(value, str) or not value or "\\" in value or ":" in value:
|
|
raise ReportError("report evidence path is invalid")
|
|
path = PurePosixPath(value)
|
|
if path.is_absolute() or str(path) != value or any(
|
|
part in ("", ".", "..") for part in path.parts
|
|
):
|
|
raise ReportError("report evidence path is invalid")
|
|
return value
|
|
|
|
|
|
def _regular_under(root: Path, relative: str) -> None:
|
|
"""Require a contained regular file and reject every symlink component."""
|
|
relative = _relative_path(relative)
|
|
current = root
|
|
try:
|
|
root_info = os.lstat(root)
|
|
except OSError as exc:
|
|
raise ReportError("report run root is unavailable") from exc
|
|
if not stat.S_ISDIR(root_info.st_mode) or stat.S_ISLNK(root_info.st_mode):
|
|
raise ReportError("report run root is invalid")
|
|
for component in PurePosixPath(relative).parts:
|
|
current = current / component
|
|
try:
|
|
info = os.lstat(current)
|
|
except OSError as exc:
|
|
raise ReportError("report evidence is unavailable") from exc
|
|
if stat.S_ISLNK(info.st_mode):
|
|
raise ReportError("report evidence path is invalid")
|
|
if not stat.S_ISREG(info.st_mode):
|
|
raise ReportError("report evidence must be a regular file")
|
|
try:
|
|
current.resolve().relative_to(root.resolve())
|
|
except ValueError as exc:
|
|
raise ReportError("report evidence escapes run root") from exc
|
|
|
|
|
|
def _raw_link(path: str) -> str:
|
|
path = _relative_path(path)
|
|
return f"[raw](<{path.replace('>', '%3E')}>)"
|
|
|
|
|
|
def _attempt_label(item: AttemptProjection) -> str:
|
|
"""Render the complete durable identity used by detailed report rows."""
|
|
identity = item.attempt.identity
|
|
return f"{item.cell.id}/r{identity.repetition}/a{identity.attempt}"
|
|
|
|
|
|
def _observation(value: Observation) -> str:
|
|
if value.status == "observed":
|
|
return (
|
|
f"{value.value} {value.unit}; clock={value.clock}; "
|
|
f"source={value.source}"
|
|
)
|
|
return f"unavailable; reason={value.reason}; source={value.source}"
|
|
|
|
|
|
def _observation_group(values: dict[str, Observation]) -> str:
|
|
"""Keep every named value while coalescing identical unavailable causes."""
|
|
groups: dict[tuple[str, str], list[str]] = {}
|
|
rendered: list[str] = []
|
|
for name, value in values.items():
|
|
if value.status == "observed":
|
|
rendered.append(f"{name}={_observation(value)}")
|
|
else:
|
|
groups.setdefault((value.reason, value.source), []).append(name)
|
|
for (reason, source), names in groups.items():
|
|
rendered.append(
|
|
f"{','.join(names)}=unavailable; reason={reason}; source={source}"
|
|
)
|
|
return "; ".join(rendered)
|
|
|
|
|
|
def _read_canonical_json(path: Path, label: str) -> dict[str, Any]:
|
|
"""Use scoring's no-follow reader and reject non-canonical JSON records."""
|
|
try:
|
|
value = _scoring._load_canonical(path, label)
|
|
except Exception as exc:
|
|
raise ReportError("report scoring evidence is unavailable") from exc
|
|
if not isinstance(value, dict):
|
|
raise ReportError("report scoring evidence is invalid")
|
|
return value
|
|
|
|
|
|
def _score_projection(
|
|
run: RunIdentity, manifest: Manifest, attempt: Attempt, *, blocked: bool
|
|
) -> ScoreProjection:
|
|
"""Strictly load the terminal score state without allocating or recovering."""
|
|
root = Path(attempt.root)
|
|
raw_paths: list[str] = []
|
|
try:
|
|
score_root = _scoring._score_root(attempt, create=False)
|
|
score_dirs = _scoring._score_dirs(score_root)
|
|
is_unscored = _scoring._validate_unscored(run, manifest, attempt)
|
|
if is_unscored:
|
|
if score_dirs:
|
|
raise ReportError("report scoring state is invalid")
|
|
raw_paths.append(
|
|
f"cells/{attempt.identity.cell_id}/repetition-"
|
|
f"{attempt.identity.repetition:04d}/attempt-"
|
|
f"{attempt.identity.attempt:06d}/scoring/unscored.json"
|
|
)
|
|
record = _read_canonical_json(root / "scoring" / "unscored.json", "unscored evidence")
|
|
reasons = record.get("reasons")
|
|
if not isinstance(reasons, list) or not all(isinstance(item, str) for item in reasons):
|
|
raise ReportError("report unscored evidence is invalid")
|
|
return ScoreProjection(
|
|
"unscored", (), None, None, tuple(reasons), None, None,
|
|
tuple(raw_paths),
|
|
)
|
|
|
|
if not score_dirs:
|
|
return ScoreProjection(
|
|
"blocked" if blocked else "unavailable", (), None, None,
|
|
("evaluator_preflight_blocked",) if blocked else ("not_recorded",),
|
|
None, None, (),
|
|
)
|
|
|
|
statuses: list[str] = []
|
|
allocations: list[dict[str, Any]] = []
|
|
for score_root in score_dirs:
|
|
status = _scoring._result_status(score_root, run, manifest, attempt)
|
|
if status is None:
|
|
raise ReportError("report scoring result is incomplete")
|
|
statuses.append(status)
|
|
allocations.append(
|
|
_scoring._validate_allocation(
|
|
score_root / "allocation.json", run, manifest, attempt, score_root.name
|
|
)
|
|
)
|
|
prefix = (
|
|
f"cells/{attempt.identity.cell_id}/repetition-"
|
|
f"{attempt.identity.repetition:04d}/attempt-"
|
|
f"{attempt.identity.attempt:06d}/scoring/{score_root.name}"
|
|
)
|
|
raw_paths.extend((f"{prefix}/allocation.json", f"{prefix}/result.json"))
|
|
|
|
if "scored" in statuses:
|
|
if statuses[-1] != "scored" or statuses.count("scored") != 1:
|
|
raise ReportError("report scoring state is invalid")
|
|
score_root = score_dirs[-1]
|
|
result = _read_canonical_json(score_root / "result.json", "scoring result")
|
|
try:
|
|
worksheet = validate_worksheet(result.get("worksheet"))
|
|
except Exception as exc:
|
|
raise ReportError("report worksheet is invalid") from exc
|
|
categories = tuple(
|
|
CategoryProjection(item.id, item.score, item.max_score)
|
|
for item in worksheet.categories
|
|
)
|
|
if not categories:
|
|
raise ReportError("report worksheet is invalid")
|
|
evaluator = allocations[-1]["evaluator"]
|
|
binding = (
|
|
evaluator["caller"], evaluator["route_id"],
|
|
evaluator["request_model"], evaluator["requested_effort"],
|
|
)
|
|
return ScoreProjection(
|
|
"scored", categories, worksheet.total, None, (), score_root.name,
|
|
binding, tuple(raw_paths),
|
|
)
|
|
|
|
if any(status != "scoring_failed" for status in statuses):
|
|
raise ReportError("report scoring state is invalid")
|
|
result = _read_canonical_json(score_dirs[-1] / "result.json", "scoring result")
|
|
reason = result.get("reason")
|
|
if not isinstance(reason, str) or not reason:
|
|
raise ReportError("report scoring failure is invalid")
|
|
evaluator = allocations[-1]["evaluator"]
|
|
binding = (
|
|
evaluator["caller"], evaluator["route_id"],
|
|
evaluator["request_model"], evaluator["requested_effort"],
|
|
)
|
|
return ScoreProjection(
|
|
"scoring_failed", (), None, None, (reason,), score_dirs[-1].name,
|
|
binding, tuple(raw_paths),
|
|
)
|
|
except ReportError:
|
|
raise
|
|
except Exception as exc:
|
|
raise ReportError("report scoring evidence is invalid") from exc
|
|
|
|
|
|
def _scoring_preflight_blocked(run: RunIdentity, manifest: Manifest) -> bool:
|
|
"""Validate evaluator preflights and report whether the latest one blocked."""
|
|
root = Path(run.root) / "scoring-preflight"
|
|
if not root.exists() and not root.is_symlink():
|
|
return False
|
|
try:
|
|
if root.is_symlink() or not root.is_dir():
|
|
raise ReportError("report scoring preflight is invalid")
|
|
evaluator = _scoring._evaluator_cell(manifest)
|
|
statuses: list[str] = []
|
|
for expected, path in enumerate(sorted(root.iterdir()), start=1):
|
|
if path.name != f"preflight-{expected:06d}.json":
|
|
raise ReportError("report scoring preflight sequence is invalid")
|
|
payload = _read_canonical_json(path, "evaluator preflight")
|
|
result, _, _ = _connectivity_result_from_payload(payload, evaluator)
|
|
statuses.append(result.status)
|
|
return bool(statuses and statuses[-1] != "ready")
|
|
except ReportError:
|
|
raise
|
|
except Exception as exc:
|
|
raise ReportError("report scoring preflight is invalid") from exc
|
|
|
|
|
|
def _rank(attempts: list[AttemptProjection]) -> tuple[AttemptProjection, ...]:
|
|
"""Assign competition ranks without using display order as a tie-breaker."""
|
|
scored = sorted(
|
|
(item for item in attempts if item.score.status == "scored"),
|
|
key=lambda item: -int(item.score.total),
|
|
)
|
|
ranks: dict[tuple[str, int, int], int] = {}
|
|
previous: int | None = None
|
|
for index, item in enumerate(scored, start=1):
|
|
total = int(item.score.total)
|
|
if total != previous:
|
|
rank = index
|
|
previous = total
|
|
ranks[(item.attempt.identity.cell_id, item.attempt.identity.repetition, item.attempt.identity.attempt)] = rank
|
|
result: list[AttemptProjection] = []
|
|
for item in attempts:
|
|
key = (item.attempt.identity.cell_id, item.attempt.identity.repetition, item.attempt.identity.attempt)
|
|
score = item.score
|
|
result.append(AttemptProjection(
|
|
item.cell, item.attempt, item.measurement, item.web,
|
|
ScoreProjection(
|
|
score.status, score.categories, score.total, ranks.get(key),
|
|
score.reasons, score.score_id, score.evaluator, score.raw_paths,
|
|
),
|
|
item.raw_paths,
|
|
))
|
|
return tuple(result)
|
|
|
|
|
|
def project_report(store: RunStore, run: RunIdentity, manifest: Manifest) -> ReportProjection:
|
|
"""Read every report input in stable manifest/slot/attempt order."""
|
|
try:
|
|
bound = store.open(manifest, run.run_id)
|
|
if bound != run:
|
|
raise ReportError("report run identity is invalid")
|
|
preflights = store.preflights(bound, manifest)
|
|
blocked = _scoring_preflight_blocked(bound, manifest)
|
|
cells = {cell.id: cell for cell in manifest.matrix}
|
|
rows: list[AttemptProjection] = []
|
|
for attempt in store.execution_attempts(bound, manifest):
|
|
cell = cells.get(attempt.identity.cell_id)
|
|
if cell is None:
|
|
raise ReportError("report attempt cell is invalid")
|
|
measurement = None
|
|
web = None
|
|
raw_paths = [
|
|
f"cells/{attempt.identity.cell_id}/repetition-"
|
|
f"{attempt.identity.repetition:04d}/attempt-"
|
|
f"{attempt.identity.attempt:06d}/attempt.json",
|
|
]
|
|
if attempt.state != "running":
|
|
measurement = load_measurement(attempt.root)
|
|
if (
|
|
measurement.run_id != bound.run_id
|
|
or measurement.cell_id != attempt.identity.cell_id
|
|
or measurement.repetition != attempt.identity.repetition
|
|
or measurement.attempt != attempt.identity.attempt
|
|
or measurement.caller != cell.caller
|
|
):
|
|
raise ReportError("report measurement identity is invalid")
|
|
web = load_web_validation(attempt.root, manifest=manifest)
|
|
if web.record["attempt"] != {
|
|
"run_id": bound.run_id, "cell_id": attempt.identity.cell_id,
|
|
"repetition": attempt.identity.repetition, "attempt": attempt.identity.attempt,
|
|
}:
|
|
raise ReportError("report web evidence identity is invalid")
|
|
raw_paths.extend((
|
|
f"cells/{attempt.identity.cell_id}/repetition-{attempt.identity.repetition:04d}/attempt-{attempt.identity.attempt:06d}/attempt-measurement.json",
|
|
f"cells/{attempt.identity.cell_id}/repetition-{attempt.identity.repetition:04d}/attempt-{attempt.identity.attempt:06d}/web-validation.json",
|
|
))
|
|
score = _score_projection(bound, manifest, attempt, blocked=blocked)
|
|
rows.append(AttemptProjection(cell, attempt, measurement, web, score, tuple(raw_paths)))
|
|
return ReportProjection(bound, manifest, preflights, _rank(rows))
|
|
except ReportError:
|
|
raise
|
|
except (AttemptStateError, MeasurementError, WebValidationError) as exc:
|
|
raise ReportError("report evidence is invalid") from exc
|
|
except Exception as exc:
|
|
raise ReportError("report evidence is unavailable") from exc
|
|
|
|
|
|
def render_report(projection: ReportProjection) -> bytes:
|
|
"""Render one fixed-order UTF-8/LF Markdown projection."""
|
|
root = Path(projection.run.root)
|
|
raw_paths = {"manifest.json", "run.json"}
|
|
scoring_preflight = root / "scoring-preflight"
|
|
if scoring_preflight.exists() or scoring_preflight.is_symlink():
|
|
# project_report already performed the schema validation; retain the
|
|
# exact immutable preflight bytes as the blocked/ready score pointer.
|
|
for expected, path in enumerate(sorted(scoring_preflight.iterdir()), start=1):
|
|
if path.name != f"preflight-{expected:06d}.json":
|
|
raise ReportError("report scoring preflight sequence is invalid")
|
|
raw_paths.add(f"scoring-preflight/{path.name}")
|
|
lines = [
|
|
"# Agent comparison benchmark report",
|
|
"",
|
|
"## Run identity",
|
|
"",
|
|
"| field | value |",
|
|
"|---|---|",
|
|
f"| run_id | {_markdown(projection.run.run_id)} |",
|
|
f"| manifest_digest | {_markdown(projection.run.manifest_digest)} |",
|
|
f"| pipeline_version | {_markdown(projection.manifest.pipeline_version)} |",
|
|
"",
|
|
"## Immutable conditions",
|
|
"",
|
|
"| field | value |",
|
|
"|---|---|",
|
|
f"| environment | {_markdown(projection.manifest.environment)} |",
|
|
f"| fixture | {_markdown(projection.manifest.fixture.version)} ({_markdown(projection.manifest.fixture.checksum)}) |",
|
|
f"| rubric | {_markdown(projection.manifest.rubric_version)} |",
|
|
f"| session_policy | {_markdown(projection.manifest.session_policy)} |",
|
|
f"| setup_cache_policy | {_markdown(projection.manifest.setup_cache_policy)} |",
|
|
f"| evaluator | {_markdown(projection.manifest.evaluator.caller)}/{_markdown(projection.manifest.evaluator.iop.request_model)}/{_markdown(projection.manifest.evaluator.iop.requested_effort)} |",
|
|
"",
|
|
"## Execution preflight",
|
|
"",
|
|
"| sequence | status | results |",
|
|
"|---:|---|---:|",
|
|
]
|
|
if projection.preflights:
|
|
for item in projection.preflights:
|
|
path = f"preflight/preflight-{item['sequence']:06d}.json"
|
|
raw_paths.add(path)
|
|
lines.append(f"| {item['sequence']} | {_markdown(item['status'])} | {len(item['results'])} |")
|
|
else:
|
|
lines.append("| — | unavailable | 0 |")
|
|
|
|
lines.extend(("", "## Attempt outcomes", "", "| cell | repetition | attempt | controller | product | harness | process | artifact | scoring | total | rank |", "|---|---:|---:|---|---|---|---|---|---|---:|---:|"))
|
|
if not projection.attempts:
|
|
lines.append("| — | — | — | blocked | unavailable | unavailable | unavailable | unavailable | unavailable | — | — |")
|
|
for item in projection.attempts:
|
|
product = "unavailable" if item.measurement is None else item.measurement.product.status
|
|
harness = "unavailable" if item.measurement is None else item.measurement.harness.status
|
|
process = "unavailable" if item.measurement is None else item.measurement.process.status
|
|
artifact = "unavailable" if item.web is None else item.web.status
|
|
total = "—" if item.score.total is None else str(item.score.total)
|
|
rank = "—" if item.score.rank is None else str(item.score.rank)
|
|
lines.append(
|
|
f"| {_markdown(item.cell.id)} | {item.attempt.identity.repetition} | {item.attempt.identity.attempt} | "
|
|
f"{_markdown(item.attempt.state)} | {_markdown(product)} | {_markdown(harness)} | "
|
|
f"{_markdown(process)} | {_markdown(artifact)} | "
|
|
f"{_markdown(item.score.status)} | {total} | {rank} |"
|
|
)
|
|
raw_paths.update(item.raw_paths)
|
|
raw_paths.update(item.score.raw_paths)
|
|
|
|
lines.extend((
|
|
"", "## Quality score breakdown", "",
|
|
"| cell/repetition/attempt | category | score | max |",
|
|
"|---|---|---:|---:|",
|
|
))
|
|
wrote_categories = False
|
|
for item in projection.attempts:
|
|
label = _attempt_label(item)
|
|
for category in item.score.categories:
|
|
lines.append(
|
|
f"| {_markdown(label)} | {_markdown(category.id)} | "
|
|
f"{category.score} | {category.max_score} |"
|
|
)
|
|
wrote_categories = True
|
|
if not wrote_categories:
|
|
lines.append("| — | unavailable | — | — |")
|
|
|
|
lines.extend(("", "## Timing and token evidence", "", "| cell/repetition/attempt | time observations | token observations |", "|---|---|---|"))
|
|
wrote_metrics = False
|
|
for item in projection.attempts:
|
|
if item.measurement is None:
|
|
continue
|
|
label = _attempt_label(item)
|
|
timeline = _observation_group(item.measurement.timeline)
|
|
usage = _observation_group(item.measurement.usage)
|
|
lines.append(f"| {_markdown(label)} | {_markdown(timeline)} | {_markdown(usage)} |")
|
|
wrote_metrics = True
|
|
if not wrote_metrics:
|
|
lines.append("| — | unavailable; reason=not_recorded | unavailable; reason=not_recorded |")
|
|
|
|
lines.extend(("", "## Web validation and scoring provenance", "", "| cell/repetition/attempt | web gates | screenshots | score_id | evaluator | scoring condition |", "|---|---|---|---|---|---|"))
|
|
if not projection.attempts:
|
|
lines.append("| — | unavailable | unavailable | — | — | blocked |")
|
|
for item in projection.attempts:
|
|
gates, shots = "unavailable", "unavailable"
|
|
if item.web is not None:
|
|
gates = ", ".join(
|
|
f"{gate['id']}={'pass' if gate['passed'] else 'fail'}"
|
|
for gate in item.web.record["gates"]
|
|
)
|
|
shots = ", ".join(
|
|
str(view["screenshot"]["file"])
|
|
for view in item.web.record["viewports"]
|
|
) or "unavailable"
|
|
evaluator = "unavailable" if item.score.evaluator is None else "/".join(item.score.evaluator)
|
|
condition = ", ".join(item.score.reasons) or "recorded"
|
|
lines.append(
|
|
f"| {_markdown(_attempt_label(item))} | {_markdown(gates)} | {_markdown(shots)} | "
|
|
f"{_markdown(item.score.score_id or '—')} | {_markdown(evaluator)} | {_markdown(condition)} |"
|
|
)
|
|
|
|
lines.extend(("", "## Limitations", "", "- Values marked `unavailable` retain the producing source and reason; they are not inferred as zero.", "- Automatic web gates establish eligibility only and contribute no quality points.", "- Equal scored totals share a competition rank; unscored and scoring-failed attempts do not receive a rank.", "", "## Raw evidence index", "", "| contained pointer |", "|---|"))
|
|
for path in sorted(raw_paths):
|
|
_regular_under(root, path)
|
|
lines.append(f"| {_raw_link(path)} |")
|
|
lines.append("")
|
|
return "\n".join(lines).encode("utf-8")
|
|
|
|
|
|
def publish_report(store: RunStore, run: RunIdentity, manifest: Manifest) -> Path:
|
|
"""Render and idempotently publish the sole run-owned report artifact."""
|
|
data = render_report(project_report(store, run, manifest))
|
|
if len(data) > _MAX_REPORT_BYTES:
|
|
raise ReportError("report exceeds the bounded artifact size")
|
|
root = Path(run.root)
|
|
path = root / REPORT_FILENAME
|
|
try:
|
|
info = os.lstat(path)
|
|
except FileNotFoundError:
|
|
info = None
|
|
except OSError as exc:
|
|
raise ReportError("report publication is unavailable") from exc
|
|
if info is not None:
|
|
if stat.S_ISLNK(info.st_mode) or not stat.S_ISREG(info.st_mode):
|
|
raise ReportError("report publication target is invalid")
|
|
try:
|
|
existing = path.read_bytes()
|
|
except OSError as exc:
|
|
raise ReportError("report publication is unavailable") from exc
|
|
if existing != data:
|
|
raise ReportError("report publication refused an existing target")
|
|
return path
|
|
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_CLOEXEC
|
|
if hasattr(os, "O_NOFOLLOW"):
|
|
flags |= os.O_NOFOLLOW
|
|
try:
|
|
descriptor = os.open(path, flags, 0o600)
|
|
with os.fdopen(descriptor, "wb") as handle:
|
|
handle.write(data)
|
|
handle.flush()
|
|
os.fsync(handle.fileno())
|
|
directory = os.open(root, os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC)
|
|
try:
|
|
os.fsync(directory)
|
|
finally:
|
|
os.close(directory)
|
|
except OSError as exc:
|
|
raise ReportError("report publication failed") from exc
|
|
return path
|