feat(task-018): add observability retry cancel and audit trail
This commit is contained in:
@@ -20,6 +20,7 @@ new Function("exports", compiled.outputText)(moduleExports);
|
||||
|
||||
const {
|
||||
buildAdminScriptVersionRows,
|
||||
buildAdminScriptAuditRows,
|
||||
defaultScriptVersionDraft,
|
||||
buildCreateScriptVersionPayload,
|
||||
parseJsonObject,
|
||||
@@ -53,3 +54,21 @@ assert.equal(payload.rollback_target_version_id, null);
|
||||
assert.deepEqual(parseJsonObject('{"a":1}'), { a: 1 });
|
||||
assert.equal(parseJsonObject("[]"), null);
|
||||
assert.deepEqual(parseJsonObject(""), {});
|
||||
|
||||
const auditRows = buildAdminScriptAuditRows([
|
||||
{
|
||||
id: "evt-1",
|
||||
target_site_id: "site-1",
|
||||
version_id: "ver-1",
|
||||
event_type: "SCRIPT_CONFIG_VERSION_ROLLBACK",
|
||||
actor_user_id: "user-1",
|
||||
payload: { action: "rollback" },
|
||||
created_at: "2026-05-21T12:00:00Z",
|
||||
version: 2,
|
||||
diff: { summary: "rollback event" },
|
||||
rollback_target_version_id: "ver-0",
|
||||
},
|
||||
]);
|
||||
assert.equal(auditRows.length, 1);
|
||||
assert.equal(auditRows[0].eventType, "SCRIPT_CONFIG_VERSION_ROLLBACK");
|
||||
assert.equal(auditRows[0].rollbackTargetVersionId, "ver-0");
|
||||
|
||||
@@ -15,100 +15,14 @@ const compiled = ts.transpileModule(source, {
|
||||
const moduleExports = {};
|
||||
new Function("exports", compiled.outputText)(moduleExports);
|
||||
|
||||
const { buildDetailSummary, buildProductionArtifactRows } = moduleExports;
|
||||
const { buildArticleTimeline, buildJobDetailRows, buildDetailSummary, buildJobControlState } = moduleExports;
|
||||
|
||||
const rows = buildProductionArtifactRows([
|
||||
{
|
||||
id: "job-section-1",
|
||||
article_id: "a1",
|
||||
parent_job_id: null,
|
||||
attempt: 1,
|
||||
job_type: "SECTION_SCAFFOLD",
|
||||
agent_profile: "fake-section-scaffold",
|
||||
status: "SUCCEEDED",
|
||||
workspace_path: null,
|
||||
input_files: [],
|
||||
output_files: [],
|
||||
payload: {
|
||||
artifact_key: "section:1",
|
||||
artifact_label: "Context and scope",
|
||||
used_evidence_ids: ["e1", "e2"],
|
||||
unsupported_claims: [],
|
||||
},
|
||||
error_category: null,
|
||||
error_message: null,
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
exit_code: 0,
|
||||
duration_ms: 3,
|
||||
queued_at: "2026-05-21T00:00:00Z",
|
||||
started_at: "2026-05-21T00:00:01Z",
|
||||
finished_at: "2026-05-21T00:00:02Z",
|
||||
},
|
||||
{
|
||||
id: "job-section-2",
|
||||
article_id: "a1",
|
||||
parent_job_id: null,
|
||||
attempt: 1,
|
||||
job_type: "SECTION_SCAFFOLD",
|
||||
agent_profile: "fake-section-scaffold",
|
||||
status: "FAILED",
|
||||
workspace_path: null,
|
||||
input_files: [],
|
||||
output_files: [],
|
||||
payload: {
|
||||
artifact_key: "section:2",
|
||||
artifact_label: "Implementation workflow",
|
||||
used_evidence_ids: ["e2"],
|
||||
unsupported_claims: [{ claim_text: "Unverified claim" }],
|
||||
},
|
||||
error_category: "UNSUPPORTED_CLAIMS_FOUND",
|
||||
error_message: "Unsupported claims introduced during scaffolding: 1",
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
exit_code: 0,
|
||||
duration_ms: 3,
|
||||
queued_at: "2026-05-21T00:00:00Z",
|
||||
started_at: "2026-05-21T00:00:01Z",
|
||||
finished_at: "2026-05-21T00:00:02Z",
|
||||
},
|
||||
{
|
||||
id: "job-non-artifact",
|
||||
article_id: "a1",
|
||||
parent_job_id: null,
|
||||
attempt: 1,
|
||||
job_type: "TEST_CODEX",
|
||||
agent_profile: "fake-codex",
|
||||
status: "SUCCEEDED",
|
||||
workspace_path: null,
|
||||
input_files: [],
|
||||
output_files: [],
|
||||
payload: {},
|
||||
error_category: null,
|
||||
error_message: null,
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
exit_code: 0,
|
||||
duration_ms: 0,
|
||||
queued_at: "2026-05-21T00:00:00Z",
|
||||
started_at: "2026-05-21T00:00:01Z",
|
||||
finished_at: "2026-05-21T00:00:02Z",
|
||||
},
|
||||
]);
|
||||
|
||||
assert.equal(rows.length, 2);
|
||||
assert.equal(rows[0].artifactLabel, "Context and scope");
|
||||
assert.equal(rows[0].status, "SUCCEEDED");
|
||||
assert.deepEqual(rows[0].usedEvidenceIds, ["e1", "e2"]);
|
||||
assert.equal(rows[1].status, "FAILED");
|
||||
assert.deepEqual(rows[1].unsupportedClaims, ["Unverified claim"]);
|
||||
|
||||
const detailSummary = buildDetailSummary({
|
||||
const detail = {
|
||||
article: {
|
||||
id: "a1",
|
||||
target_site_id: "site1",
|
||||
status: "PUBLISH_COMMIT_CREATED",
|
||||
publishing_status: "PUBLISH_COMMIT_CREATED",
|
||||
status: "PARALLEL_PRODUCTION_RUNNING",
|
||||
publishing_status: "PUBLISH_NOT_STARTED",
|
||||
brief_description: "desc",
|
||||
language: "en",
|
||||
content_type: "longform_guide",
|
||||
@@ -116,26 +30,106 @@ const detailSummary = buildDetailSummary({
|
||||
updated_at: "2026-05-21T00:00:00Z",
|
||||
},
|
||||
target_site: { name: "Demo Site" },
|
||||
workflow_events: [],
|
||||
agent_jobs: [],
|
||||
publish_commit: {
|
||||
id: "pc1",
|
||||
article_id: "a1",
|
||||
target_site_id: "site1",
|
||||
repository_url: "/tmp/repo.git",
|
||||
branch: "main",
|
||||
commit_sha: "abc123",
|
||||
status: "PUBLISH_COMMIT_CREATED",
|
||||
created_at: "2026-05-21T00:00:00Z",
|
||||
content_bundle_manifest: {
|
||||
validation: {
|
||||
label: "Best-effort content-shape validation only.",
|
||||
},
|
||||
timeline: [
|
||||
{
|
||||
id: "2",
|
||||
article_id: "a1",
|
||||
entry_type: "AGENT_JOB",
|
||||
source: "AGENT",
|
||||
event_type: "SECTION_SCAFFOLD_FAILED",
|
||||
from_status: null,
|
||||
to_status: null,
|
||||
actor_user_id: null,
|
||||
job_id: "job2",
|
||||
job_type: "SECTION_SCAFFOLD",
|
||||
job_status: "FAILED",
|
||||
retry_eligible: true,
|
||||
cancel_eligible: false,
|
||||
safe_failure_summary: "Section failed.",
|
||||
payload: {},
|
||||
created_at: "2026-05-21T00:00:02Z",
|
||||
},
|
||||
},
|
||||
});
|
||||
{
|
||||
id: "1",
|
||||
article_id: "a1",
|
||||
entry_type: "WORKFLOW_EVENT",
|
||||
source: "USER",
|
||||
event_type: "PLAN_APPROVED",
|
||||
from_status: "PLAN_REVIEW_REQUIRED",
|
||||
to_status: "RESEARCH_RUNNING",
|
||||
actor_user_id: "u1",
|
||||
job_id: null,
|
||||
job_type: null,
|
||||
job_status: null,
|
||||
retry_eligible: false,
|
||||
cancel_eligible: false,
|
||||
safe_failure_summary: null,
|
||||
payload: {},
|
||||
created_at: "2026-05-21T00:00:01Z",
|
||||
},
|
||||
],
|
||||
workflow_events: [],
|
||||
agent_jobs: [
|
||||
{
|
||||
id: "job2",
|
||||
article_id: "a1",
|
||||
parent_job_id: null,
|
||||
attempt: 1,
|
||||
job_type: "SECTION_SCAFFOLD",
|
||||
agent_profile: "fake-section-scaffold",
|
||||
status: "FAILED",
|
||||
workspace_path: null,
|
||||
input_files: [],
|
||||
output_files: [],
|
||||
payload: { last_successful_step: "Outline written" },
|
||||
error_category: "UNSUPPORTED_CLAIMS_FOUND",
|
||||
error_message: "Unsupported claims introduced during scaffolding: 1",
|
||||
stdout: "token=[REDACTED]",
|
||||
stderr: "",
|
||||
exit_code: 1,
|
||||
duration_ms: 5,
|
||||
queued_at: "2026-05-21T00:00:00Z",
|
||||
started_at: "2026-05-21T00:00:01Z",
|
||||
finished_at: "2026-05-21T00:00:02Z",
|
||||
retry_eligible: true,
|
||||
retry_block_reason: null,
|
||||
cancel_eligible: false,
|
||||
safe_failure_summary: "Section failed safely.",
|
||||
},
|
||||
],
|
||||
publish_commit: null,
|
||||
};
|
||||
|
||||
assert.equal(
|
||||
detailSummary.publishingValidationLabel,
|
||||
"Best-effort content-shape validation only.",
|
||||
const timeline = buildArticleTimeline(detail);
|
||||
assert.equal(timeline.length, 2);
|
||||
assert.equal(timeline[0].eventType, "PLAN_APPROVED");
|
||||
assert.equal(timeline[1].eventType, "SECTION_SCAFFOLD_FAILED");
|
||||
assert.equal(timeline[1].source, "AGENT");
|
||||
assert.equal(timeline[1].retryEligible, true);
|
||||
|
||||
const jobRows = buildJobDetailRows(detail.agent_jobs);
|
||||
assert.equal(jobRows.length, 1);
|
||||
assert.equal(jobRows[0].jobType, "SECTION_SCAFFOLD");
|
||||
assert.equal(jobRows[0].status, "FAILED");
|
||||
assert.equal(jobRows[0].errorCategory, "UNSUPPORTED_CLAIMS_FOUND");
|
||||
assert.equal(jobRows[0].errorMessage, "Unsupported claims introduced during scaffolding: 1");
|
||||
assert.equal(jobRows[0].lastSuccessfulStep, "Outline written");
|
||||
assert.equal(jobRows[0].retryEligible, true);
|
||||
assert.deepEqual(
|
||||
buildJobControlState(jobRows[0], "ADMIN"),
|
||||
{ showRetry: true, showCancel: false, showAdminLogs: true, showSafeSummary: false },
|
||||
);
|
||||
assert.deepEqual(
|
||||
buildJobControlState(jobRows[0], "EDITOR"),
|
||||
{ showRetry: false, showCancel: false, showAdminLogs: false, showSafeSummary: true },
|
||||
);
|
||||
|
||||
const summary = buildDetailSummary(detail, {
|
||||
viewerRole: "EDITOR",
|
||||
viewerEmail: "editor@example.com",
|
||||
});
|
||||
assert.equal(summary.viewerRole, "EDITOR");
|
||||
assert.equal(summary.viewerEmail, "editor@example.com");
|
||||
assert.equal(summary.targetSite, "Demo Site");
|
||||
assert.equal(summary.timeline.length, 2);
|
||||
assert.equal(summary.jobDetails.length, 1);
|
||||
|
||||
Reference in New Issue
Block a user