@hunter-harness/workflow-harness 0.2.10 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/harness/bundles/general/claude-code/.harness-build.json +1 -1
- package/harness/bundles/general/claude-code/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/general/claude-code/contracts/workflow-policy.json +30 -0
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -2
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +4 -4
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/claude-code/harness-plan/reference.md +7 -2
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/checklist.md +3 -2
- package/harness/bundles/general/claude-code/harness-run/reference.md +6 -4
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +282 -120
- package/harness/bundles/general/claude-code/scripts/harness_events.py +400 -24
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +585 -21
- package/harness/bundles/general/claude-code/scripts/harness_integration.py +403 -18
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +225 -29
- package/harness/bundles/general/claude-code/scripts/harness_phase.py +926 -0
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +50 -0
- package/harness/bundles/general/claude-code/scripts/harness_runtime.py +248 -0
- package/harness/bundles/general/claude-code/scripts/harness_state.py +15 -2
- package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/general/claude-code/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +30 -0
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -2
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +4 -4
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/codebuddy/harness-plan/reference.md +7 -2
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +3 -2
- package/harness/bundles/general/codebuddy/harness-run/reference.md +6 -4
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +282 -120
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +400 -24
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +585 -21
- package/harness/bundles/general/codebuddy/scripts/harness_integration.py +403 -18
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +225 -29
- package/harness/bundles/general/codebuddy/scripts/harness_phase.py +926 -0
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +50 -0
- package/harness/bundles/general/codebuddy/scripts/harness_runtime.py +248 -0
- package/harness/bundles/general/codebuddy/scripts/harness_state.py +15 -2
- package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/general/codebuddy/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/general/codex/contracts/workflow-policy.json +30 -0
- package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -2
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +4 -4
- package/harness/bundles/general/codex/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/codex/harness-plan/reference.md +7 -2
- package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/checklist.md +3 -2
- package/harness/bundles/general/codex/harness-run/reference.md +6 -4
- package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codex/scripts/harness_archive.py +282 -120
- package/harness/bundles/general/codex/scripts/harness_events.py +400 -24
- package/harness/bundles/general/codex/scripts/harness_gate.py +585 -21
- package/harness/bundles/general/codex/scripts/harness_integration.py +403 -18
- package/harness/bundles/general/codex/scripts/harness_ledger.py +225 -29
- package/harness/bundles/general/codex/scripts/harness_phase.py +926 -0
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/general/codex/scripts/harness_profile.py +50 -0
- package/harness/bundles/general/codex/scripts/harness_runtime.py +248 -0
- package/harness/bundles/general/codex/scripts/harness_state.py +15 -2
- package/harness/bundles/general/codex/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/general/codex/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/general/cursor/contracts/workflow-policy.json +30 -0
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -2
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +4 -4
- package/harness/bundles/general/cursor/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/cursor/harness-plan/reference.md +7 -2
- package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/checklist.md +3 -2
- package/harness/bundles/general/cursor/harness-run/reference.md +6 -4
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/cursor/scripts/harness_archive.py +282 -120
- package/harness/bundles/general/cursor/scripts/harness_events.py +400 -24
- package/harness/bundles/general/cursor/scripts/harness_gate.py +585 -21
- package/harness/bundles/general/cursor/scripts/harness_integration.py +403 -18
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +225 -29
- package/harness/bundles/general/cursor/scripts/harness_phase.py +926 -0
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/general/cursor/scripts/harness_profile.py +50 -0
- package/harness/bundles/general/cursor/scripts/harness_runtime.py +248 -0
- package/harness/bundles/general/cursor/scripts/harness_state.py +15 -2
- package/harness/bundles/general/cursor/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/general/cursor/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/java/claude-code/contracts/workflow-policy.json +30 -0
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -2
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +4 -4
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/claude-code/harness-plan/reference.md +7 -2
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +282 -120
- package/harness/bundles/java/claude-code/scripts/harness_events.py +400 -24
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +585 -21
- package/harness/bundles/java/claude-code/scripts/harness_integration.py +403 -18
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +225 -29
- package/harness/bundles/java/claude-code/scripts/harness_phase.py +926 -0
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +50 -0
- package/harness/bundles/java/claude-code/scripts/harness_runtime.py +248 -0
- package/harness/bundles/java/claude-code/scripts/harness_state.py +15 -2
- package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/java/claude-code/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +30 -0
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -2
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +4 -4
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +7 -2
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +282 -120
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +400 -24
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +585 -21
- package/harness/bundles/java/codebuddy/scripts/harness_integration.py +403 -18
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +225 -29
- package/harness/bundles/java/codebuddy/scripts/harness_phase.py +926 -0
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +50 -0
- package/harness/bundles/java/codebuddy/scripts/harness_runtime.py +248 -0
- package/harness/bundles/java/codebuddy/scripts/harness_state.py +15 -2
- package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/java/codebuddy/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/java/codex/contracts/workflow-policy.json +30 -0
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -2
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +4 -4
- package/harness/bundles/java/codex/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/codex/harness-plan/reference.md +7 -2
- package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codex/scripts/harness_archive.py +282 -120
- package/harness/bundles/java/codex/scripts/harness_events.py +400 -24
- package/harness/bundles/java/codex/scripts/harness_gate.py +585 -21
- package/harness/bundles/java/codex/scripts/harness_integration.py +403 -18
- package/harness/bundles/java/codex/scripts/harness_ledger.py +225 -29
- package/harness/bundles/java/codex/scripts/harness_phase.py +926 -0
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/java/codex/scripts/harness_profile.py +50 -0
- package/harness/bundles/java/codex/scripts/harness_runtime.py +248 -0
- package/harness/bundles/java/codex/scripts/harness_state.py +15 -2
- package/harness/bundles/java/codex/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/java/codex/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/java/cursor/contracts/workflow-policy.json +30 -0
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -2
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +4 -4
- package/harness/bundles/java/cursor/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/cursor/harness-plan/reference.md +7 -2
- package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/cursor/scripts/harness_archive.py +282 -120
- package/harness/bundles/java/cursor/scripts/harness_events.py +400 -24
- package/harness/bundles/java/cursor/scripts/harness_gate.py +585 -21
- package/harness/bundles/java/cursor/scripts/harness_integration.py +403 -18
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +225 -29
- package/harness/bundles/java/cursor/scripts/harness_phase.py +926 -0
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/java/cursor/scripts/harness_profile.py +50 -0
- package/harness/bundles/java/cursor/scripts/harness_runtime.py +248 -0
- package/harness/bundles/java/cursor/scripts/harness_state.py +15 -2
- package/harness/bundles/java/cursor/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/java/cursor/scripts/harness_workflow_policy.py +19 -0
- package/harness/manifests/general/claude-code.json +43 -27
- package/harness/manifests/general/codebuddy.json +43 -27
- package/harness/manifests/general/codex.json +43 -27
- package/harness/manifests/general/cursor.json +43 -27
- package/harness/manifests/java/claude-code.json +43 -27
- package/harness/manifests/java/codebuddy.json +43 -27
- package/harness/manifests/java/codex.json +43 -27
- package/harness/manifests/java/cursor.json +43 -27
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -12,10 +12,13 @@ Python 3.10+, stdlib only. UTF-8 without BOM. Windows path safe.
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
import argparse
|
|
15
|
+
import copy
|
|
15
16
|
import contextlib
|
|
16
17
|
import datetime as dt
|
|
18
|
+
import hashlib
|
|
17
19
|
import json
|
|
18
20
|
import os
|
|
21
|
+
import re
|
|
19
22
|
import sys
|
|
20
23
|
import tempfile
|
|
21
24
|
import time
|
|
@@ -44,7 +47,9 @@ EVENT_TYPES = frozenset(
|
|
|
44
47
|
"verification",
|
|
45
48
|
"artifact",
|
|
46
49
|
"issue",
|
|
50
|
+
"issue.resolve",
|
|
47
51
|
"decision",
|
|
52
|
+
"correction",
|
|
48
53
|
}
|
|
49
54
|
)
|
|
50
55
|
|
|
@@ -69,6 +74,12 @@ OPTIONAL_FIELDS = (
|
|
|
69
74
|
"message",
|
|
70
75
|
"decision",
|
|
71
76
|
"reason",
|
|
77
|
+
"issue_id",
|
|
78
|
+
"scope",
|
|
79
|
+
"target_event_id",
|
|
80
|
+
"target_field",
|
|
81
|
+
"old_value_hash",
|
|
82
|
+
"new_value_json",
|
|
72
83
|
"run_id",
|
|
73
84
|
"attempt",
|
|
74
85
|
"executor_tool",
|
|
@@ -76,6 +87,78 @@ OPTIONAL_FIELDS = (
|
|
|
76
87
|
"executor_model",
|
|
77
88
|
"handoff_from_tool",
|
|
78
89
|
"handoff_reason",
|
|
90
|
+
"trace_id",
|
|
91
|
+
"span_id",
|
|
92
|
+
"parent_span_id",
|
|
93
|
+
"runner_ms",
|
|
94
|
+
"orchestration_active_ms",
|
|
95
|
+
"wall_clock_ms",
|
|
96
|
+
"user_wait_ms",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
_PROVENANCE_FIELDS = frozenset(
|
|
100
|
+
{
|
|
101
|
+
"run_id",
|
|
102
|
+
"attempt",
|
|
103
|
+
"executor_tool",
|
|
104
|
+
"executor_agent",
|
|
105
|
+
"executor_model",
|
|
106
|
+
"handoff_from_tool",
|
|
107
|
+
"handoff_reason",
|
|
108
|
+
"trace_id",
|
|
109
|
+
"span_id",
|
|
110
|
+
"parent_span_id",
|
|
111
|
+
"runner_ms",
|
|
112
|
+
"orchestration_active_ms",
|
|
113
|
+
"wall_clock_ms",
|
|
114
|
+
"user_wait_ms",
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
_EVENT_ALLOWED_FIELDS = {
|
|
118
|
+
"phase.start": frozenset({"note"}) | _PROVENANCE_FIELDS,
|
|
119
|
+
"phase.end": frozenset(
|
|
120
|
+
{"status", "duration_ms", "note", "reason", "issue_id"}
|
|
121
|
+
)
|
|
122
|
+
| _PROVENANCE_FIELDS,
|
|
123
|
+
"command": frozenset({"command", "exit_code", "duration_ms", "note"})
|
|
124
|
+
| _PROVENANCE_FIELDS,
|
|
125
|
+
"verification": frozenset(
|
|
126
|
+
{"name", "status", "reason", "command", "exit_code", "duration_ms", "note"}
|
|
127
|
+
)
|
|
128
|
+
| _PROVENANCE_FIELDS,
|
|
129
|
+
"artifact": frozenset({"path", "kind", "note"}) | _PROVENANCE_FIELDS,
|
|
130
|
+
"issue": frozenset({"code", "scope", "severity", "message", "reason", "note"})
|
|
131
|
+
| frozenset({"issue_id"})
|
|
132
|
+
| _PROVENANCE_FIELDS,
|
|
133
|
+
"issue.resolve": frozenset({"issue_id", "reason", "note"})
|
|
134
|
+
| _PROVENANCE_FIELDS,
|
|
135
|
+
"decision": frozenset({"decision", "reason", "note"}) | _PROVENANCE_FIELDS,
|
|
136
|
+
"correction": frozenset(
|
|
137
|
+
{
|
|
138
|
+
"target_event_id",
|
|
139
|
+
"target_field",
|
|
140
|
+
"old_value_hash",
|
|
141
|
+
"new_value_json",
|
|
142
|
+
"reason",
|
|
143
|
+
"note",
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
| _PROVENANCE_FIELDS,
|
|
147
|
+
}
|
|
148
|
+
_EVENT_REQUIRED_FIELDS = {
|
|
149
|
+
"issue": ("severity",),
|
|
150
|
+
"issue.resolve": ("issue_id", "reason"),
|
|
151
|
+
"verification": ("name", "status"),
|
|
152
|
+
"correction": (
|
|
153
|
+
"target_event_id",
|
|
154
|
+
"target_field",
|
|
155
|
+
"old_value_hash",
|
|
156
|
+
"new_value_json",
|
|
157
|
+
"reason",
|
|
158
|
+
),
|
|
159
|
+
}
|
|
160
|
+
_CORRECTION_PROTECTED_FIELDS = frozenset(
|
|
161
|
+
{"schema_version", "id", "timestamp", "phase", "type"}
|
|
79
162
|
)
|
|
80
163
|
|
|
81
164
|
|
|
@@ -92,8 +175,16 @@ def emit_json(payload: dict[str, Any], *, as_json: bool) -> None:
|
|
|
92
175
|
sys.stdout.write(f"{msg}\n")
|
|
93
176
|
|
|
94
177
|
|
|
95
|
-
def emit_error(
|
|
178
|
+
def emit_error(
|
|
179
|
+
message: str,
|
|
180
|
+
*,
|
|
181
|
+
as_json: bool,
|
|
182
|
+
code: int = 1,
|
|
183
|
+
error_code: str | None = None,
|
|
184
|
+
) -> int:
|
|
96
185
|
payload = {"ok": False, "error": message}
|
|
186
|
+
if error_code:
|
|
187
|
+
payload["code"] = error_code
|
|
97
188
|
if as_json:
|
|
98
189
|
sys.stderr.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
|
99
190
|
else:
|
|
@@ -165,11 +256,109 @@ def normalize_event(raw: dict[str, Any]) -> dict[str, Any]:
|
|
|
165
256
|
except (TypeError, ValueError):
|
|
166
257
|
version_int = 1
|
|
167
258
|
event["schema_version"] = version_int
|
|
259
|
+
if version_int < SCHEMA_VERSION and "schemaValidation" not in event:
|
|
260
|
+
event["schemaValidation"] = "legacy"
|
|
168
261
|
if "note" not in event or event["note"] is None:
|
|
169
262
|
event["note"] = ""
|
|
170
263
|
return event
|
|
171
264
|
|
|
172
265
|
|
|
266
|
+
def canonical_value_hash(value: Any) -> str:
|
|
267
|
+
"""Return the stable optimistic-concurrency hash for a corrected value."""
|
|
268
|
+
payload = json.dumps(
|
|
269
|
+
value,
|
|
270
|
+
ensure_ascii=False,
|
|
271
|
+
sort_keys=True,
|
|
272
|
+
separators=(",", ":"),
|
|
273
|
+
).encode("utf-8")
|
|
274
|
+
return "sha256:" + hashlib.sha256(payload).hexdigest()
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def apply_event_corrections(events: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
278
|
+
"""Project append-only correction events without mutating event history.
|
|
279
|
+
|
|
280
|
+
Corrections may target only earlier events and use an old-value hash as an
|
|
281
|
+
optimistic-concurrency guard. The correction records remain in the raw
|
|
282
|
+
stream; projections return only the corrected domain events.
|
|
283
|
+
"""
|
|
284
|
+
projected: list[dict[str, Any]] = []
|
|
285
|
+
by_id: dict[str, dict[str, Any]] = {}
|
|
286
|
+
for raw in events:
|
|
287
|
+
event = copy.deepcopy(raw)
|
|
288
|
+
if event.get("type") != "correction":
|
|
289
|
+
projected.append(event)
|
|
290
|
+
event_id = str(event.get("id") or "").strip()
|
|
291
|
+
if event_id:
|
|
292
|
+
by_id[event_id] = event
|
|
293
|
+
continue
|
|
294
|
+
|
|
295
|
+
correction_id = str(event.get("id") or "<unknown>")
|
|
296
|
+
target_id = str(event.get("target_event_id") or "").strip()
|
|
297
|
+
target = by_id.get(target_id)
|
|
298
|
+
if target is None:
|
|
299
|
+
raise ValueError(
|
|
300
|
+
f"CORRECTION_TARGET_NOT_FOUND: {correction_id} targets {target_id}"
|
|
301
|
+
)
|
|
302
|
+
field = str(event.get("target_field") or "").strip()
|
|
303
|
+
if not field or field in _CORRECTION_PROTECTED_FIELDS:
|
|
304
|
+
raise ValueError(
|
|
305
|
+
f"CORRECTION_FIELD_NOT_ALLOWED: {correction_id} targets {field}"
|
|
306
|
+
)
|
|
307
|
+
actual_hash = canonical_value_hash(target.get(field))
|
|
308
|
+
expected_hash = str(event.get("old_value_hash") or "").strip()
|
|
309
|
+
if actual_hash != expected_hash:
|
|
310
|
+
raise ValueError(
|
|
311
|
+
"CORRECTION_OLD_VALUE_MISMATCH: "
|
|
312
|
+
f"{correction_id} expected {expected_hash}, found {actual_hash}"
|
|
313
|
+
)
|
|
314
|
+
target[field] = copy.deepcopy(event.get("new_value"))
|
|
315
|
+
return projected
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _issue_identity(event: dict[str, Any]) -> str:
|
|
319
|
+
explicit = str(event.get("issue_id") or "").strip()
|
|
320
|
+
if explicit:
|
|
321
|
+
return explicit
|
|
322
|
+
code = str(event.get("code") or "").strip()
|
|
323
|
+
scope = str(event.get("scope") or "").strip()
|
|
324
|
+
if code:
|
|
325
|
+
return f"code:{code}|scope:{scope}"
|
|
326
|
+
return str(event.get("id") or "").strip()
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def current_issues(events: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
330
|
+
"""Return unresolved issues while preserving the full append-only history."""
|
|
331
|
+
active: dict[str, dict[str, Any]] = {}
|
|
332
|
+
for event in apply_event_corrections(events):
|
|
333
|
+
event_type = event.get("type")
|
|
334
|
+
if event_type == "issue":
|
|
335
|
+
identity = _issue_identity(event)
|
|
336
|
+
if identity:
|
|
337
|
+
item = copy.deepcopy(event)
|
|
338
|
+
item["issue_id"] = identity
|
|
339
|
+
active[identity] = item
|
|
340
|
+
elif event_type == "issue.resolve":
|
|
341
|
+
active.pop(_issue_identity(event), None)
|
|
342
|
+
elif event_type == "phase.end" and str(event.get("status") or "").upper() == "OK":
|
|
343
|
+
identity = str(event.get("issue_id") or "").strip()
|
|
344
|
+
if not identity:
|
|
345
|
+
continue
|
|
346
|
+
phase = event.get("phase")
|
|
347
|
+
attempt = event.get("attempt")
|
|
348
|
+
issue = active.get(identity)
|
|
349
|
+
if issue is None or issue.get("phase") != phase:
|
|
350
|
+
continue
|
|
351
|
+
issue_attempt = issue.get("attempt")
|
|
352
|
+
if (
|
|
353
|
+
isinstance(attempt, int)
|
|
354
|
+
and isinstance(issue_attempt, int)
|
|
355
|
+
and issue_attempt >= attempt
|
|
356
|
+
):
|
|
357
|
+
continue
|
|
358
|
+
active.pop(identity, None)
|
|
359
|
+
return list(active.values())
|
|
360
|
+
|
|
361
|
+
|
|
173
362
|
def load_events(path: Path) -> list[dict[str, Any]]:
|
|
174
363
|
if not path.exists():
|
|
175
364
|
return []
|
|
@@ -329,7 +518,12 @@ def build_event(args: argparse.Namespace, existing: list[dict[str, Any]]) -> dic
|
|
|
329
518
|
value = getattr(args, field, None)
|
|
330
519
|
if value is None:
|
|
331
520
|
continue
|
|
332
|
-
|
|
521
|
+
if field == "new_value_json":
|
|
522
|
+
event["new_value"] = json.loads(value)
|
|
523
|
+
else:
|
|
524
|
+
event[field] = value
|
|
525
|
+
if args.type == "issue" and not event.get("issue_id"):
|
|
526
|
+
event["issue_id"] = _issue_identity(event)
|
|
333
527
|
environment_defaults = {
|
|
334
528
|
"run_id": "HUNTER_HARNESS_RUN_ID",
|
|
335
529
|
"executor_tool": "HUNTER_HARNESS_TOOL",
|
|
@@ -344,6 +538,61 @@ def build_event(args: argparse.Namespace, existing: list[dict[str, Any]]) -> dic
|
|
|
344
538
|
return event
|
|
345
539
|
|
|
346
540
|
|
|
541
|
+
def validate_append_event(args: argparse.Namespace) -> tuple[str, str] | None:
|
|
542
|
+
"""Validate type-specific append fields before any file is mutated."""
|
|
543
|
+
event_type = str(args.type)
|
|
544
|
+
for field in _EVENT_REQUIRED_FIELDS.get(event_type, ()):
|
|
545
|
+
value = getattr(args, field, None)
|
|
546
|
+
if value is None or (isinstance(value, str) and not value.strip()):
|
|
547
|
+
return (
|
|
548
|
+
"EVENT_REQUIRED_FIELD",
|
|
549
|
+
f"EVENT_REQUIRED_FIELD: {event_type} requires --{field.replace('_', '-')}",
|
|
550
|
+
)
|
|
551
|
+
allowed = _EVENT_ALLOWED_FIELDS[event_type]
|
|
552
|
+
for field in OPTIONAL_FIELDS:
|
|
553
|
+
value = getattr(args, field, None)
|
|
554
|
+
if value is not None and field not in allowed:
|
|
555
|
+
return (
|
|
556
|
+
"EVENT_FIELD_NOT_ALLOWED",
|
|
557
|
+
"EVENT_FIELD_NOT_ALLOWED: "
|
|
558
|
+
f"{event_type} does not accept --{field.replace('_', '-')}",
|
|
559
|
+
)
|
|
560
|
+
for field, length in (("trace_id", 32), ("span_id", 16), ("parent_span_id", 16)):
|
|
561
|
+
value = getattr(args, field, None)
|
|
562
|
+
if value is not None and not re.fullmatch(rf"[0-9a-f]{{{length}}}", str(value)):
|
|
563
|
+
return (
|
|
564
|
+
"EVENT_TRACE_FIELD_INVALID",
|
|
565
|
+
f"EVENT_TRACE_FIELD_INVALID: --{field.replace('_', '-')} must be {length} lowercase hex characters",
|
|
566
|
+
)
|
|
567
|
+
for field in (
|
|
568
|
+
"runner_ms",
|
|
569
|
+
"orchestration_active_ms",
|
|
570
|
+
"wall_clock_ms",
|
|
571
|
+
"user_wait_ms",
|
|
572
|
+
):
|
|
573
|
+
value = getattr(args, field, None)
|
|
574
|
+
if value is not None and (not isinstance(value, int) or value < 0):
|
|
575
|
+
return (
|
|
576
|
+
"EVENT_TIMING_FIELD_INVALID",
|
|
577
|
+
f"EVENT_TIMING_FIELD_INVALID: --{field.replace('_', '-')} must be a nonnegative integer",
|
|
578
|
+
)
|
|
579
|
+
if event_type == "correction":
|
|
580
|
+
target_field = str(getattr(args, "target_field", "") or "").strip()
|
|
581
|
+
if target_field in _CORRECTION_PROTECTED_FIELDS:
|
|
582
|
+
return (
|
|
583
|
+
"CORRECTION_FIELD_NOT_ALLOWED",
|
|
584
|
+
f"CORRECTION_FIELD_NOT_ALLOWED: cannot correct {target_field}",
|
|
585
|
+
)
|
|
586
|
+
try:
|
|
587
|
+
json.loads(str(getattr(args, "new_value_json", "")))
|
|
588
|
+
except json.JSONDecodeError as exc:
|
|
589
|
+
return (
|
|
590
|
+
"CORRECTION_VALUE_INVALID_JSON",
|
|
591
|
+
f"CORRECTION_VALUE_INVALID_JSON: {exc.msg}",
|
|
592
|
+
)
|
|
593
|
+
return None
|
|
594
|
+
|
|
595
|
+
|
|
347
596
|
def status_symbol(status: Any, reason: Any = None) -> str:
|
|
348
597
|
text = str(status or "").strip().lower()
|
|
349
598
|
reason_text = str(reason or "").strip()
|
|
@@ -475,21 +724,19 @@ def phase_duration_ms(phase_events: list[dict[str, Any]]) -> int | None:
|
|
|
475
724
|
|
|
476
725
|
|
|
477
726
|
def late_event_stats(phase_events: list[dict[str, Any]]) -> dict[str, int]:
|
|
478
|
-
"""Count events recorded after the closing phase.end (RET-21)."""
|
|
479
|
-
|
|
480
|
-
for event in phase_events:
|
|
727
|
+
"""Count events recorded after the final closing phase.end (RET-21)."""
|
|
728
|
+
final_end_index = None
|
|
729
|
+
for index, event in enumerate(phase_events):
|
|
481
730
|
if event.get("type") == "phase.end":
|
|
482
|
-
|
|
483
|
-
if
|
|
731
|
+
final_end_index = index
|
|
732
|
+
if final_end_index is None:
|
|
484
733
|
return {"lateEventCount": 0, "lateEventSpanMs": 0}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if seen_end and event.get("timestamp"):
|
|
492
|
-
late_stamps.append(event["timestamp"])
|
|
734
|
+
end_ts = phase_events[final_end_index].get("timestamp")
|
|
735
|
+
late_stamps = [
|
|
736
|
+
event["timestamp"]
|
|
737
|
+
for event in phase_events[final_end_index + 1 :]
|
|
738
|
+
if event.get("timestamp")
|
|
739
|
+
]
|
|
493
740
|
if not late_stamps:
|
|
494
741
|
return {"lateEventCount": 0, "lateEventSpanMs": 0}
|
|
495
742
|
span = duration_ms_between(end_ts, late_stamps[-1]) or 0
|
|
@@ -523,6 +770,20 @@ def attempt_invocations(phase_events: list[dict[str, Any]]) -> list[dict[str, An
|
|
|
523
770
|
return invocations
|
|
524
771
|
|
|
525
772
|
|
|
773
|
+
def phase_final_state(phase_events: list[dict[str, Any]]) -> dict[str, Any]:
|
|
774
|
+
"""Reduce a phase to the state of its latest attempt."""
|
|
775
|
+
invocations = attempt_invocations(phase_events)
|
|
776
|
+
if not invocations:
|
|
777
|
+
return {"attempt": None, "status": None, "durationMs": None, "closed": False}
|
|
778
|
+
latest = invocations[-1]
|
|
779
|
+
return {
|
|
780
|
+
"attempt": latest.get("attempt"),
|
|
781
|
+
"status": latest.get("status"),
|
|
782
|
+
"durationMs": latest.get("durationMs"),
|
|
783
|
+
"closed": latest.get("status") is not None,
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
|
|
526
787
|
def canonical_phase_timing(phase_events: list[dict[str, Any]]) -> dict[str, Any]:
|
|
527
788
|
"""Single reducer for every duration view (RET-20).
|
|
528
789
|
|
|
@@ -530,7 +791,12 @@ def canonical_phase_timing(phase_events: list[dict[str, Any]]) -> dict[str, Any]
|
|
|
530
791
|
wallClockSpanMs: first → last timestamp in the bucket, late events included.
|
|
531
792
|
"""
|
|
532
793
|
stamps = [e.get("timestamp") for e in phase_events if e.get("timestamp")]
|
|
533
|
-
|
|
794
|
+
invocation_durations = [
|
|
795
|
+
invocation["durationMs"]
|
|
796
|
+
for invocation in attempt_invocations(phase_events)
|
|
797
|
+
if invocation.get("durationMs") is not None
|
|
798
|
+
]
|
|
799
|
+
active = sum(invocation_durations) if invocation_durations else None
|
|
534
800
|
late = late_event_stats(phase_events)
|
|
535
801
|
wall = duration_ms_between(stamps[0], stamps[-1]) if len(stamps) >= 2 else active
|
|
536
802
|
return {
|
|
@@ -639,6 +905,18 @@ def render_event_line(event: dict[str, Any]) -> list[str]:
|
|
|
639
905
|
code = event.get("code")
|
|
640
906
|
prefix = f"[{code}] " if code else ""
|
|
641
907
|
return [f"- issue: {prefix}{symbol}"]
|
|
908
|
+
if etype == "issue.resolve":
|
|
909
|
+
issue_id = str(event.get("issue_id") or "").strip()
|
|
910
|
+
reason = str(event.get("reason") or event.get("note") or "").strip()
|
|
911
|
+
suffix = f" — {reason}" if reason else ""
|
|
912
|
+
return [f"- issue.resolve: {issue_id}{suffix}"] if issue_id else []
|
|
913
|
+
if etype == "correction":
|
|
914
|
+
target = str(event.get("target_event_id") or "").strip()
|
|
915
|
+
field = str(event.get("target_field") or "").strip()
|
|
916
|
+
reason = str(event.get("reason") or event.get("note") or "").strip()
|
|
917
|
+
suffix = f" — {reason}" if reason else ""
|
|
918
|
+
label = f"{target}.{field}".strip(".")
|
|
919
|
+
return [f"- correction: {label}{suffix}"] if label else []
|
|
642
920
|
if etype == "artifact":
|
|
643
921
|
path = str(event.get("path") or "").strip()
|
|
644
922
|
note = str(event.get("note") or "").strip()
|
|
@@ -666,7 +944,14 @@ def render_execution_log(events: list[dict[str, Any]]) -> str:
|
|
|
666
944
|
lines.append("")
|
|
667
945
|
return "\n".join(lines)
|
|
668
946
|
|
|
669
|
-
|
|
947
|
+
projected_domain_events = iter(apply_event_corrections(events))
|
|
948
|
+
projected_events = [
|
|
949
|
+
copy.deepcopy(event)
|
|
950
|
+
if event.get("type") == "correction"
|
|
951
|
+
else next(projected_domain_events)
|
|
952
|
+
for event in events
|
|
953
|
+
]
|
|
954
|
+
for phase, phase_events in group_events_by_phase(projected_events):
|
|
670
955
|
attempts = split_phase_attempts(phase_events)
|
|
671
956
|
for attempt_record in attempts:
|
|
672
957
|
attempt_events = attempt_record["events"]
|
|
@@ -710,8 +995,9 @@ def write_execution_log(change_dir: Path, content: str) -> Path:
|
|
|
710
995
|
def build_summary(change_dir: Path, events: list[dict[str, Any]]) -> dict[str, Any]:
|
|
711
996
|
phases: dict[str, Any] = {}
|
|
712
997
|
issues: list[dict[str, Any]] = []
|
|
998
|
+
projected_events = apply_event_corrections(events)
|
|
713
999
|
|
|
714
|
-
for phase, phase_events in group_events_by_phase(
|
|
1000
|
+
for phase, phase_events in group_events_by_phase(projected_events):
|
|
715
1001
|
attempt_records: list[dict[str, Any]] = []
|
|
716
1002
|
for record in split_phase_attempts(phase_events):
|
|
717
1003
|
attempt_events = record["events"]
|
|
@@ -745,12 +1031,13 @@ def build_summary(change_dir: Path, events: list[dict[str, Any]]) -> dict[str, A
|
|
|
745
1031
|
"attempts": attempt_records,
|
|
746
1032
|
}
|
|
747
1033
|
|
|
748
|
-
for event in
|
|
1034
|
+
for event in projected_events:
|
|
749
1035
|
if event.get("type") != "issue":
|
|
750
1036
|
continue
|
|
751
1037
|
issues.append(
|
|
752
1038
|
{
|
|
753
1039
|
"id": event.get("id"),
|
|
1040
|
+
"issue_id": _issue_identity(event),
|
|
754
1041
|
"timestamp": event.get("timestamp"),
|
|
755
1042
|
"phase": event.get("phase"),
|
|
756
1043
|
"code": event.get("code"),
|
|
@@ -765,9 +1052,42 @@ def build_summary(change_dir: Path, events: list[dict[str, Any]]) -> dict[str, A
|
|
|
765
1052
|
"event_count": len(events),
|
|
766
1053
|
"phases": phases,
|
|
767
1054
|
"issues": issues,
|
|
1055
|
+
"current_issues": [
|
|
1056
|
+
{
|
|
1057
|
+
"id": event.get("id"),
|
|
1058
|
+
"issue_id": _issue_identity(event),
|
|
1059
|
+
"timestamp": event.get("timestamp"),
|
|
1060
|
+
"phase": event.get("phase"),
|
|
1061
|
+
"code": event.get("code"),
|
|
1062
|
+
"severity": event.get("severity"),
|
|
1063
|
+
"message": event.get("message"),
|
|
1064
|
+
}
|
|
1065
|
+
for event in current_issues(events)
|
|
1066
|
+
],
|
|
768
1067
|
}
|
|
769
1068
|
|
|
770
1069
|
|
|
1070
|
+
def phase_end_already_recorded(
|
|
1071
|
+
events: list[dict[str, Any]], candidate: dict[str, Any]
|
|
1072
|
+
) -> bool:
|
|
1073
|
+
"""Return whether the candidate attempt already has a terminal event."""
|
|
1074
|
+
phase_events = [
|
|
1075
|
+
event for event in events if event.get("phase") == candidate.get("phase")
|
|
1076
|
+
]
|
|
1077
|
+
candidate_attempt = candidate.get("attempt")
|
|
1078
|
+
if isinstance(candidate_attempt, int):
|
|
1079
|
+
return any(
|
|
1080
|
+
event.get("type") == "phase.end"
|
|
1081
|
+
and event.get("attempt") == candidate_attempt
|
|
1082
|
+
for event in phase_events
|
|
1083
|
+
)
|
|
1084
|
+
attempts = split_phase_attempts(phase_events)
|
|
1085
|
+
if not attempts:
|
|
1086
|
+
return False
|
|
1087
|
+
latest_events = attempts[-1].get("events") or []
|
|
1088
|
+
return any(event.get("type") == "phase.end" for event in latest_events)
|
|
1089
|
+
|
|
1090
|
+
|
|
771
1091
|
def cmd_append(args: argparse.Namespace) -> int:
|
|
772
1092
|
as_json = bool(args.json)
|
|
773
1093
|
if args.type not in EVENT_TYPES:
|
|
@@ -775,6 +1095,13 @@ def cmd_append(args: argparse.Namespace) -> int:
|
|
|
775
1095
|
f"unsupported type: {args.type}; expected one of {sorted(EVENT_TYPES)}",
|
|
776
1096
|
as_json=as_json,
|
|
777
1097
|
)
|
|
1098
|
+
legacy_lenient = bool(getattr(args, "legacy_lenient", False))
|
|
1099
|
+
validation = validate_append_event(args)
|
|
1100
|
+
if validation:
|
|
1101
|
+
error_code, message = validation
|
|
1102
|
+
legacy_compatible = legacy_lenient and args.type in {"issue", "verification"}
|
|
1103
|
+
if not legacy_compatible:
|
|
1104
|
+
return emit_error(message, as_json=as_json, error_code=error_code)
|
|
778
1105
|
change_dir = resolve_change_dir(args.change_dir)
|
|
779
1106
|
archived = archived_change_dir(change_dir)
|
|
780
1107
|
if archived is not None:
|
|
@@ -790,24 +1117,55 @@ def cmd_append(args: argparse.Namespace) -> int:
|
|
|
790
1117
|
# new_event_id 用完整 uuid,无需扫描去重。锁覆盖 atomic_append_line 的
|
|
791
1118
|
# open/write/flush/fsync 全过程。
|
|
792
1119
|
event = build_event(args, [])
|
|
793
|
-
#
|
|
794
|
-
|
|
1120
|
+
# Explicit compatibility mode preserves old append behavior and marks the
|
|
1121
|
+
# resulting event so strict consumers can distinguish it from valid v3.
|
|
1122
|
+
if validation:
|
|
1123
|
+
event["schemaValidation"] = "legacy"
|
|
1124
|
+
if legacy_lenient and args.type == "issue" and not args.severity:
|
|
795
1125
|
event["severity"] = "info"
|
|
796
1126
|
print(
|
|
797
1127
|
"warning: issue without --severity, defaulted to info",
|
|
798
1128
|
file=sys.stderr,
|
|
799
1129
|
)
|
|
800
|
-
if args.type == "verification" and (not args.name or not args.status):
|
|
1130
|
+
if legacy_lenient and args.type == "verification" and (not args.name or not args.status):
|
|
801
1131
|
print(
|
|
802
1132
|
"warning: verification missing --name or --status",
|
|
803
1133
|
file=sys.stderr,
|
|
804
1134
|
)
|
|
805
1135
|
line = json.dumps(event, ensure_ascii=False, separators=(",", ":"))
|
|
1136
|
+
phase_closed = False
|
|
1137
|
+
projection_error: str | None = None
|
|
806
1138
|
try:
|
|
807
1139
|
with event_file_lock(lock_path):
|
|
808
|
-
|
|
809
|
-
|
|
1140
|
+
existing_events = (
|
|
1141
|
+
load_events(path)
|
|
1142
|
+
if args.type in {"phase.end", "correction"}
|
|
1143
|
+
else []
|
|
1144
|
+
)
|
|
1145
|
+
if args.type == "phase.end":
|
|
1146
|
+
phase_closed = phase_end_already_recorded(existing_events, event)
|
|
1147
|
+
elif args.type == "correction":
|
|
1148
|
+
try:
|
|
1149
|
+
apply_event_corrections([*existing_events, event])
|
|
1150
|
+
except ValueError as exc:
|
|
1151
|
+
projection_error = str(exc)
|
|
1152
|
+
if not phase_closed and projection_error is None:
|
|
1153
|
+
atomic_append_line(path, line)
|
|
1154
|
+
except (OSError, TimeoutError, ValueError) as exc:
|
|
810
1155
|
return emit_error(f"append failed: {exc}", as_json=as_json)
|
|
1156
|
+
if phase_closed:
|
|
1157
|
+
return emit_error(
|
|
1158
|
+
"PHASE_ALREADY_CLOSED: refusing a second phase.end for the same attempt",
|
|
1159
|
+
as_json=as_json,
|
|
1160
|
+
error_code="PHASE_ALREADY_CLOSED",
|
|
1161
|
+
)
|
|
1162
|
+
if projection_error is not None:
|
|
1163
|
+
error_code = projection_error.split(":", 1)[0]
|
|
1164
|
+
return emit_error(
|
|
1165
|
+
projection_error,
|
|
1166
|
+
as_json=as_json,
|
|
1167
|
+
error_code=error_code,
|
|
1168
|
+
)
|
|
811
1169
|
|
|
812
1170
|
# §6.1: phase.end append -> 追加成功后执行一次 render(从完整 events 重建 log)。
|
|
813
1171
|
# 普通 command/issue 等 append 不渲染(O(1));显式 `render` 子命令随时重建。
|
|
@@ -911,6 +1269,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
911
1269
|
p_append.add_argument("--message", default=None)
|
|
912
1270
|
p_append.add_argument("--decision", default=None)
|
|
913
1271
|
p_append.add_argument("--reason", default=None)
|
|
1272
|
+
p_append.add_argument("--issue-id", default=None)
|
|
1273
|
+
p_append.add_argument("--scope", default=None)
|
|
1274
|
+
p_append.add_argument("--target-event-id", default=None)
|
|
1275
|
+
p_append.add_argument("--target-field", default=None)
|
|
1276
|
+
p_append.add_argument("--old-value-hash", default=None)
|
|
1277
|
+
p_append.add_argument("--new-value-json", default=None)
|
|
914
1278
|
p_append.add_argument("--run-id", default=None)
|
|
915
1279
|
p_append.add_argument("--attempt", type=int, default=None)
|
|
916
1280
|
p_append.add_argument("--executor-tool", default=None)
|
|
@@ -918,6 +1282,18 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
918
1282
|
p_append.add_argument("--executor-model", default=None)
|
|
919
1283
|
p_append.add_argument("--handoff-from-tool", default=None)
|
|
920
1284
|
p_append.add_argument("--handoff-reason", default=None)
|
|
1285
|
+
p_append.add_argument("--trace-id", default=None)
|
|
1286
|
+
p_append.add_argument("--span-id", default=None)
|
|
1287
|
+
p_append.add_argument("--parent-span-id", default=None)
|
|
1288
|
+
p_append.add_argument("--runner-ms", type=int, default=None)
|
|
1289
|
+
p_append.add_argument("--orchestration-active-ms", type=int, default=None)
|
|
1290
|
+
p_append.add_argument("--wall-clock-ms", type=int, default=None)
|
|
1291
|
+
p_append.add_argument("--user-wait-ms", type=int, default=None)
|
|
1292
|
+
p_append.add_argument(
|
|
1293
|
+
"--legacy-lenient",
|
|
1294
|
+
action="store_true",
|
|
1295
|
+
help="accept legacy incomplete/type-mismatched events and mark them explicitly",
|
|
1296
|
+
)
|
|
921
1297
|
p_append.set_defaults(func=cmd_append)
|
|
922
1298
|
|
|
923
1299
|
p_render = sub.add_parser(
|