"""c_artifact_write (03-C §3.7): the only writer of `artifact` rows. Flagged as an unowned gap when scoping U-011 (REQ-02's happy AND failure cases both need it directly, and no Unit in development-plan.md's 43-unit breakdown built it). Built now because U-014 (end-to-end acceptance) is the first Unit that actually needs artifacts to exist for real, not seeded. Simplification, noted rather than silently assumed: `validation_status` is set to 'passed' unconditionally at write time. REQ-02's boundary case ("PRD 中出现互相矛盾的场景假设时,一致性校验应在 verifying 态被拦截") describes semantic content review, which would need its own LLM-judgment call this codebase doesn't build yet (distinct from work_node_execution's schema-shape validation, already real since U-013) -- out of scope here, same as every other REQ boundary/failure case this session has deferred rather than faked. """ import json import os import subprocess import uuid _GIT_IDENTITY = ["-c", "user.name=graphlemon", "-c", "user.email=graphlemon@localhost"] class InvalidDependencyError(Exception): """REQ-02 failure case: depends_on_artifact_ids references a nonexistent artifact_id.""" class GitCommitFailedError(Exception): """GIT_COMMIT_FAILED -- 03-C §3.7.""" def write_artifact( conn, target_folder_path, artifact_type, task_id, produced_by_run_id, file_path, content, depends_on_artifact_ids=None, ): """Writes `content` to `/`, commits it to the target project's own git history (not graphlemon's -- 01-M 1.5: artifact.git_commit_sha is the *target project's* commit), and creates the immutable `artifact` row. Returns {"artifact_id", "git_commit_sha"}. """ depends_on_artifact_ids = depends_on_artifact_ids or [] for dep_id in depends_on_artifact_ids: if conn.execute("SELECT 1 FROM artifact WHERE artifact_id = ?", (dep_id,)).fetchone() is None: raise InvalidDependencyError("depends_on_artifact_ids references nonexistent artifact: " + dep_id) abs_path = os.path.join(target_folder_path, file_path) os.makedirs(os.path.dirname(abs_path), exist_ok=True) with open(abs_path, "w", encoding="utf-8") as f: f.write(content) try: subprocess.run( ["git", "add", file_path], cwd=target_folder_path, check=True, capture_output=True, text=True ) subprocess.run( ["git"] + _GIT_IDENTITY + [ "commit", "-m", "graphlemon: {} artifact for task {}".format(artifact_type, task_id) ], cwd=target_folder_path, check=True, capture_output=True, text=True, ) sha = subprocess.run( ["git", "rev-parse", "HEAD"], cwd=target_folder_path, check=True, capture_output=True, text=True ).stdout.strip() except subprocess.CalledProcessError as e: raise GitCommitFailedError(str(e)) from e artifact_id = str(uuid.uuid4()) conn.execute( "INSERT INTO artifact " "(artifact_id, artifact_type, task_id, produced_by_run_id, file_path, git_commit_sha, " "depends_on_artifact_ids, validation_status) VALUES (?, ?, ?, ?, ?, ?, ?, 'passed')", ( artifact_id, artifact_type, task_id, produced_by_run_id, file_path, sha, json.dumps(depends_on_artifact_ids), ), ) conn.commit() return {"artifact_id": artifact_id, "git_commit_sha": sha}