Clarify world-class submission artifact roles

This commit is contained in:
yaojingang
2026-06-16 07:11:23 +08:00
parent e0a19e9b24
commit 28c83c6976
30 changed files with 417 additions and 283 deletions
+69 -24
View File
@@ -69,6 +69,25 @@ def artifact_rows_by_key_and_path(rows: list[dict[str, Any]]) -> dict[tuple[str,
return ready
def template_artifact_ref_paths(skill_dir: Path, item: dict[str, Any]) -> set[str]:
template_path = skill_dir / str(item.get("template_path", ""))
errors: list[str] = []
payload = load_template_payload(template_path, errors)
if payload is None:
return set()
refs = payload.get("artifact_refs", [])
if not isinstance(refs, list):
return set()
paths: set[str] = set()
for ref in refs:
if not isinstance(ref, dict):
continue
path = str(ref.get("path", "")).strip()
if path:
paths.add(path)
return paths
def load_template_payload(path: Path, errors: list[str]) -> dict[str, Any] | None:
try:
payload = json.loads(path.read_text(encoding="utf-8"))
@@ -201,12 +220,22 @@ def artifact_row(skill_dir: Path, evidence_key: str, pattern: str, path: Path, s
}
def with_artifact_role(row: dict[str, Any], submission_ref_paths: set[str]) -> dict[str, Any]:
is_submission_ref = str(row.get("path", "")) in submission_ref_paths
return {
**row,
"artifact_role": "submission-ref" if is_submission_ref else "supporting-evidence",
"submission_ref_required": is_submission_ref,
}
def artifact_checklist_for_item(skill_dir: Path, item: dict[str, Any]) -> list[dict[str, Any]]:
key = str(item.get("evidence_key", ""))
must_collect = item.get("must_collect", {}) if isinstance(item.get("must_collect", {}), dict) else {}
artifacts = must_collect.get("evidence_artifacts", [])
if not isinstance(artifacts, list):
return []
submission_ref_paths = template_artifact_ref_paths(skill_dir, item)
rows: list[dict[str, Any]] = []
for artifact in artifacts:
pattern = str(artifact or "").strip()
@@ -214,31 +243,12 @@ def artifact_checklist_for_item(skill_dir: Path, item: dict[str, Any]) -> list[d
continue
if Path(pattern).is_absolute() or ".." in Path(pattern).parts:
rows.append(
{
"evidence_key": key,
"source_pattern": pattern,
"path": pattern,
"status": "unsafe-path",
"exists": False,
"is_file": False,
"sha256": "",
"artifact_ref_ready": False,
"copy_path": "",
"copy_sha256": "",
"contains_raw_content": False,
"concrete_reference_required": True,
}
)
continue
if has_glob_pattern(pattern):
matches = sorted(path for path in skill_dir.glob(pattern) if path.is_file())
if not matches:
rows.append(
with_artifact_role(
{
"evidence_key": key,
"source_pattern": pattern,
"path": pattern,
"status": "glob-no-match",
"status": "unsafe-path",
"exists": False,
"is_file": False,
"sha256": "",
@@ -247,13 +257,38 @@ def artifact_checklist_for_item(skill_dir: Path, item: dict[str, Any]) -> list[d
"copy_sha256": "",
"contains_raw_content": False,
"concrete_reference_required": True,
}
},
submission_ref_paths,
)
)
continue
if has_glob_pattern(pattern):
matches = sorted(path for path in skill_dir.glob(pattern) if path.is_file())
if not matches:
rows.append(
with_artifact_role(
{
"evidence_key": key,
"source_pattern": pattern,
"path": pattern,
"status": "glob-no-match",
"exists": False,
"is_file": False,
"sha256": "",
"artifact_ref_ready": False,
"copy_path": "",
"copy_sha256": "",
"contains_raw_content": False,
"concrete_reference_required": True,
},
submission_ref_paths,
)
)
continue
for match in matches:
rows.append(artifact_row(skill_dir, key, pattern, match, "ready"))
rows.append(with_artifact_role(artifact_row(skill_dir, key, pattern, match, "ready"), submission_ref_paths))
continue
rows.append(artifact_row(skill_dir, key, pattern, skill_dir / pattern, ""))
rows.append(with_artifact_role(artifact_row(skill_dir, key, pattern, skill_dir / pattern, ""), submission_ref_paths))
return rows
@@ -305,6 +340,10 @@ def build_submission_kit(
artifact_ready_count = sum(1 for item in artifact_checklist if item.get("artifact_ref_ready"))
artifact_missing_count = sum(1 for item in artifact_checklist if not item.get("artifact_ref_ready"))
artifact_glob_count = sum(1 for item in artifact_checklist if item.get("concrete_reference_required"))
submission_ref_rows = [item for item in artifact_checklist if item.get("submission_ref_required")]
supporting_rows = [item for item in artifact_checklist if not item.get("submission_ref_required")]
submission_ref_ready_count = sum(1 for item in submission_ref_rows if item.get("artifact_ref_ready"))
supporting_artifact_ready_count = sum(1 for item in supporting_rows if item.get("artifact_ref_ready"))
source_summary = summarize_source_checklist(source_checklist)
matrix_summary = summarize_evidence_matrix(evidence_matrix)
ok = not unknown_keys and skipped_count == 0
@@ -325,6 +364,12 @@ def build_submission_kit(
"artifact_ready_count": artifact_ready_count,
"artifact_missing_count": artifact_missing_count,
"artifact_glob_expansion_count": artifact_glob_count,
"submission_ref_count": len(submission_ref_rows),
"submission_ref_ready_count": submission_ref_ready_count,
"submission_ref_missing_count": len(submission_ref_rows) - submission_ref_ready_count,
"supporting_artifact_count": len(supporting_rows),
"supporting_artifact_ready_count": supporting_artifact_ready_count,
"supporting_artifact_missing_count": len(supporting_rows) - supporting_artifact_ready_count,
"artifact_prefill_enabled": prefill_artifacts,
"artifact_ref_prefill_count": prefilled_artifact_ref_count,
"artifact_ref_unfilled_count": unfilled_artifact_ref_count,
+24 -12
View File
@@ -50,14 +50,15 @@ def render_readme(report: dict[str, Any]) -> str:
"",
"This matrix combines draft, artifact, and source-check readiness into one operator action list. Matrix rows are guidance only; they do not count as completion evidence.",
"",
"| Evidence | Stage | Draft | Artifacts | Source checks | Next action |",
"| --- | --- | --- | ---: | ---: | --- |",
"| Evidence | Stage | Draft | Submission refs | Supporting assets | Source checks | Next action |",
"| --- | --- | --- | ---: | ---: | ---: | --- |",
]
)
for item in report.get("evidence_matrix", []):
lines.append(
f"| `{item['evidence_key']}` | `{item['stage']}` | `{item['draft_status']}` | "
f"`{item['artifact_ready_count']}/{item['artifact_total_count']}` | "
f"`{item.get('submission_ref_ready_count', 0)}/{item.get('submission_ref_total_count', 0)}` | "
f"`{item.get('supporting_artifact_ready_count', 0)}/{item.get('supporting_artifact_total_count', 0)}` | "
f"`{item['source_pass_count']}/{item['source_check_count']}` | {item['next_action']} |"
)
lines.extend(["", "## Execution Runbook", ""])
@@ -77,13 +78,16 @@ def render_readme(report: dict[str, Any]) -> str:
"",
"Use these paths and SHA-256 digests when filling `artifact_refs`. Glob patterns are expanded into concrete files; submissions must reference concrete paths, not globs.",
"",
"| Evidence | Path | Status | SHA-256 |",
"| --- | --- | --- | --- |",
"| Evidence | Role | Path | Status | SHA-256 |",
"| --- | --- | --- | --- | --- |",
]
)
for item in report.get("artifact_checklist", []):
digest = item.get("sha256") or "n/a"
lines.append(f"| `{item['evidence_key']}` | `{item['path']}` | `{item['status']}` | `{digest}` |")
lines.append(
f"| `{item['evidence_key']}` | `{item.get('artifact_role', 'supporting-evidence')}` | "
f"`{item['path']}` | `{item['status']}` | `{digest}` |"
)
lines.extend(
[
"",
@@ -172,6 +176,7 @@ def render_html_artifact_checklist(items: list[dict[str, Any]]) -> str:
</div>
<dl>
<dt>Pattern</dt><dd><code>{pattern}</code></dd>
<dt>Role</dt><dd>{role}</dd>
<dt>Status</dt><dd>{status}</dd>
<dt>SHA-256</dt><dd><code>{sha}</code></dd>
</dl>
@@ -181,6 +186,7 @@ def render_html_artifact_checklist(items: list[dict[str, Any]]) -> str:
key=html_text(item.get("evidence_key", "")),
path=html_text(item.get("path", "")),
pattern=html_text(item.get("source_pattern", "")),
role=html_text(item.get("artifact_role", "supporting-evidence")),
sha=html_text(item.get("sha256") or "n/a"),
)
for item in items
@@ -229,7 +235,8 @@ def render_html_matrix(items: list[dict[str, Any]]) -> str:
</header>
<dl>
<dt>Draft</dt><dd>{draft}</dd>
<dt>Artifacts</dt><dd>{artifact_ready}/{artifact_total} ready</dd>
<dt>Submission refs</dt><dd>{submission_ref_ready}/{submission_ref_total} ready</dd>
<dt>Supporting assets</dt><dd>{supporting_ready}/{supporting_total} ready</dd>
<dt>Source</dt><dd>{source_pass}/{source_total} pass</dd>
<dt>Owner</dt><dd>{owner}</dd>
</dl>
@@ -239,8 +246,10 @@ def render_html_matrix(items: list[dict[str, Any]]) -> str:
stage=html_text(item.get("stage", "")),
key=html_text(item.get("evidence_key", "")),
draft=html_text(item.get("draft_status", "")),
artifact_ready=html_text(item.get("artifact_ready_count", 0)),
artifact_total=html_text(item.get("artifact_total_count", 0)),
submission_ref_ready=html_text(item.get("submission_ref_ready_count", 0)),
submission_ref_total=html_text(item.get("submission_ref_total_count", 0)),
supporting_ready=html_text(item.get("supporting_artifact_ready_count", 0)),
supporting_total=html_text(item.get("supporting_artifact_total_count", 0)),
source_pass=html_text(item.get("source_pass_count", 0)),
source_total=html_text(item.get("source_check_count", 0)),
owner=html_text(item.get("owner", "")),
@@ -291,12 +300,15 @@ def render_html(report: dict[str, Any]) -> str:
summary = report["summary"]
artifact_ready = summary.get("artifact_ready_count", 0)
artifact_total = summary.get("artifact_checklist_count", 0)
submission_ref_ready = summary.get("submission_ref_ready_count", 0)
submission_ref_total = summary.get("submission_ref_count", 0)
stats = [
("Requested", summary["requested_count"]),
("Written", summary["written_count"]),
("Existing", summary["existing_count"]),
("Skipped", summary["skipped_count"]),
("Artifacts", f"{artifact_ready}/{artifact_total}"),
("Submit refs", f"{submission_ref_ready}/{submission_ref_total}"),
("Support", f"{artifact_ready - submission_ref_ready}/{artifact_total - submission_ref_total}"),
("Prefilled", summary.get("artifact_ref_prefill_count", 0)),
]
stat_html = "".join(
@@ -378,9 +390,9 @@ def render_html(report: dict[str, Any]) -> str:
<article class="panel"><h2>Workflow</h2><ol><li>Run the real provider, human review, native permission, or native client telemetry work first.</li><li>Edit the matching JSON draft with aggregate artifact references and provenance metadata.</li><li>Set template_only to false only after real evidence exists.</li><li>Use prefilled SHA-256 values as convenience data, not evidence acceptance.</li><li>Validate intake, refresh the ledger, then guard public claims.</li></ol></article>
<aside class="panel"><h2>Commands</h2><ul class="commands">{render_html_commands(report['commands'])}</ul></aside>
</section>
<section class="section" id="matrix"><h2>Evidence Matrix</h2><p class="muted">The matrix combines draft status, artifact readiness, source checks, and the next operator action. It is guidance only and never counts as accepted evidence.</p><div class="matrix-grid">{matrix_html}</div></section>
<section class="section" id="matrix"><h2>Evidence Matrix</h2><p class="muted">The matrix separates submission artifact_refs from supporting assets, then combines draft status, source checks, and the next operator action. It is guidance only and never counts as accepted evidence.</p><div class="matrix-grid">{matrix_html}</div></section>
<section class="section" id="drafts"><h2>Drafts</h2><div class="draft-grid">{render_html_files(report['files'])}</div></section>
<section class="section" id="artifacts"><h2>Artifact Checklist</h2><p class="muted">Copy concrete paths and SHA-256 digests from here into artifact_refs after real evidence exists. Glob patterns are expanded for operator convenience only.</p><div class="artifact-grid">{artifact_html}</div></section>
<section class="section" id="artifacts"><h2>Artifact Checklist</h2><p class="muted">Rows marked submission-ref are the paths expected in artifact_refs. Supporting-evidence rows help reviewers audit the packet but do not all need to be copied into the submission. Glob patterns are expanded for operator convenience only.</p><div class="artifact-grid">{artifact_html}</div></section>
<section class="section" id="source"><h2>Source Evidence Snapshot</h2><p class="muted">This section shows current aggregate source checks. It explains remaining blockers without changing the ledger.</p><div class="source-grid">{source_html}</div></section>
<section class="section" id="evidence"><h2>Evidence Requirements</h2><div class="evidence-grid">{evidence_html}</div></section>
<section class="section" id="safety"><h2>Safety Boundary</h2><div class="notice"><ul><li>Drafts never count as accepted ledger evidence.</li><li>Valid intake means ready for ledger review, not world-class completion.</li><li>Do not include credentials, raw prompts, raw outputs, transcripts, notes, or private user content.</li></ul></div></section>
+20
View File
@@ -93,6 +93,10 @@ def build_evidence_matrix(
draft_status, draft_path = _draft_status(files_by_key, key)
artifact_ready_count = sum(1 for row in artifact_rows if row.get("artifact_ref_ready"))
artifact_missing_count = len(artifact_rows) - artifact_ready_count
submission_ref_rows = [row for row in artifact_rows if row.get("submission_ref_required")]
supporting_rows = [row for row in artifact_rows if not row.get("submission_ref_required")]
submission_ref_ready_count = sum(1 for row in submission_ref_rows if row.get("artifact_ref_ready"))
supporting_artifact_ready_count = sum(1 for row in supporting_rows if row.get("artifact_ref_ready"))
source_pass_count = sum(1 for row in source_rows if row.get("status") == "pass")
source_blocked_count = len(source_rows) - source_pass_count
invalid_draft = draft_status not in {"written", "exists"}
@@ -115,6 +119,12 @@ def build_evidence_matrix(
"artifact_ready_count": artifact_ready_count,
"artifact_total_count": len(artifact_rows),
"artifact_missing_count": artifact_missing_count,
"submission_ref_ready_count": submission_ref_ready_count,
"submission_ref_total_count": len(submission_ref_rows),
"submission_ref_missing_count": len(submission_ref_rows) - submission_ref_ready_count,
"supporting_artifact_ready_count": supporting_artifact_ready_count,
"supporting_artifact_total_count": len(supporting_rows),
"supporting_artifact_missing_count": len(supporting_rows) - supporting_artifact_ready_count,
"source_pass_count": source_pass_count,
"source_check_count": len(source_rows),
"source_blocked_count": source_blocked_count,
@@ -137,5 +147,15 @@ def summarize_evidence_matrix(matrix: list[dict[str, Any]]) -> dict[str, int]:
"evidence_matrix_prepare_draft_count": sum(1 for item in matrix if item.get("stage") == "prepare-draft"),
"evidence_matrix_fix_artifacts_count": sum(1 for item in matrix if item.get("stage") == "fix-artifacts"),
"evidence_matrix_validate_packet_count": sum(1 for item in matrix if item.get("stage") == "validate-packet"),
"evidence_matrix_submission_ref_count": sum(int(item.get("submission_ref_total_count", 0)) for item in matrix),
"evidence_matrix_submission_ref_ready_count": sum(
int(item.get("submission_ref_ready_count", 0)) for item in matrix
),
"evidence_matrix_supporting_artifact_count": sum(
int(item.get("supporting_artifact_total_count", 0)) for item in matrix
),
"evidence_matrix_supporting_artifact_ready_count": sum(
int(item.get("supporting_artifact_ready_count", 0)) for item in matrix
),
"evidence_matrix_counts_as_completion": 0,
}