// std/run_artifacts - directory-backed run artifact helpers for harnesses.
import {
FileReplaceOptions,
FileReplaceReceipt,
FsFailure,
StructuredReadFailure,
TypedReadResult,
is_dir,
read_json_contract,
read_json_contract_result,
read_json_result,
replace_text_result,
write_lines,
} from "std/fs"
import { pretty } from "std/json"
import { SchemaContract, SchemaContractFailure, schema_contract_check } from "std/schema"
type RunArtifactsOpenOptions = {root?: string, namespace?: string, run_id?: string}
type RunArtifactsListOptions = {root?: string, namespace?: string, limit?: int}
pub type ArtifactWriteOptions = {
replace?: FileReplaceOptions,
pretty?: bool,
trailing_newline?: bool,
}
type RunArtifactTextWriteOptions = {trailing_newline?: bool, ensure_parent?: bool}
pub type ArtifactDescriptor<T> = {name: string, contract: SchemaContract<T>}
pub type ArtifactWriteFailure = SchemaContractFailure | FsFailure
type RunArtifactPaths = {
facts: string,
audit: string,
review: string,
agent_result: string,
agent_trace: string,
agent_llm_transcript: string,
}
type RunArtifactsRun = {
kind: string,
namespace?: string,
run_id: string,
root: string,
dir: string,
modified?: int | float,
paths: RunArtifactPaths,
}
fn __run_artifacts_required_text(value, label) -> string {
const text = trim(to_string(value ?? ""))
if text == "" {
throw "std/run_artifacts: " + label + " is required"
}
return text
}
fn __run_artifacts_clean_relative(value, label) -> string {
const raw = __run_artifacts_required_text(value, label)
const posix = path_to_posix(raw)
if path_is_absolute(posix) {
throw "std/run_artifacts: " + label + " must be relative"
}
for segment in path_segments(posix) {
if segment == ".." {
throw "std/run_artifacts: " + label + " must not contain '..'"
}
}
const normalized = path_normalize(posix)
if normalized == "." || normalized == "" {
throw "std/run_artifacts: " + label + " is required"
}
if normalized == ".." || starts_with(normalized, "../") {
throw "std/run_artifacts: " + label + " must stay inside the run directory"
}
return normalized
}
fn __run_artifacts_clean_segment(value, label) -> string {
const clean = __run_artifacts_clean_relative(value, label)
if contains(clean, "/") {
throw "std/run_artifacts: " + label + " must be one path segment"
}
return clean
}
fn __run_artifacts_root(options) -> string {
const opts = options ?? {}
const root = to_string(opts?.root ?? runtime_paths().run_root)
if root == "" {
throw "std/run_artifacts: root is required"
}
return path_normalize(root)
}
fn __run_artifacts_namespace(options) {
const raw = trim(to_string((options ?? {})?.namespace ?? ""))
if raw == "" {
return nil
}
return __run_artifacts_clean_relative(raw, "namespace")
}
fn __run_artifacts_kind_dir(root, kind, namespace) -> string {
if namespace == nil {
return path_join(root, kind)
}
return path_join(root, namespace, kind)
}
fn __run_artifacts_default_run_id() -> string {
return to_string(to_int(harness.clock.timestamp())) + "-" + uuid_v7()
}
fn __run_artifacts_run_dir(run: {dir: string, ...rest}) -> string {
const dir = to_string(run.dir ?? "")
if dir == "" {
throw "std/run_artifacts: run.dir is required"
}
return dir
}
fn __run_artifacts_path(run: {dir: string, ...rest}, name) -> string {
const rel = __run_artifacts_clean_relative(name, "artifact path")
const root = path_normalize(__run_artifacts_run_dir(run))
const joined = path_normalize(path_join(root, rel))
const relative = path_relative_to(joined, root)
if relative == nil || relative == ".." || starts_with(relative, "../") {
throw "std/run_artifacts: artifact path must stay inside the run directory"
}
return joined
}
fn __run_artifact_json_text(value, options: ArtifactWriteOptions) -> string {
const opts = options ?? {}
const body = if opts.pretty ?? false {
pretty(value)
} else {
json_stringify(value)
}
if opts.trailing_newline ?? true {
return body + "\n"
}
return body
}
fn __run_artifact_replace_options(options: ArtifactWriteOptions) -> FileReplaceOptions {
return (options ?? {}).replace ?? {}
}
fn __run_artifacts_standard_paths(run: {dir: string, ...rest}) -> RunArtifactPaths {
return {
facts: __run_artifacts_path(run, "facts.json"),
audit: __run_artifacts_path(run, "audit.json"),
review: __run_artifacts_path(run, "review.md"),
agent_result: __run_artifacts_path(run, "agent-result.json"),
agent_trace: __run_artifacts_path(run, "agent-trace.json"),
agent_llm_transcript: __run_artifacts_path(run, "agent-llm/llm_transcript.jsonl"),
}
}
fn __run_artifacts_make_run(kind, namespace, root, run_id, dir, modified = nil) -> RunArtifactsRun {
const base = {
kind: kind,
namespace: namespace,
run_id: run_id,
root: root,
dir: dir,
modified: modified,
}
return base + {paths: __run_artifacts_standard_paths(base)}
}
/**
* Create or resolve a run artifact directory below the configured run root.
*
* @effects: [fs.write]
* @errors: [validation, fs]
* @example: run_artifacts_open("release", {root: root, run_id: "run-1"})
*/
pub fn run_artifacts_open(kind: string, options: RunArtifactsOpenOptions = {}) -> RunArtifactsRun {
const opts = options ?? {}
const clean_kind = __run_artifacts_clean_segment(kind, "kind")
const namespace = __run_artifacts_namespace(opts)
const root = __run_artifacts_root(opts)
const run_id = if opts.run_id == nil {
__run_artifacts_default_run_id()
} else {
__run_artifacts_clean_segment(opts.run_id, "run_id")
}
const dir = path_join(__run_artifacts_kind_dir(root, clean_kind, namespace), run_id)
harness.fs.mkdir(dir)
return __run_artifacts_make_run(clean_kind, namespace, root, run_id, dir)
}
/**
* Return an artifact path inside `run.dir`, rejecting absolute paths and traversal.
*
* @effects: []
* @errors: [validation]
* @example: run_artifact_path(run, "facts.json")
*/
pub fn run_artifact_path(run: RunArtifactsRun, name: string) -> string {
return __run_artifacts_path(run, name)
}
/**
* Bind one stable artifact name to its structural schema and validation rules.
*
* @effects: []
* @errors: [validation]
* @example: artifact_descriptor("receipt.json", receipt_contract())
*/
pub fn artifact_descriptor<T>(name: string, contract: SchemaContract<T>) -> ArtifactDescriptor<T> {
return {name: __run_artifacts_clean_relative(name, "artifact path"), contract: contract}
}
/**
* Validate and conditionally replace descriptor-bound JSON.
*
* @effects: [fs.write]
* @errors: [ArtifactWriteFailure]
*/
pub fn run_artifact_write_json<T>(
run: RunArtifactsRun,
descriptor: ArtifactDescriptor<T>,
value: T,
options: ArtifactWriteOptions = {},
) -> FileReplaceReceipt {
return unwrap(run_artifact_write_json_result(run, descriptor, value, options))
}
/**
* Result form of `run_artifact_write_json`.
*
* Validation finishes before the conditional-replacement boundary, so an
* invalid value never mutates the destination.
*
* @effects: [fs.write]
* @errors: []
*/
pub fn run_artifact_write_json_result<T>(
run: RunArtifactsRun,
descriptor: ArtifactDescriptor<T>,
value: T,
options: ArtifactWriteOptions = {},
) -> Result<FileReplaceReceipt, ArtifactWriteFailure> {
const validated = schema_contract_check(value, descriptor.contract)
if !is_ok(validated) {
return Err(unwrap_err(validated))
}
const written = replace_text_result(
run_artifact_path(run, descriptor.name),
__run_artifact_json_text(unwrap(validated), options),
__run_artifact_replace_options(options),
)
if !is_ok(written) {
return Err(unwrap_err(written))
}
return Ok(unwrap(written))
}
/**
* Conditionally replace JSON without a descriptor or validation contract.
* This is the low-level escape hatch for untyped external formats.
*
* @effects: [fs.write]
* @errors: [validation, FsFailure]
*/
pub fn run_artifact_write_json_raw(
run: RunArtifactsRun,
name: string,
value,
options: ArtifactWriteOptions = {},
) -> FileReplaceReceipt {
return unwrap(run_artifact_write_json_raw_result(run, name, value, options))
}
/**
* Result form of `run_artifact_write_json_raw`.
*
* @effects: [fs.write]
* @errors: [validation]
*/
pub fn run_artifact_write_json_raw_result(
run: RunArtifactsRun,
name: string,
value,
options: ArtifactWriteOptions = {},
) -> Result<FileReplaceReceipt, FsFailure> {
return replace_text_result(
run_artifact_path(run, name),
__run_artifact_json_text(value, options),
__run_artifact_replace_options(options),
)
}
/**
* Read and validate descriptor-bound JSON.
*
* @effects: [fs.read]
* @errors: [validation, TypedReadFailure]
*/
pub fn run_artifact_read_json<T>(run: RunArtifactsRun, descriptor: ArtifactDescriptor<T>) -> T {
return read_json_contract(run_artifact_path(run, descriptor.name), descriptor.contract)
}
/**
* Result form of `run_artifact_read_json`.
*
* @effects: [fs.read]
* @errors: [validation]
*/
pub fn run_artifact_read_json_result<T>(
run: RunArtifactsRun,
descriptor: ArtifactDescriptor<T>,
) -> TypedReadResult<T> {
return read_json_contract_result(run_artifact_path(run, descriptor.name), descriptor.contract)
}
/**
* Read JSON without a descriptor or validation contract.
* This is the low-level escape hatch for untyped external formats.
*
* @effects: [fs.read]
* @errors: [validation, StructuredReadFailure]
*/
pub fn run_artifact_read_json_raw(run: RunArtifactsRun, name: string) -> unknown {
return unwrap(run_artifact_read_json_raw_result(run, name))
}
/**
* Result form of `run_artifact_read_json_raw`.
*
* @effects: [fs.read]
* @errors: [validation]
*/
pub fn run_artifact_read_json_raw_result(
run: RunArtifactsRun,
name: string,
) -> Result<unknown, StructuredReadFailure> {
return read_json_result(run_artifact_path(run, name))
}
/**
* Write a text artifact with standard parent-directory and newline handling.
*
* @effects: [fs.read, fs.write]
* @errors: [validation, fs]
* @example: run_artifact_write_text(run, "review.md", body)
*/
pub fn run_artifact_write_text(
run: RunArtifactsRun,
name: string,
text: string,
options: RunArtifactTextWriteOptions = {},
) -> nil {
write_lines(run_artifact_path(run, name), [text ?? ""], options ?? {})
return nil
}
/**
* Read a text artifact, returning `fallback` when it cannot be read.
*
* @effects: [fs.read]
* @errors: [validation]
* @example: run_artifact_read_text(run, "review.md", "")
*/
pub fn run_artifact_read_text(
run: RunArtifactsRun,
name: string,
fallback: string? = nil,
) -> string? {
const result = try {
harness.fs.read_text(run_artifact_path(run, name))
}
if is_ok(result) {
return unwrap(result)
}
return fallback
}
/**
* Return the transcript sidecar directory for a run without creating it.
*
* @effects: []
* @errors: [validation]
* @example: run_artifact_transcript_dir(run, "agent-llm")
*/
pub fn run_artifact_transcript_dir(run: RunArtifactsRun, name: string = "agent-llm") -> string {
const dir_name = if trim(to_string(name ?? "")) == "" {
"agent-llm"
} else {
name
}
return run_artifact_path(run, dir_name)
}
/**
* Return the standard JSONL transcript sidecar path for a named transcript directory.
*
* @effects: []
* @errors: [validation]
* @example: run_artifact_transcript_path(run, "agent-llm")
*/
pub fn run_artifact_transcript_path(run: RunArtifactsRun, name: string = "agent-llm") -> string {
const dir_name = if trim(to_string(name ?? "")) == "" {
"agent-llm"
} else {
name
}
return run_artifact_path(run, path_join(dir_name, "llm_transcript.jsonl"))
}
/**
* List recent run artifact directories newest-first for a kind.
*
* @effects: [fs.read]
* @errors: [validation]
* @example: run_artifacts_list("release", {limit: 5})
*/
pub fn run_artifacts_list(
kind: string,
options: RunArtifactsListOptions = {},
) -> list<RunArtifactsRun> {
const opts = options ?? {}
const clean_kind = __run_artifacts_clean_segment(kind, "kind")
const namespace = __run_artifacts_namespace(opts)
const root = __run_artifacts_root(opts)
const limit = to_int(opts.limit ?? 20) ?? 20
if limit <= 0 {
return []
}
const kind_dir = __run_artifacts_kind_dir(root, clean_kind, namespace)
if !is_dir(kind_dir) {
return []
}
const names_result = try {
harness.fs.list_dir(kind_dir)
}
if !is_ok(names_result) {
return []
}
let rows = []
for name in unwrap(names_result) {
const dir = path_join(kind_dir, name)
const info = try {
harness.fs.stat(dir)
}
if is_ok(info) && (unwrap(info)?.is_dir ?? false) {
const stat = unwrap(info)
rows = rows.push(
__run_artifacts_make_run(clean_kind, namespace, root, name, dir, stat?.modified),
)
}
}
return rows.sort_by(
{ row ->
const modified = row.modified ?? 0
return pair(0 - modified, row.run_id ?? "")
},
)
.take(limit)
.to_list()
}
/**
* Reconstruct the run artifact shape from an existing directory path without writing.
*
* @effects: []
* @errors: [validation]
* @example: run_artifacts_from_dir("release", previous_dir)
*/
pub fn run_artifacts_from_dir(kind: string, dir: string) -> RunArtifactsRun {
const clean_kind = __run_artifacts_clean_segment(kind, "kind")
const clean_dir = path_normalize(__run_artifacts_required_text(dir, "dir"))
const run_id = __run_artifacts_clean_segment(path_basename(clean_dir), "run_id")
const root = path_parent(path_parent(clean_dir))
return __run_artifacts_make_run(clean_kind, nil, root, run_id, clean_dir)
}