@walwal-harness/cli 6.1.5 → 6.1.6
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/CHANGELOG.md +12 -0
- package/assets/launchd/com.walwal.harness-wake.plist.template +1 -1
- package/assets/templates/company-pipeline-manifest.json +71 -0
- package/assets/templates/config.json +4 -0
- package/bin/init.js +1 -0
- package/conventions/shared.md +18 -0
- package/package.json +1 -1
- package/scripts/conductor-tick.sh +97 -4
- package/scripts/harness-hourly-review.sh +284 -5
- package/scripts/harness-parity-audit.sh +101 -0
- package/scripts/harness-queue-manager.sh +1 -1
- package/scripts/harness-service-ops-monitor.sh +48 -3
- package/scripts/harness-wake-install.sh +1 -0
- package/scripts/harness-wake.sh +100 -8
- package/scripts/harness-worker-dispatch.sh +52 -21
- package/scripts/lib/harness-agent-resolver.sh +127 -0
|
@@ -34,6 +34,7 @@ fi
|
|
|
34
34
|
|
|
35
35
|
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
36
36
|
id="M-$(date -u +%Y%m%dT%H%M%SZ)-hourly"
|
|
37
|
+
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
37
38
|
meeting_dir="$PROJECT_ROOT/.harness/actions/meetings/$id"
|
|
38
39
|
meeting_rel=".harness/actions/meetings/$id/meeting-$id.md"
|
|
39
40
|
meeting_path="$PROJECT_ROOT/$meeting_rel"
|
|
@@ -52,6 +53,7 @@ current_agent="$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null || ech
|
|
|
52
53
|
next_agent="$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null || echo none)"
|
|
53
54
|
agent_status="$(jq -r '.agent_status // "unknown"' "$PROGRESS" 2>/dev/null || echo unknown)"
|
|
54
55
|
conductor_state="$(jq -r '.conductor.state // "idle"' "$PROGRESS" 2>/dev/null || echo idle)"
|
|
56
|
+
conductor_action="$(jq -r '.conductor.current_action // "none"' "$PROGRESS" 2>/dev/null || echo none)"
|
|
55
57
|
tick_count="$(jq -r '.conductor.tick_count // 0' "$PROGRESS" 2>/dev/null || echo 0)"
|
|
56
58
|
health_count="$(jq -r '(.service_ops.health // []) | length' "$PROGRESS" 2>/dev/null || echo 0)"
|
|
57
59
|
alert_count="$(jq -r '[.service_ops.health[]? | select(.status == "down" or .status == "degraded")] | length' "$PROGRESS" 2>/dev/null || echo 0)"
|
|
@@ -66,11 +68,63 @@ last_meeting="$(jq -r '.meetings.last_review_path // .meetings.current_record_pa
|
|
|
66
68
|
cqo_audit="$(jq -r '.cqo.audit_path // empty' "$PROGRESS" 2>/dev/null || true)"
|
|
67
69
|
cto_review="$(jq -r '.cto.review_path // empty' "$PROGRESS" 2>/dev/null || true)"
|
|
68
70
|
open_incidents="$(jq -r '(.service_ops.incident.open // []) | length' "$PROGRESS" 2>/dev/null || echo 0)"
|
|
71
|
+
incident_repeat_count="$(jq -r '.service_ops.incident.repeat_count // 0' "$PROGRESS" 2>/dev/null || echo 0)"
|
|
72
|
+
incident_recovery_required="$(jq -r '.service_ops.incident.recovery_required // false' "$PROGRESS" 2>/dev/null || echo false)"
|
|
73
|
+
prev_required_json="$(jq -c '.meetings.decision.required_execution // .meetings.required_execution // null' "$PROGRESS" 2>/dev/null || echo null)"
|
|
74
|
+
prev_required_action="$(jq -r '(.meetings.decision.required_execution // .meetings.required_execution // {}).action // "none"' "$PROGRESS" 2>/dev/null || echo none)"
|
|
75
|
+
prev_required_owner="$(jq -r '(.meetings.decision.required_execution // .meetings.required_execution // {}).owner // "none"' "$PROGRESS" 2>/dev/null || echo none)"
|
|
76
|
+
prev_required_deliverable="$(jq -r '(.meetings.decision.required_execution // .meetings.required_execution // {}).deliverable_path // ""' "$PROGRESS" 2>/dev/null || true)"
|
|
77
|
+
prev_execution_result="none"
|
|
78
|
+
prev_execution_detail="no prior required_execution"
|
|
79
|
+
if [ "$prev_required_json" != "null" ]; then
|
|
80
|
+
if [ -n "$prev_required_deliverable" ] && [ -f "$PROJECT_ROOT/$prev_required_deliverable" ]; then
|
|
81
|
+
prev_execution_result="executed_unverified"
|
|
82
|
+
prev_execution_detail="deliverable exists: $prev_required_deliverable"
|
|
83
|
+
if [ "$prev_required_action" = "runtime-recovery-runbook" ] && [ "${open_incidents:-0}" -eq 0 ] && [ "$alert_count" -eq 0 ]; then
|
|
84
|
+
prev_execution_result="executed_and_verified"
|
|
85
|
+
prev_execution_detail="deliverable exists and Service-Ops has no open incident"
|
|
86
|
+
fi
|
|
87
|
+
elif [ "$prev_required_action" = "runtime-recovery-runbook" ] && [ "$(jq -r '.cto.step_b_status // .cto.step_a_status // ""' "$PROGRESS" 2>/dev/null || true)" != "" ]; then
|
|
88
|
+
prev_execution_result="attempted_blocked"
|
|
89
|
+
prev_execution_detail="CTO recovery state advanced but required deliverable missing: ${prev_required_deliverable:-unset}"
|
|
90
|
+
else
|
|
91
|
+
prev_execution_result="paperwork_only_failure"
|
|
92
|
+
prev_execution_detail="required deliverable missing: ${prev_required_deliverable:-unset}"
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
69
95
|
owner_escalation="false"
|
|
96
|
+
strategy_total_signals=0
|
|
97
|
+
strategy_previous_signals="$(jq -r '.goals.strategy_cadence.total_signals // 0' "$PROGRESS" 2>/dev/null || echo 0)"
|
|
98
|
+
strategy_delta=0
|
|
99
|
+
strategy_status="not_applicable"
|
|
100
|
+
strategy_target_per_hour=0
|
|
101
|
+
strategy_target_per_day=0
|
|
102
|
+
strategy_last_signal="none"
|
|
103
|
+
strategy_required=false
|
|
104
|
+
|
|
105
|
+
if grep -Eiq 'strategy|algo|backtest|신규.?전략|시간당.?1|일.?6|daily.?6|hourly.?1' "$log_path" "$PROJECT_ROOT/.harness/actions/goals.md" 2>/dev/null; then
|
|
106
|
+
strategy_required=true
|
|
107
|
+
strategy_target_per_hour=1
|
|
108
|
+
strategy_target_per_day=6
|
|
109
|
+
strategy_total_signals="$(grep -Eic '(^|[|[:space:]])(new-strategy|strategy-generated|algos?_candidate|backtest-jobs?|backtest_enqueue|신규.?전략|전략.?생성)([|[:space:]]|$)' "$log_path" 2>/dev/null || echo 0)"
|
|
110
|
+
strategy_last_signal="$(grep -Ei '(^|[|[:space:]])(new-strategy|strategy-generated|algos?_candidate|backtest-jobs?|backtest_enqueue|신규.?전략|전략.?생성)([|[:space:]]|$)' "$log_path" 2>/dev/null | tail -1 || true)"
|
|
111
|
+
[ -n "$strategy_last_signal" ] || strategy_last_signal="none"
|
|
112
|
+
if [ "$strategy_total_signals" -lt "$strategy_previous_signals" ]; then
|
|
113
|
+
strategy_previous_signals="$strategy_total_signals"
|
|
114
|
+
fi
|
|
115
|
+
strategy_delta=$((strategy_total_signals - strategy_previous_signals))
|
|
116
|
+
if [ "$strategy_delta" -ge "$strategy_target_per_hour" ]; then
|
|
117
|
+
strategy_status="on_track"
|
|
118
|
+
else
|
|
119
|
+
strategy_status="behind_goal"
|
|
120
|
+
fi
|
|
121
|
+
fi
|
|
70
122
|
|
|
71
123
|
verdict="working"
|
|
72
124
|
if [ "$alert_count" -gt 0 ]; then
|
|
73
125
|
verdict="incident"
|
|
126
|
+
elif [ "$strategy_required" = "true" ] && [ "$strategy_status" = "behind_goal" ]; then
|
|
127
|
+
verdict="goal_drift"
|
|
74
128
|
elif [ "$conductor_state" = "escalated" ]; then
|
|
75
129
|
verdict="owner_needed"
|
|
76
130
|
elif [ "$conductor_state" = "waiting_owner" ]; then
|
|
@@ -83,6 +137,25 @@ meeting_type="standup"
|
|
|
83
137
|
decision_owner="planner"
|
|
84
138
|
decision_action="execution-plan"
|
|
85
139
|
decision_rationale="정기 회의에서 active GOAL 대비 현재 작업과 운영 신호를 재정렬하고 다음 work package를 명확히 한다."
|
|
140
|
+
progress_meaningful_json='[]'
|
|
141
|
+
required_execution_json='null'
|
|
142
|
+
handoff_requires_execution=false
|
|
143
|
+
handoff_feature="$(printf "%s" "$conductor_action" | awk -F: '{print $NF}')"
|
|
144
|
+
case "$handoff_feature" in
|
|
145
|
+
""|none|null|pass-through|monitor|completed|running|ready|execution-plan|continue-current-handoff|goal-alignment)
|
|
146
|
+
handoff_feature="$stamp"
|
|
147
|
+
;;
|
|
148
|
+
esac
|
|
149
|
+
safe_handoff_feature="$(printf "%s" "$handoff_feature" | tr -cs 'A-Za-z0-9._-' '-' | sed 's/^-//; s/-$//')"
|
|
150
|
+
[ -n "$safe_handoff_feature" ] || safe_handoff_feature="$stamp"
|
|
151
|
+
|
|
152
|
+
case "$next_agent" in
|
|
153
|
+
planner|cto|cqo|generator-*|evaluator-*|documentationer|coo-developer)
|
|
154
|
+
if [ "$next_agent" != "$current_agent" ] || [[ "$conductor_action" == *handoff* ]] || [[ "$conductor_action" == advance:* ]] || [[ "$conductor_action" == spawn:* ]] || [[ "$conductor_action" == dispatch:* ]]; then
|
|
155
|
+
handoff_requires_execution=true
|
|
156
|
+
fi
|
|
157
|
+
;;
|
|
158
|
+
esac
|
|
86
159
|
case "$next_agent" in
|
|
87
160
|
planner|cto|cqo|service-ops|dispatcher)
|
|
88
161
|
decision_owner="$next_agent"
|
|
@@ -93,13 +166,56 @@ esac
|
|
|
93
166
|
if [ "$verdict" = "incident" ]; then
|
|
94
167
|
meeting_type="incident-war-room"
|
|
95
168
|
decision_owner="cto"
|
|
96
|
-
|
|
97
|
-
|
|
169
|
+
if [ "$incident_recovery_required" = "true" ] || [ "${incident_repeat_count:-0}" -ge 2 ]; then
|
|
170
|
+
decision_action="runtime-recovery-runbook"
|
|
171
|
+
decision_rationale="동일 운영 장애가 ${incident_repeat_count}회 연속 감지됐다. 반복 회의가 아니라 CTO가 복구 runbook을 실행하고 CQO가 evidence를 검증한다."
|
|
172
|
+
progress_meaningful_json='["runtime-recovery-runbook required"]'
|
|
173
|
+
required_execution_json="$(jq -n --arg id "RE-$stamp-runtime-recovery" --arg source "$meeting_rel" '{
|
|
174
|
+
id:$id,
|
|
175
|
+
owner:"cto",
|
|
176
|
+
action:"runtime-recovery-runbook",
|
|
177
|
+
deliverable_path:".harness/actions/cto-runbook-runtime-recovery-current.md",
|
|
178
|
+
verifier:"cqo+service-ops",
|
|
179
|
+
due:"next_tick",
|
|
180
|
+
source_path:$source,
|
|
181
|
+
success_condition:"Service-Ops verifies every configured production service as ok; partial recovery remains incident."
|
|
182
|
+
}')"
|
|
183
|
+
else
|
|
184
|
+
decision_action="runtime-recovery"
|
|
185
|
+
decision_rationale="Service-Ops가 down/degraded 신호를 발견했으므로 CTO가 복구 경로를 정하고 CQO가 복구 evidence를 검증한다."
|
|
186
|
+
progress_meaningful_json='["runtime-recovery decision"]'
|
|
187
|
+
required_execution_json="$(jq -n --arg id "RE-$stamp-runtime-recovery" --arg source "$meeting_rel" '{
|
|
188
|
+
id:$id,
|
|
189
|
+
owner:"cto",
|
|
190
|
+
action:"runtime-recovery",
|
|
191
|
+
deliverable_path:".harness/actions/cto-action-runtime-recovery-current.md",
|
|
192
|
+
verifier:"service-ops",
|
|
193
|
+
due:"next_tick",
|
|
194
|
+
source_path:$source,
|
|
195
|
+
success_condition:"Service-Ops health improves or records a concrete blocker."
|
|
196
|
+
}')"
|
|
197
|
+
fi
|
|
98
198
|
elif [ "$verdict" = "idle" ] || [ "$verdict" = "autonomous_review" ]; then
|
|
99
199
|
meeting_type="followup-review"
|
|
100
200
|
decision_owner="planner"
|
|
101
201
|
decision_action="goal-alignment"
|
|
102
202
|
decision_rationale="Owner 대기 없이 CEO/COO/CTO/CQO/Service-Ops가 GOAL 대비 다음 work package를 재정렬한다."
|
|
203
|
+
elif [ "$verdict" = "goal_drift" ]; then
|
|
204
|
+
meeting_type="spec-review"
|
|
205
|
+
decision_owner="planner"
|
|
206
|
+
decision_action="strategy-cadence-recovery"
|
|
207
|
+
decision_rationale="전략 생성 cadence가 목표보다 뒤처졌다. COO가 다음 시간의 전략 후보 생성/백테스트 work package를 즉시 재배정한다."
|
|
208
|
+
progress_meaningful_json='["strategy cadence recovery decision"]'
|
|
209
|
+
required_execution_json="$(jq -n --arg id "RE-$stamp-strategy-cadence" --arg source "$meeting_rel" --arg deliverable ".harness/actions/strategy-work-package-${stamp}.md" '{
|
|
210
|
+
id:$id,
|
|
211
|
+
owner:"planner",
|
|
212
|
+
action:"create-strategy-work-package",
|
|
213
|
+
deliverable_path:$deliverable,
|
|
214
|
+
verifier:"cqo",
|
|
215
|
+
due:"next_tick",
|
|
216
|
+
source_path:$source,
|
|
217
|
+
success_condition:"A strategy work package exists with candidate idea, data source, backtest command, and CQO acceptance criteria."
|
|
218
|
+
}')"
|
|
103
219
|
elif [ "$verdict" = "owner_needed" ]; then
|
|
104
220
|
meeting_type="all-hands"
|
|
105
221
|
decision_owner="dispatcher"
|
|
@@ -108,6 +224,82 @@ elif [ "$verdict" = "owner_needed" ]; then
|
|
|
108
224
|
owner_escalation="true"
|
|
109
225
|
fi
|
|
110
226
|
|
|
227
|
+
if [ "$verdict" = "working" ] && [ "$handoff_requires_execution" = "true" ]; then
|
|
228
|
+
decision_owner="$next_agent"
|
|
229
|
+
case "$next_agent" in
|
|
230
|
+
planner)
|
|
231
|
+
decision_action="execution-plan"
|
|
232
|
+
deliverable_path=".harness/actions/planner-execution-plan-${safe_handoff_feature}.md"
|
|
233
|
+
verifier="cto+cqo"
|
|
234
|
+
success_condition="Planner creates a concrete execution plan that reconciles the current handoff with GOAL, queue priority, non-goals, owner, and verifier."
|
|
235
|
+
;;
|
|
236
|
+
cto)
|
|
237
|
+
decision_action="technical-handoff"
|
|
238
|
+
deliverable_path=".harness/actions/cto-action-${safe_handoff_feature}.md"
|
|
239
|
+
verifier="cqo"
|
|
240
|
+
success_condition="CTO records implementation scope, affected files, validation commands, blocker status, and the next execution owner."
|
|
241
|
+
;;
|
|
242
|
+
cqo)
|
|
243
|
+
decision_action="quality-verification"
|
|
244
|
+
deliverable_path=".harness/actions/cqo-audit-${safe_handoff_feature}.md"
|
|
245
|
+
verifier="meeting-manager"
|
|
246
|
+
success_condition="CQO records PASS/FAIL with evidence, regressions, and the next routing decision."
|
|
247
|
+
;;
|
|
248
|
+
generator-*)
|
|
249
|
+
decision_action="implement-${safe_handoff_feature}"
|
|
250
|
+
deliverable_path=".harness/actions/gen-report-${safe_handoff_feature}.md"
|
|
251
|
+
verifier="cqo+evaluator-code-quality"
|
|
252
|
+
success_condition="Generator produces the named implementation report, changed-file list, validation output, and handoff to CQO/evaluator."
|
|
253
|
+
;;
|
|
254
|
+
evaluator-*)
|
|
255
|
+
decision_action="verify-${safe_handoff_feature}"
|
|
256
|
+
deliverable_path=".harness/actions/evaluation-${safe_handoff_feature}.md"
|
|
257
|
+
verifier="cqo"
|
|
258
|
+
success_condition="Evaluator writes an evidence-backed PASS/FAIL report and does not rubber-stamp missing evidence."
|
|
259
|
+
;;
|
|
260
|
+
documentationer)
|
|
261
|
+
decision_action="document-${safe_handoff_feature}"
|
|
262
|
+
deliverable_path=".harness/actions/documentation-${safe_handoff_feature}.md"
|
|
263
|
+
verifier="planner"
|
|
264
|
+
success_condition="Documentationer writes the requested brief/report with source paths and decision impact."
|
|
265
|
+
;;
|
|
266
|
+
coo-developer)
|
|
267
|
+
decision_action="spike-${safe_handoff_feature}"
|
|
268
|
+
deliverable_path=".harness/actions/hypothesis/${safe_handoff_feature}/spike/report.md"
|
|
269
|
+
verifier="planner+cqo"
|
|
270
|
+
success_condition="COO developer records spike result, repro/evidence, and verdict input for Planner."
|
|
271
|
+
;;
|
|
272
|
+
*)
|
|
273
|
+
decision_action="execute-${safe_handoff_feature}"
|
|
274
|
+
deliverable_path=".harness/actions/execution-${safe_handoff_feature}.md"
|
|
275
|
+
verifier="meeting-manager"
|
|
276
|
+
success_condition="The owner writes a concrete deliverable and next-step evidence before the following hourly review."
|
|
277
|
+
;;
|
|
278
|
+
esac
|
|
279
|
+
decision_rationale="working 상태라도 현재 handoff는 산출물 없는 '계속 진행'으로 통과할 수 없다. 다음 tick까지 owner가 deliverable을 남기고 verifier가 검증한다."
|
|
280
|
+
progress_meaningful_json="$(jq -n --arg item "execution contract required: $next_agent/$decision_action" '[$item]')"
|
|
281
|
+
required_execution_json="$(jq -n \
|
|
282
|
+
--arg id "RE-$stamp-handoff" \
|
|
283
|
+
--arg owner "$decision_owner" \
|
|
284
|
+
--arg action "$decision_action" \
|
|
285
|
+
--arg deliverable "$deliverable_path" \
|
|
286
|
+
--arg verifier "$verifier" \
|
|
287
|
+
--arg source "$meeting_rel" \
|
|
288
|
+
--arg success "$success_condition" \
|
|
289
|
+
--arg conductor_action "$conductor_action" \
|
|
290
|
+
'{
|
|
291
|
+
id:$id,
|
|
292
|
+
owner:$owner,
|
|
293
|
+
action:$action,
|
|
294
|
+
deliverable_path:$deliverable,
|
|
295
|
+
verifier:$verifier,
|
|
296
|
+
due:"next_tick",
|
|
297
|
+
source_path:$source,
|
|
298
|
+
source_conductor_action:$conductor_action,
|
|
299
|
+
success_condition:$success
|
|
300
|
+
}')"
|
|
301
|
+
fi
|
|
302
|
+
|
|
111
303
|
decision_json="$(jq -n \
|
|
112
304
|
--arg owner "$decision_owner" \
|
|
113
305
|
--arg action_type "$decision_action" \
|
|
@@ -117,6 +309,8 @@ decision_json="$(jq -n \
|
|
|
117
309
|
--arg ops_report "$ops_report" \
|
|
118
310
|
--arg cqo_audit "$cqo_audit" \
|
|
119
311
|
--arg cto_review "$cto_review" \
|
|
312
|
+
--argjson progress_meaningful "$progress_meaningful_json" \
|
|
313
|
+
--argjson required_execution "$required_execution_json" \
|
|
120
314
|
--argjson owner_escalation "$owner_escalation" '
|
|
121
315
|
{
|
|
122
316
|
owner: $owner,
|
|
@@ -132,6 +326,11 @@ decision_json="$(jq -n \
|
|
|
132
326
|
drift_classification: $drift,
|
|
133
327
|
source_path: $source,
|
|
134
328
|
owner_escalation: $owner_escalation,
|
|
329
|
+
progress_classification: {
|
|
330
|
+
meaningful_actions: $progress_meaningful,
|
|
331
|
+
paperwork_only: ["hourly-review", "service-ops-monitor"]
|
|
332
|
+
},
|
|
333
|
+
required_execution: $required_execution,
|
|
135
334
|
tracks: [],
|
|
136
335
|
rendezvous: null
|
|
137
336
|
}')"
|
|
@@ -165,6 +364,9 @@ meeting_json="$(jq -n \
|
|
|
165
364
|
echo "- drift_classification: $drift"
|
|
166
365
|
echo "- previous_decision: $last_decision"
|
|
167
366
|
echo "- previous_meeting: $last_meeting"
|
|
367
|
+
echo "- previous_required_execution: $prev_required_owner/$prev_required_action"
|
|
368
|
+
echo "- previous_execution_result: $prev_execution_result"
|
|
369
|
+
echo "- previous_execution_detail: $prev_execution_detail"
|
|
168
370
|
if [ -n "$ops_report" ]; then
|
|
169
371
|
echo "- service_ops_report: $ops_report"
|
|
170
372
|
fi
|
|
@@ -175,6 +377,21 @@ meeting_json="$(jq -n \
|
|
|
175
377
|
echo "- cqo_audit: $cqo_audit"
|
|
176
378
|
fi
|
|
177
379
|
echo ""
|
|
380
|
+
echo "## Progress Classification"
|
|
381
|
+
echo ""
|
|
382
|
+
echo "- previous_required_execution_result: $prev_execution_result"
|
|
383
|
+
echo "- previous_required_execution_detail: $prev_execution_detail"
|
|
384
|
+
if [ "$verdict" = "incident" ] && { [ "$incident_recovery_required" = "true" ] || [ "${incident_repeat_count:-0}" -ge 2 ]; }; then
|
|
385
|
+
echo "- meaningful_actions: runtime-recovery-runbook required"
|
|
386
|
+
echo "- paperwork_only: hourly meeting minutes, Service-Ops monitor report"
|
|
387
|
+
elif [ "$verdict" = "goal_drift" ]; then
|
|
388
|
+
echo "- meaningful_actions: strategy cadence recovery decision"
|
|
389
|
+
echo "- paperwork_only: hourly meeting minutes, Service-Ops monitor report"
|
|
390
|
+
else
|
|
391
|
+
echo "- meaningful_actions: none detected in this deterministic batch until conductor/agent tick records execution"
|
|
392
|
+
echo "- paperwork_only: hourly meeting minutes, Service-Ops monitor report"
|
|
393
|
+
fi
|
|
394
|
+
echo ""
|
|
178
395
|
echo "## Role Briefs"
|
|
179
396
|
echo ""
|
|
180
397
|
echo "### Dispatcher/CEO"
|
|
@@ -214,6 +431,12 @@ meeting_json="$(jq -n \
|
|
|
214
431
|
echo "- warnings: $warn_count"
|
|
215
432
|
echo "- alerts: $alert_count"
|
|
216
433
|
echo "- open_incidents: $open_incidents"
|
|
434
|
+
echo "- incident_repeat_count: $incident_repeat_count"
|
|
435
|
+
echo "- incident_recovery_required: $incident_recovery_required"
|
|
436
|
+
if [ "$strategy_required" = "true" ]; then
|
|
437
|
+
echo "- strategy_cadence: $strategy_status (delta=$strategy_delta, total_signals=$strategy_total_signals, target_per_hour=$strategy_target_per_hour, target_per_day=$strategy_target_per_day)"
|
|
438
|
+
echo "- last_strategy_signal: $strategy_last_signal"
|
|
439
|
+
fi
|
|
217
440
|
echo "- 판단: 운영 신호를 회의에 제출하고, 장애 시 incident-war-room을 요구한다."
|
|
218
441
|
echo ""
|
|
219
442
|
if [ "$health_count" -gt 0 ]; then
|
|
@@ -234,11 +457,22 @@ meeting_json="$(jq -n \
|
|
|
234
457
|
case "$verdict" in
|
|
235
458
|
incident)
|
|
236
459
|
echo "- Service-Ops: down/degraded 서비스가 있으므로 feature work보다 장애 triage가 우선이다."
|
|
237
|
-
|
|
460
|
+
if [ "$incident_recovery_required" = "true" ] || [ "${incident_repeat_count:-0}" -ge 2 ]; then
|
|
461
|
+
echo "- CTO: 같은 장애가 반복됐으므로 추가 회의가 아니라 runtime-recovery-runbook 실행으로 전환한다."
|
|
462
|
+
else
|
|
463
|
+
echo "- CTO: 복구 절차, 재시작, 설정 누락, 포트 충돌 중 원인을 좁히고 runtime-recovery work item을 받는다."
|
|
464
|
+
fi
|
|
238
465
|
echo "- CQO: 복구 완료 주장은 health check와 재현 가능한 evidence 없이는 PASS로 보지 않는다."
|
|
239
466
|
echo "- COO: 장애 복구가 GOAL 진행을 막는 범위를 queue에 반영한다."
|
|
240
467
|
echo "- CEO: Owner에게 묻지 않고 내부 복구 루프를 진행한다. 단 GOAL 변경이 필요하면 별도 escalation한다."
|
|
241
468
|
;;
|
|
469
|
+
goal_drift)
|
|
470
|
+
echo "- CEO: Owner가 없어도 최초 GOAL의 KPI를 기준으로 회사 루프를 평가한다."
|
|
471
|
+
echo "- COO: 전략 생성/백테스트 cadence가 목표보다 낮으므로 다음 work package를 전략 후보 생성으로 고정한다."
|
|
472
|
+
echo "- CTO: 필요한 데이터/실행 경로가 막혔는지 점검한다."
|
|
473
|
+
echo "- CQO: 후보 전략은 backtest evidence 없이는 유의미 진척으로 인정하지 않는다."
|
|
474
|
+
echo "- Service-Ops: 운영 장애가 cadence 저하 원인인지 계속 감시한다."
|
|
475
|
+
;;
|
|
242
476
|
owner_needed)
|
|
243
477
|
echo "- CEO: conductor escalation의 원인이 내부 해결 가능한지 우선 판정한다."
|
|
244
478
|
echo "- COO/CTO/CQO/Service-Ops: 각자 해결 가능한 내부 action을 제시하고, 불가능할 때만 Owner escalation을 유지한다."
|
|
@@ -272,6 +506,11 @@ meeting_json="$(jq -n \
|
|
|
272
506
|
echo "- Service-Ops: 복구 후 monitor를 다시 돌려 last_check와 health evidence를 갱신한다."
|
|
273
507
|
echo "- CQO: 복구 evidence를 검증하고 regression risk를 기록한다."
|
|
274
508
|
;;
|
|
509
|
+
goal_drift)
|
|
510
|
+
echo "- Planner/COO: 다음 autonomous tick에서 전략 후보 생성/백테스트 work package를 발급한다."
|
|
511
|
+
echo "- CTO: 전략 실행 경로와 데이터 의존성 차단 여부를 점검한다."
|
|
512
|
+
echo "- CQO: backtest 결과가 없는 후보를 유의미 진척으로 집계하지 않는다."
|
|
513
|
+
;;
|
|
275
514
|
idle|autonomous_review)
|
|
276
515
|
echo "- Planner/COO: Owner 대기 없이 active GOAL 기준 다음 work package를 정의한다."
|
|
277
516
|
echo "- Conductor: meeting decision을 읽고 다음 부서를 자동 배정한다."
|
|
@@ -280,9 +519,34 @@ meeting_json="$(jq -n \
|
|
|
280
519
|
echo "- Conductor: 다음 autonomous tick에서 decision.owner/action_type 기준으로 handoff한다."
|
|
281
520
|
;;
|
|
282
521
|
esac
|
|
522
|
+
echo ""
|
|
523
|
+
echo "## Required Execution"
|
|
524
|
+
echo ""
|
|
525
|
+
if [ "$required_execution_json" = "null" ]; then
|
|
526
|
+
echo "- none"
|
|
527
|
+
else
|
|
528
|
+
jq -r '"- owner: \(.owner)\n- action: \(.action)\n- deliverable_path: \(.deliverable_path)\n- verifier: \(.verifier)\n- due: \(.due)\n- success_condition: \(.success_condition)"' <<<"$required_execution_json"
|
|
529
|
+
fi
|
|
283
530
|
} > "$meeting_path"
|
|
284
531
|
|
|
285
|
-
jq
|
|
532
|
+
jq \
|
|
533
|
+
--arg ts "$ts" \
|
|
534
|
+
--arg id "$id" \
|
|
535
|
+
--arg rel "$meeting_rel" \
|
|
536
|
+
--arg verdict "$verdict" \
|
|
537
|
+
--arg type "$meeting_type" \
|
|
538
|
+
--arg strategy_status "$strategy_status" \
|
|
539
|
+
--arg strategy_last_signal "$strategy_last_signal" \
|
|
540
|
+
--arg prev_execution_result "$prev_execution_result" \
|
|
541
|
+
--arg prev_execution_detail "$prev_execution_detail" \
|
|
542
|
+
--argjson strategy_required "$strategy_required" \
|
|
543
|
+
--argjson strategy_target_per_hour "$strategy_target_per_hour" \
|
|
544
|
+
--argjson strategy_target_per_day "$strategy_target_per_day" \
|
|
545
|
+
--argjson strategy_total_signals "$strategy_total_signals" \
|
|
546
|
+
--argjson strategy_previous_signals "$strategy_previous_signals" \
|
|
547
|
+
--argjson strategy_delta "$strategy_delta" \
|
|
548
|
+
--argjson incident_recovery_required "$incident_recovery_required" \
|
|
549
|
+
--argjson decision "$decision_json" '
|
|
286
550
|
.meetings.last_type = "hourly-review" |
|
|
287
551
|
.meetings.last_reason = $verdict |
|
|
288
552
|
.meetings.last_review_at = $ts |
|
|
@@ -291,7 +555,22 @@ jq --arg ts "$ts" --arg id "$id" --arg rel "$meeting_rel" --arg verdict "$verdic
|
|
|
291
555
|
.meetings.current_record_path = $rel |
|
|
292
556
|
.meetings.last_attendees = ["dispatcher","planner","cto","cqo","service-ops","meeting-manager"] |
|
|
293
557
|
.meetings.decision = $decision |
|
|
294
|
-
|
|
558
|
+
.meetings.previous_required_execution_result = $prev_execution_result |
|
|
559
|
+
.meetings.previous_required_execution_detail = $prev_execution_detail |
|
|
560
|
+
.meetings.required_execution = $decision.required_execution |
|
|
561
|
+
.goals.strategy_cadence = {
|
|
562
|
+
"required": $strategy_required,
|
|
563
|
+
"status": $strategy_status,
|
|
564
|
+
"target_per_hour": $strategy_target_per_hour,
|
|
565
|
+
"target_per_day": $strategy_target_per_day,
|
|
566
|
+
"total_signals": $strategy_total_signals,
|
|
567
|
+
"previous_total_signals": $strategy_previous_signals,
|
|
568
|
+
"delta_since_last_review": $strategy_delta,
|
|
569
|
+
"last_signal": $strategy_last_signal,
|
|
570
|
+
"last_check": $ts
|
|
571
|
+
} |
|
|
572
|
+
.service_ops.incident.recovery_required = $incident_recovery_required |
|
|
573
|
+
if $verdict == "incident" or $verdict == "autonomous_review" or $verdict == "idle" or $verdict == "goal_drift"
|
|
295
574
|
then .meetings.active = ((.meetings.active // []) + ["meeting-manager","dispatcher","planner","cto","cqo","service-ops"] | unique) |
|
|
296
575
|
.meetings.requested_type = (.meetings.requested_type // $type) |
|
|
297
576
|
.meetings.requested_reason = (.meetings.requested_reason // $verdict)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-parity-audit.sh — compare installed project company-loop surface to walwal-harness
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
HARNESS_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
8
|
+
MANIFEST="$HARNESS_ROOT/assets/templates/company-pipeline-manifest.json"
|
|
9
|
+
|
|
10
|
+
usage() {
|
|
11
|
+
echo "usage: bash scripts/harness-parity-audit.sh <project-root> [<project-root> ...]" >&2
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
[ "$#" -gt 0 ] || { usage; exit 2; }
|
|
15
|
+
[ -f "$MANIFEST" ] || { echo "MISSING manifest: $MANIFEST" >&2; exit 2; }
|
|
16
|
+
command -v jq >/dev/null 2>&1 || { echo "MISSING jq" >&2; exit 2; }
|
|
17
|
+
|
|
18
|
+
sha() {
|
|
19
|
+
LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 shasum -a 256 "$1" | awk '{print $1}'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
project_path_for() {
|
|
23
|
+
local rel="$1"
|
|
24
|
+
case "$rel" in
|
|
25
|
+
conventions/*) echo ".harness/$rel" ;;
|
|
26
|
+
gotchas/*) echo ".harness/$rel" ;;
|
|
27
|
+
skills/*) echo ".harness/$rel" ;;
|
|
28
|
+
*) echo "$rel" ;;
|
|
29
|
+
esac
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
config_core_filter() {
|
|
33
|
+
jq -S '
|
|
34
|
+
del(.project, .name, .description, .paths, .production, .telegram, .services, .owners, .runtime, .tech_stack, .integrations)
|
|
35
|
+
' "$1"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
status=0
|
|
39
|
+
for project in "$@"; do
|
|
40
|
+
project="$(cd "$project" && pwd)"
|
|
41
|
+
echo "== $project =="
|
|
42
|
+
|
|
43
|
+
if [ ! -d "$project/.harness" ]; then
|
|
44
|
+
echo "DRIFT missing .harness"
|
|
45
|
+
status=1
|
|
46
|
+
continue
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
while IFS= read -r rel; do
|
|
50
|
+
src="$HARNESS_ROOT/$rel"
|
|
51
|
+
dst="$project/$(project_path_for "$rel")"
|
|
52
|
+
if [ ! -f "$src" ]; then
|
|
53
|
+
echo "UPSTREAM_REQUIRED missing-source $rel"
|
|
54
|
+
status=1
|
|
55
|
+
elif [ ! -f "$dst" ]; then
|
|
56
|
+
echo "DRIFT missing $rel -> $(project_path_for "$rel")"
|
|
57
|
+
status=1
|
|
58
|
+
elif [ "$(sha "$src")" != "$(sha "$dst")" ]; then
|
|
59
|
+
echo "DRIFT hash $rel -> $(project_path_for "$rel")"
|
|
60
|
+
status=1
|
|
61
|
+
else
|
|
62
|
+
echo "PASS $rel"
|
|
63
|
+
fi
|
|
64
|
+
done < <(jq -r '.identical_files[]' "$MANIFEST")
|
|
65
|
+
|
|
66
|
+
while IFS=$'\t' read -r src_rel dst_rel mode; do
|
|
67
|
+
src="$HARNESS_ROOT/$src_rel"
|
|
68
|
+
dst="$project/$dst_rel"
|
|
69
|
+
if [ ! -f "$dst" ]; then
|
|
70
|
+
echo "DRIFT missing $dst_rel"
|
|
71
|
+
status=1
|
|
72
|
+
elif [ "$mode" = "identical" ] && [ "$(sha "$src")" != "$(sha "$dst")" ]; then
|
|
73
|
+
echo "DRIFT hash $src_rel -> $dst_rel"
|
|
74
|
+
status=1
|
|
75
|
+
else
|
|
76
|
+
echo "PASS $src_rel -> $dst_rel"
|
|
77
|
+
fi
|
|
78
|
+
done < <(jq -r '.template_mappings[] | [.source, .target, .mode] | @tsv' "$MANIFEST")
|
|
79
|
+
|
|
80
|
+
if [ -f "$project/.harness/config.json" ]; then
|
|
81
|
+
tmp_src="$(mktemp)"
|
|
82
|
+
tmp_dst="$(mktemp)"
|
|
83
|
+
config_core_filter "$HARNESS_ROOT/assets/templates/config.json" > "$tmp_src"
|
|
84
|
+
config_core_filter "$project/.harness/config.json" > "$tmp_dst"
|
|
85
|
+
if jq -e -s '.[0] == .[1]' "$tmp_src" "$tmp_dst" >/dev/null; then
|
|
86
|
+
echo "PASS config-core"
|
|
87
|
+
else
|
|
88
|
+
echo "DRIFT config-core"
|
|
89
|
+
diff -u "$tmp_src" "$tmp_dst" | sed 's/^/ /'
|
|
90
|
+
status=1
|
|
91
|
+
fi
|
|
92
|
+
rm -f "$tmp_src" "$tmp_dst"
|
|
93
|
+
else
|
|
94
|
+
echo "DRIFT missing .harness/config.json"
|
|
95
|
+
status=1
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
echo
|
|
99
|
+
done
|
|
100
|
+
|
|
101
|
+
exit "$status"
|
|
@@ -141,7 +141,7 @@ cmd_init() {
|
|
|
141
141
|
}) | from_entries
|
|
142
142
|
)
|
|
143
143
|
}
|
|
144
|
-
' "$FEATURES" > "$QUEUE"
|
|
144
|
+
' "$FEATURES" > "${QUEUE}.tmp.$$" && mv "${QUEUE}.tmp.$$" "$QUEUE"
|
|
145
145
|
|
|
146
146
|
local ready_count blocked_count passed_count
|
|
147
147
|
ready_count=$(jq '.queue.ready | length' "$QUEUE")
|
|
@@ -79,7 +79,15 @@ if [ "$live" != "true" ] || [ "${service_count:-0}" -eq 0 ]; then
|
|
|
79
79
|
.service_ops.monitor.last_check = $ts |
|
|
80
80
|
.service_ops.monitor.stream_active = false |
|
|
81
81
|
.service_ops.monitor.last_report = $report |
|
|
82
|
-
.service_ops.health = []
|
|
82
|
+
.service_ops.health = [] |
|
|
83
|
+
.service_ops.incident.open = [] |
|
|
84
|
+
.service_ops.incident.signature = "" |
|
|
85
|
+
.service_ops.incident.repeat_count = 0 |
|
|
86
|
+
.service_ops.incident.recovery_required = false |
|
|
87
|
+
.service_ops.incident.partial_recovery = false |
|
|
88
|
+
.service_ops.incident.close_candidate = false |
|
|
89
|
+
.service_ops.incident.closed = false |
|
|
90
|
+
.service_ops.incident.last_seen_at = $ts
|
|
83
91
|
' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
|
|
84
92
|
|
|
85
93
|
{
|
|
@@ -187,15 +195,47 @@ incidents_json="$(jq '
|
|
|
187
195
|
ts: .ts
|
|
188
196
|
}
|
|
189
197
|
]' <<<"$results_json")"
|
|
198
|
+
incident_signature="$(jq -r '[.[].id] | sort | join(",")' <<<"$incidents_json")"
|
|
199
|
+
prev_signature="$(jq -r '(.service_ops.incident.signature // "") as $s | if $s != "" then $s else ([.service_ops.incident.open[]?.id] | sort | join(",")) end' "$PROGRESS" 2>/dev/null || echo "")"
|
|
200
|
+
prev_repeat="$(jq -r '.service_ops.incident.repeat_count // 0' "$PROGRESS" 2>/dev/null || echo 0)"
|
|
201
|
+
repeat_count=0
|
|
202
|
+
if [ -n "$incident_signature" ]; then
|
|
203
|
+
if [ -n "$prev_signature" ] && [ "${prev_repeat:-0}" -lt 1 ]; then
|
|
204
|
+
prev_repeat=1
|
|
205
|
+
fi
|
|
206
|
+
if [ "$incident_signature" = "$prev_signature" ]; then
|
|
207
|
+
repeat_count=$((prev_repeat + 1))
|
|
208
|
+
else
|
|
209
|
+
repeat_count=1
|
|
210
|
+
fi
|
|
211
|
+
fi
|
|
212
|
+
recovery_required=false
|
|
213
|
+
if [ "$repeat_count" -ge 2 ]; then
|
|
214
|
+
recovery_required=true
|
|
215
|
+
fi
|
|
216
|
+
partial_recovery=false
|
|
217
|
+
close_candidate=false
|
|
218
|
+
if [ "$ok_count" -gt 0 ] && [ "$down_count" -gt 0 ]; then
|
|
219
|
+
partial_recovery=true
|
|
220
|
+
elif [ "$down_count" -eq 0 ] && [ "$warn_count" -eq 0 ]; then
|
|
221
|
+
close_candidate=true
|
|
222
|
+
fi
|
|
190
223
|
|
|
191
|
-
jq --arg ts "$ts" --arg report "$report_rel" --argjson health "$results_json" --argjson incidents "$incidents_json" --argjson warn "$warn_count" --argjson alert "$down_count" '
|
|
224
|
+
jq --arg ts "$ts" --arg report "$report_rel" --arg signature "$incident_signature" --argjson repeat "$repeat_count" --argjson recovery "$recovery_required" --argjson partial "$partial_recovery" --argjson close_candidate "$close_candidate" --argjson health "$results_json" --argjson incidents "$incidents_json" --argjson warn "$warn_count" --argjson alert "$down_count" '
|
|
192
225
|
.service_ops.monitor.stream_active = false |
|
|
193
226
|
.service_ops.monitor.last_check = $ts |
|
|
194
227
|
.service_ops.monitor.last_report = $report |
|
|
195
228
|
.service_ops.monitor.warns_this_sprint = ((.service_ops.monitor.warns_this_sprint // 0) + $warn) |
|
|
196
229
|
.service_ops.monitor.alerts_this_sprint = ((.service_ops.monitor.alerts_this_sprint // 0) + $alert) |
|
|
197
230
|
.service_ops.health = $health |
|
|
198
|
-
.service_ops.incident.open = $incidents
|
|
231
|
+
.service_ops.incident.open = $incidents |
|
|
232
|
+
.service_ops.incident.signature = $signature |
|
|
233
|
+
.service_ops.incident.repeat_count = $repeat |
|
|
234
|
+
.service_ops.incident.recovery_required = $recovery |
|
|
235
|
+
.service_ops.incident.partial_recovery = $partial |
|
|
236
|
+
.service_ops.incident.close_candidate = $close_candidate |
|
|
237
|
+
.service_ops.incident.closed = (if $close_candidate then true else false end) |
|
|
238
|
+
.service_ops.incident.last_seen_at = $ts
|
|
199
239
|
' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
|
|
200
240
|
|
|
201
241
|
{
|
|
@@ -208,6 +248,11 @@ jq --arg ts "$ts" --arg report "$report_rel" --argjson health "$results_json" --
|
|
|
208
248
|
echo "- ok: $ok_count"
|
|
209
249
|
echo "- warnings: $warn_count"
|
|
210
250
|
echo "- alerts: $down_count"
|
|
251
|
+
echo "- incident_signature: ${incident_signature:-none}"
|
|
252
|
+
echo "- repeat_count: $repeat_count"
|
|
253
|
+
echo "- recovery_required: $recovery_required"
|
|
254
|
+
echo "- partial_recovery: $partial_recovery"
|
|
255
|
+
echo "- close_candidate: $close_candidate"
|
|
211
256
|
echo ""
|
|
212
257
|
echo "| Service | Port | Health | Logs | Status |"
|
|
213
258
|
echo "|---|---:|---|---|---|"
|