This repository was archived by the owner on Oct 8, 2026. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathcodegen-log_test.sh
More file actions
executable file
·834 lines (761 loc) · 51.2 KB
/
Copy pathcodegen-log_test.sh
File metadata and controls
executable file
·834 lines (761 loc) · 51.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
#!/usr/bin/env bash
# codegen-log_test.sh — unit tests for codegen-log (init / section / append),
# JSONL cycle-log storage.
#
# Cases:
# (a) --role override yields correct role event, ignoring ambient AGENT_TYPE/CLAUDE_ROLE
# (b) empty-body `section --role <role>` still appends a role event
# (c) `append --role <role>` preserves prior role event and adds the new body in order
# (e) unsupported `--role foo` exits 2, error names --role remedy
# (g) `init` is idempotent: re-init on an existing slug prints the same path;
# a distinct slug forks a new log
# (h) `--slug` resolves to the matching log among multiple logs in one workspace
# (i) `--slug` with zero matches exits 2 with "no cycle log matches slug"
# (j) `--slug` with multiple matches exits 2 with "ambiguous slug"
# (o) `section` plain-prose body passes through byte-identical (opaque string,
# no re-parsing as markdown structure — the whole class of stray-H2
# indenting logic is gone under JSONL)
# (r) append --learned emits a structured learned event
# (s) append --died interrupted/aborted emits structured died events
# (t) append --verdict clear|failed|inconclusive emits structured gate events
# (u) --learned/--died/--verdict are mutually exclusive with each other;
# --died/--verdict are append-only; --learned+--body are mutually
# exclusive on append (but --learned is valid ALONGSIDE --body on section)
# (v) init writes the .active sentinel with the resolved absolute log path
# (w) positional role resolves for section/append; implicit stdin (no --body)
# (x) .active sentinel precedence over a more-recently-touched log
# (y) relocate renames the log file (preserving _cycle.jsonl suffix) and
# rewrites .active to the new path
# (aa) `verdict` writes a structured "gate" event (role=dev-gate), and
# repeated calls APPEND a fresh event each time rather than replacing
# the prior one (dev-gate re-runs across retries must all remain visible)
# (bb) `section <role> --learned "<text>"` emits BOTH a "role" event and a
# "learned" event in one call; `section` without --learned emits only
# the "role" event and never refuses the write for OMITTING --learned
# (a non-placeholder body still writes with zero learned events)
# (dd) substance filter: --learned/--no-learning/section+append body text
# that is a whole-text placeholder OR contains a compliance-echo
# phrase is REFUSED (exit 2, writes nothing); genuine content survives
# (ee) --no-learning emits a structured no_learning event (the legal,
# countable "nothing to learn" exit); mutually exclusive with
# --learned/--died/--verdict; append-only
# (ff) `show` read-only render: default table format renders role rows,
# "no anomalies", and a totals line; --format md/html render alternate
# formats (html escapes special chars); --role drills into one role's
# body/learned/no_learning; --full prints every role's body
# (gg) `show` anomaly detection: a role invoked (per cycle-summary) with no
# ev:role body, and a role with neither learned nor no_learning, both
# surface as anomalies; a clean cycle prints "no anomalies"
# (hh) `show` degrades deterministically: no sibling cycle-summary dir ->
# turns/cost render "—", one stderr note, exit 0; unknown ev kind is
# counted and reported, never dropped
# (ii) `show` error paths: bad --format exits 2; --role naming a role absent
# from the log exits 2; a malformed (non-JSON) log line exits non-zero
# instead of a partial render
set -euo pipefail
# Every case below builds its own explicit workspace + log path and must never
# inherit an ambient CODEGEN_LOG_PATH from the invoking shell (e.g. a live
# developer session working on codegen-log/orchestration-loop pitches, which
# legitimately exports CODEGEN_LOG_PATH pointing at its OWN active cycle log).
# CODEGEN_LOG_PATH is codegen-log's highest-precedence resolver — left set, it
# silently redirects every `section`/`append` call below into that unrelated
# log instead of the per-case tmp workspace, which then reads back your own
# cycle log's role/body events. Unsetting once here is exhaustive; scrubbing
# every individual `env -u ...` call site is not (this file also runs several
# calls without an explicit `env -u` wrapper at all).
unset CODEGEN_LOG_PATH
HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CODEGEN_ROOT="$HOOKS_DIR"
CODEGEN_LOG="$CODEGEN_ROOT/codegen-log"
pass=0
fail=0
check() {
local desc="$1"
local expected="$2"
local actual="$3"
if [[ "$actual" == "$expected" ]]; then
[ -n "${VERBOSE:-}" ] && printf 'PASS: %s\n' "$desc"
pass=$((pass + 1))
else
printf 'FAIL: %s — expected %q, got %q\n' "$desc" "$expected" "$actual"
fail=$((fail + 1))
fi
}
assert_contains() {
local desc="$1"
local haystack="$2"
local needle="$3"
if [[ "$haystack" == *"$needle"* ]]; then
[ -n "${VERBOSE:-}" ] && printf 'PASS: %s\n' "$desc"
pass=$((pass + 1))
else
printf 'FAIL: %s — expected to find %q\n got: %s\n' "$desc" "$needle" "${haystack:0:400}"
fail=$((fail + 1))
fi
}
assert_not_contains() {
local desc="$1"
local haystack="$2"
local needle="$3"
if [[ "$haystack" != *"$needle"* ]]; then
[ -n "${VERBOSE:-}" ] && printf 'PASS: %s\n' "$desc"
pass=$((pass + 1))
else
printf 'FAIL: %s — expected NOT to find %q\n got: %s\n' "$desc" "$needle" "${haystack:0:400}"
fail=$((fail + 1))
fi
}
# jq_count <file> <jq-select-expr> — count matching JSONL lines.
jq_count() {
local file="$1" expr="$2"
jq -c "$expr" "$file" 2>/dev/null | grep -c . || true
}
new_workspace() {
local tmp
tmp="$(mktemp -d)"
mkdir -p "$tmp/codegen/logging"
printf '%s' "$tmp"
}
init_log() {
local root="$1"
local slug="$2"
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$root" \
"$CODEGEN_LOG" init --slug "$slug"
}
# init_log_stamped <root> <slug> <stamp> — init pinned to an explicit
# YYYYMMDD_HHMMSS run-identity stamp, so re-init calls in the same test can
# assert exact-path adoption without racing the wall clock.
init_log_stamped() {
local root="$1"
local slug="$2"
local stamp="$3"
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$root" \
"$CODEGEN_LOG" init --slug "$slug" --stamp "$stamp"
}
# ─────────────────────────────────────────────────────────────────────────────
# (a) --role override yields correct role event, ignoring ambient env
WS_A="$(new_workspace)"
LOG_A="$(init_log "$WS_A" test-role-override)"
OUT_A=$(printf 'body a\n' | env -u AGENT_TYPE -u CODEGEN_LOG_PATH CLAUDE_ROLE=context-curator \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_A" \
"$CODEGEN_LOG" section --role reviewer-phoenix --body @-)
check "(a) --role override appends a reviewer-phoenix role event, not context-curator" "1" "$(jq_count "$LOG_A" 'select(.ev=="role" and .role=="reviewer-phoenix")')"
check "(a) context-curator role event NOT inserted (env ignored)" "0" "$(jq_count "$LOG_A" 'select(.ev=="role" and .role=="context-curator")')"
# ─────────────────────────────────────────────────────────────────────────────
# (b) empty-body `section --role <role>` still appends a role event
WS_B="$(new_workspace)"
LOG_B="$(init_log "$WS_B" test-empty-open)"
printf '' | env -u AGENT_TYPE -u CLAUDE_ROLE -u CODEGEN_LOG_PATH \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_B" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- >/dev/null
check "(b) empty-body section appends a developer-phoenix-backend role event" "1" "$(jq_count "$LOG_B" 'select(.ev=="role" and .role=="developer-phoenix-backend")')"
# ─────────────────────────────────────────────────────────────────────────────
# (c) append preserves prior role event, adds new one in order
WS_C="$(new_workspace)"
LOG_C="$(init_log "$WS_C" test-append-order)"
printf 'ORIGINAL BODY LINE\n' | env -u AGENT_TYPE -u CLAUDE_ROLE -u CODEGEN_LOG_PATH \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_C" \
"$CODEGEN_LOG" section --role reviewer-phoenix --body @- >/dev/null
printf 'SECOND BODY LINE\n' | env -u AGENT_TYPE -u CLAUDE_ROLE -u CODEGEN_LOG_PATH \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_C" \
"$CODEGEN_LOG" append --role reviewer-phoenix --body @- >/dev/null
check "(c) append appends a second reviewer-phoenix role event" "2" "$(jq_count "$LOG_C" 'select(.ev=="role" and .role=="reviewer-phoenix")')"
ORIG_IDX=$(grep -n "ORIGINAL BODY LINE" "$LOG_C" | cut -d: -f1)
SECOND_IDX=$(grep -n "SECOND BODY LINE" "$LOG_C" | cut -d: -f1)
ORDER_OK=0
[ "$ORIG_IDX" -lt "$SECOND_IDX" ] && ORDER_OK=1
check "(c) original body precedes appended body" "1" "$ORDER_OK"
# ─────────────────────────────────────────────────────────────────────────────
# (e) unsupported --role foo exits 2
WS_E="$(new_workspace)"
init_log "$WS_E" test-unsupported-role >/dev/null
set +e
ERR_E=$(printf 'x\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_E" \
"$CODEGEN_LOG" section --role foo --body @- 2>&1)
RC_E=$?
set -e
check "(e) unsupported --role foo exits 2" "2" "$RC_E"
assert_contains "(e) unsupported-role error names --role remedy" "$ERR_E" "--role"
# (e2) `loop` is in the accepted role vocabulary (it authors files_to_touch);
# `planner-phoenix` is NOT — the role is gone, so its name must be rejected
# exactly like any other unknown token.
printf 'x\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_E" \
"$CODEGEN_LOG" section --role loop --body @- >/dev/null
check "(e2) --role loop is accepted" "1" \
"$(jq_count "$(ls "$WS_E"/codegen/logging/*_test-unsupported-role_cycle.jsonl)" 'select(.ev=="role" and .role=="loop")')"
set +e
ERR_E3=$(printf 'x\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_E" \
"$CODEGEN_LOG" section --role planner-phoenix --body @- 2>&1)
RC_E3=$?
set -e
check "(e2) --role planner-phoenix is rejected (role deleted)" "2" "$RC_E3"
# ─────────────────────────────────────────────────────────────────────────────
# (g) init binds by RUN IDENTITY: re-init with the SAME slug+stamp adopts the
# exact existing path (idempotent at the exact-path level); re-init with the
# SAME slug but a DIFFERENT stamp (a genuine retry) forks a NEW path — a
# slug alone is never enough to reuse a predecessor's log. A distinct slug
# also forks a new path.
WS_G="$(new_workspace)"
LOG_G1="$(init_log_stamped "$WS_G" test-idempotent 20260101_100000)"
LOG_G1_AGAIN="$(init_log_stamped "$WS_G" test-idempotent 20260101_100000)"
check "(g) same slug+stamp re-init adopts the exact same path" "$LOG_G1" "$LOG_G1_AGAIN"
LOG_G2="$(init_log_stamped "$WS_G" test-idempotent 20260101_110000)"
IDEMPOTENT_RETRY_DISTINCT=1
[ "$LOG_G1" = "$LOG_G2" ] && IDEMPOTENT_RETRY_DISTINCT=0
check "(g) same slug, different stamp (a retry) forks a new log" "1" "$IDEMPOTENT_RETRY_DISTINCT"
LOG_G3="$(init_log_stamped "$WS_G" test-idempotent-other 20260101_100000)"
IDEMPOTENT_DISTINCT=1
[ "$LOG_G1" = "$LOG_G3" ] && IDEMPOTENT_DISTINCT=0
check "(g) a distinct slug forks a new log" "1" "$IDEMPOTENT_DISTINCT"
# ─────────────────────────────────────────────────────────────────────────────
# (h) --slug resolves to the matching log among multiple logs in one workspace
WS_H="$(new_workspace)"
LOG_H1="$(init_log "$WS_H" slug-one)"
LOG_H2="$(init_log "$WS_H" slug-two)"
printf 'body for slug one\n' | env -u AGENT_TYPE -u CLAUDE_ROLE -u CODEGEN_LOG_PATH \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_H" \
"$CODEGEN_LOG" section --role context-curator --slug slug-one --body @- >/dev/null
CONTENT_H1="$(cat "$LOG_H1")"
CONTENT_H2="$(cat "$LOG_H2")"
assert_contains "(h) --slug section writes into the matching log" "$CONTENT_H1" "body for slug one"
NOT_IN_H2=1
[[ "$CONTENT_H2" == *"body for slug one"* ]] && NOT_IN_H2=0
check "(h) --slug section does NOT write into the non-matching log" "1" "$NOT_IN_H2"
# ─────────────────────────────────────────────────────────────────────────────
# (i) --slug with zero matches exits 2 with "no cycle log matches slug"
WS_I="$(new_workspace)"
init_log "$WS_I" some-other-slug >/dev/null
set +e
ERR_I=$(printf 'x\n' | env -u AGENT_TYPE -u CLAUDE_ROLE -u CODEGEN_LOG_PATH \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_I" \
"$CODEGEN_LOG" section --role context-curator --slug missing-slug --body @- 2>&1)
RC_I=$?
set -e
check "(i) --slug zero-match exits 2" "2" "$RC_I"
assert_contains "(i) --slug zero-match error message" "$ERR_I" "no cycle log matches slug"
# ─────────────────────────────────────────────────────────────────────────────
# (j) --slug with multiple matches exits 2 with "ambiguous slug"
WS_J="$(new_workspace)"
touch "$WS_J/codegen/logging/20260101_000001_dup_cycle.jsonl"
touch "$WS_J/codegen/logging/20260101_000002_dup_cycle.jsonl"
set +e
ERR_J=$(printf 'x\n' | env -u AGENT_TYPE -u CLAUDE_ROLE -u CODEGEN_LOG_PATH \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_J" \
"$CODEGEN_LOG" section --role context-curator --slug dup --body @- 2>&1)
RC_J=$?
set -e
check "(j) --slug many-match exits 2" "2" "$RC_J"
assert_contains "(j) --slug many-match error message" "$ERR_J" "ambiguous slug"
# ─────────────────────────────────────────────────────────────────────────────
# (o) section: plain-prose body passes through byte-identical (opaque string
# under JSONL — no re-parsing as markdown, so no indenting/mangling exists)
WS_O="$(new_workspace)"
LOG_O="$(init_log "$WS_O" test-opaque-prose-passthrough)"
printf 'just plain prose\nwith multiple lines\nno structural markers\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_O" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- >/dev/null
BODY_O="$(jq -r --arg r developer-phoenix-backend 'select(.ev=="role" and .role==$r)|.body' "$LOG_O")"
assert_contains "(o) plain prose passes through unchanged" "$BODY_O" $'just plain prose\nwith multiple lines\nno structural markers'
# ─────────────────────────────────────────────────────────────────────────────
# (o2) section: a body containing a stray "## Fake Header" line passes through
# verbatim, never promoted to structure — the whole indent-mangling mechanism
# is gone under JSONL (a body is just an opaque JSON string value).
WS_O2="$(new_workspace)"
LOG_O2="$(init_log "$WS_O2" test-opaque-stray-h2)"
printf 'prose before\n\n## Fake Header\n\nprose after\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_O2" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- >/dev/null
BODY_O2="$(jq -r --arg r developer-phoenix-backend 'select(.ev=="role" and .role==$r)|.body' "$LOG_O2")"
assert_contains "(o2) stray H2 preserved verbatim in body (no indent-mangling)" "$BODY_O2" "## Fake Header"
# ─────────────────────────────────────────────────────────────────────────────
# (r) append --learned emits a structured learned event.
WS_R="$(new_workspace)"
LOG_R="$(init_log "$WS_R" test-learned-marker)"
printf 'body\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_R" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- >/dev/null
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_R" \
"$CODEGEN_LOG" append --role developer-phoenix-backend --learned "[local] - nothing notable" >/dev/null
check "(r) --learned emits exactly one learned event" "1" "$(jq_count "$LOG_R" 'select(.ev=="learned" and .role=="developer-phoenix-backend")')"
check "(r) --learned emits the supplied text" "[local] - nothing notable" "$(jq -r 'select(.ev=="learned")|.text' "$LOG_R")"
# ─────────────────────────────────────────────────────────────────────────────
# (s) append --died interrupted/aborted emits structured died events.
WS_S="$(new_workspace)"
LOG_S="$(init_log "$WS_S" test-died-marker)"
printf 'body\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_S" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- >/dev/null
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_S" \
"$CODEGEN_LOG" append --role developer-phoenix-backend --died interrupted --cause "timeout" >/dev/null
check "(s) --died interrupted emits a died event" "1" "$(jq_count "$LOG_S" 'select(.ev=="died" and .kind=="interrupted")')"
check "(s) --died interrupted carries the cause" "timeout" "$(jq -r 'select(.ev=="died" and .kind=="interrupted")|.cause' "$LOG_S")"
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_S" \
"$CODEGEN_LOG" append --role developer-phoenix-backend --died aborted >/dev/null
check "(s) --died aborted emits a died event" "1" "$(jq_count "$LOG_S" 'select(.ev=="died" and .kind=="aborted")')"
# ─────────────────────────────────────────────────────────────────────────────
# (t) append --verdict clear|failed|inconclusive emits structured gate events.
WS_T="$(new_workspace)"
LOG_T="$(init_log "$WS_T" test-verdict-marker)"
printf 'body\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_T" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- >/dev/null
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_T" \
"$CODEGEN_LOG" append --role developer-phoenix-backend --verdict clear >/dev/null
check "(t) --verdict clear emits gate event verdict=clear" "1" "$(jq_count "$LOG_T" 'select(.ev=="gate" and .verdict=="clear")')"
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_T" \
"$CODEGEN_LOG" append --role developer-phoenix-backend --verdict failed >/dev/null
check "(t) --verdict failed emits gate event verdict=failed" "1" "$(jq_count "$LOG_T" 'select(.ev=="gate" and .verdict=="failed")')"
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_T" \
"$CODEGEN_LOG" append --role developer-phoenix-backend --verdict inconclusive >/dev/null
check "(t) --verdict inconclusive emits gate event verdict=inconclusive" "1" "$(jq_count "$LOG_T" 'select(.ev=="gate" and .verdict=="inconclusive")')"
# ─────────────────────────────────────────────────────────────────────────────
# (u) --learned/--died/--verdict are mutually exclusive with each other and
# with --body; --learned/--died/--verdict are append-only.
WS_U="$(new_workspace)"
init_log "$WS_U" test-marker-exclusivity >/dev/null
set +e
ERR_U1=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_U" \
"$CODEGEN_LOG" append --role context-curator --learned "x" --verdict clear 2>&1)
RC_U1=$?
set -e
check "(u) --learned + --verdict exits 2" "2" "$RC_U1"
assert_contains "(u) mutual-exclusivity error message" "$ERR_U1" "mutually exclusive"
set +e
ERR_U2=$(printf 'x\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_U" \
"$CODEGEN_LOG" append --role context-curator --learned "x" --body @- 2>&1)
RC_U2=$?
set -e
check "(u) --learned + --body on append exits 2" "2" "$RC_U2"
assert_contains "(u) --learned+--body mutual-exclusivity error message" "$ERR_U2" "mutually exclusive"
# ─────────────────────────────────────────────────────────────────────────────
# (v) init writes the .active sentinel with the resolved absolute log path.
WS_V="$(new_workspace)"
LOG_V="$(init_log "$WS_V" test-active-sentinel)"
SENTINEL_V="$WS_V/codegen/logging/.active"
check "(v) init writes .active sentinel file" "0" "$([ -f "$SENTINEL_V" ] && printf 0 || printf 1)"
check "(v) .active sentinel contains the resolved log path" "$LOG_V" "$(cat "$SENTINEL_V")"
# ─────────────────────────────────────────────────────────────────────────────
# (w) positional role resolves for section/append; implicit stdin (no --body).
WS_W="$(new_workspace)"
LOG_W="$(init_log "$WS_W" test-positional-role)"
printf 'positional section body\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_W" \
"$CODEGEN_LOG" section developer-phoenix-backend >/dev/null
assert_contains "(w) positional section resolves role + implicit stdin" "$(cat "$LOG_W")" "positional section body"
printf 'positional append body\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_W" \
"$CODEGEN_LOG" append developer-phoenix-backend >/dev/null
assert_contains "(w) positional append resolves role + implicit stdin" "$(cat "$LOG_W")" "positional append body"
# ─────────────────────────────────────────────────────────────────────────────
# (x) .active sentinel precedence: with no CODEGEN_LOG_PATH/--slug given, the
# sentinel-pointed log is used, NOT the most-recently-touched log on disk.
WS_X="$(new_workspace)"
LOG_X1="$(init_log "$WS_X" test-sentinel-precedence-one)"
LOG_X2="$(init_log "$WS_X" test-sentinel-precedence-two)"
# .active now points at LOG_X2 (the most recent init). Touch LOG_X1 so mtime
# fallback (if wrongly used) would pick LOG_X1 instead.
touch "$LOG_X1"
printf 'via sentinel\n' | env -u AGENT_TYPE -u CLAUDE_ROLE -u CODEGEN_LOG_PATH \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_X" \
"$CODEGEN_LOG" section developer-phoenix-backend >/dev/null
VIA_SENTINEL_IN_X2=0
grep -q "via sentinel" "$LOG_X2" && VIA_SENTINEL_IN_X2=1
check "(x) .active sentinel is honored over a more-recently-touched log" "1" "$VIA_SENTINEL_IN_X2"
VIA_SENTINEL_IN_X1=0
grep -q "via sentinel" "$LOG_X1" && VIA_SENTINEL_IN_X1=1
check "(x) .active sentinel write did not divert to the touched log" "0" "$VIA_SENTINEL_IN_X1"
# ─────────────────────────────────────────────────────────────────────────────
# (y) relocate renames the log file (preserving _cycle.jsonl suffix) and
# rewrites .active to the new path.
WS_Y="$(new_workspace)"
LOG_Y="$(init_log "$WS_Y" test-relocate-old)"
RELOCATE_OUT_Y=$(env -u AGENT_TYPE -u CLAUDE_ROLE -u CODEGEN_LOG_PATH \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_Y" \
"$CODEGEN_LOG" relocate --new-slug test-relocate-new)
NEW_LOG_Y=$(printf '%s' "$RELOCATE_OUT_Y" | tail -n 1)
check "(y) relocate returns a path ending in the new slug + _cycle.jsonl" "0" "$([[ "$NEW_LOG_Y" == *_test-relocate-new_cycle.jsonl ]] && printf 0 || printf 1)"
check "(y) old log path no longer exists after relocate" "0" "$([ ! -f "$LOG_Y" ] && printf 0 || printf 1)"
check "(y) new log path exists after relocate" "0" "$([ -f "$NEW_LOG_Y" ] && printf 0 || printf 1)"
check "(y) .active sentinel rewritten to the new path" "$NEW_LOG_Y" "$(cat "$WS_Y/codegen/logging/.active")"
# ─────────────────────────────────────────────────────────────────────────────
# (z) bare positional-less section/append with no --role and no ambient env
# still exits 2 with a clear error naming both remedies (positional + --role);
# ambient env vars (AGENT_TYPE/CLAUDE_ROLE) are NEVER consulted.
WS_Z="$(new_workspace)"
init_log "$WS_Z" test-bare-section-removed >/dev/null
set +e
ERR_Z=$(printf 'x\n' | env AGENT_TYPE=developer-phoenix-backend CLAUDE_ROLE=developer-phoenix-backend \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_Z" \
"$CODEGEN_LOG" section --body @- 2>&1)
RC_Z=$?
set -e
check "(z) bare section with no role exits 2 even with AGENT_TYPE/CLAUDE_ROLE set" "2" "$RC_Z"
assert_contains "(z) error names the positional remedy" "$ERR_Z" "positional"
assert_contains "(z) error names the --role remedy" "$ERR_Z" "--role"
# ─────────────────────────────────────────────────────────────────────────────
# (aa) `verdict` writes a structured gate event (role=dev-gate), and repeated
# calls APPEND a fresh event each time rather than replacing the prior one
# (dev-gate re-runs across retries must all remain visible).
WS_AA="$(new_workspace)"
LOG_AA="$(init_log "$WS_AA" test-verdict-subcommand)"
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_AA" \
"$CODEGEN_LOG" verdict --gate "make test" --mode short --result "ALL CLEAR ✅" >/dev/null
check "(aa) verdict emits exactly one gate event" "1" "$(jq_count "$LOG_AA" 'select(.ev=="gate" and .role=="dev-gate")')"
check "(aa) verdict derives verdict=clear" "clear" "$(jq -r 'select(.ev=="gate")|.verdict' "$LOG_AA")"
check "(aa) verdict carries gate command" "make test" "$(jq -r 'select(.ev=="gate")|.gate' "$LOG_AA")"
check "(aa) verdict carries mode" "short" "$(jq -r 'select(.ev=="gate")|.mode' "$LOG_AA")"
check "(aa) verdict carries raw result text" "ALL CLEAR ✅" "$(jq -r 'select(.ev=="gate")|.result' "$LOG_AA")"
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_AA" \
"$CODEGEN_LOG" verdict --gate "make test" --mode short --result "FAILED ❌ exit=1" --detail "Log: /tmp/foo.log" >/dev/null
check "(aa) second verdict call appends a NEW gate event (does not replace)" "2" "$(jq_count "$LOG_AA" 'select(.ev=="gate" and .role=="dev-gate")')"
check "(aa) first verdict event still present after second call" "1" "$(jq_count "$LOG_AA" 'select(.ev=="gate" and .result=="ALL CLEAR ✅")')"
check "(aa) second verdict event derives verdict=failed" "1" "$(jq_count "$LOG_AA" 'select(.ev=="gate" and .verdict=="failed")')"
check "(aa) second verdict event's detail present" "Log: /tmp/foo.log" "$(jq -r 'select(.ev=="gate" and .verdict=="failed")|.detail' "$LOG_AA")"
# ─────────────────────────────────────────────────────────────────────────────
# (bb) `section <role> --learned "<text>"` emits BOTH an "ev":"role" event
# (from --body) AND an "ev":"learned" event, in one call. `section` without
# --learned emits exactly one "ev":"role" event and zero "ev":"learned"
# events — section NEVER refuses a write.
WS_BB="$(new_workspace)"
LOG_BB="$(init_log "$WS_BB" test-section-learned)"
printf 'did the work\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_BB" \
"$CODEGEN_LOG" section context-curator --learned "[local] learned something useful this step" >/dev/null
check "(bb) section --learned emits exactly one role event" "1" "$(jq_count "$LOG_BB" 'select(.ev=="role" and .role=="context-curator")')"
check "(bb) section --learned emits exactly one learned event" "1" "$(jq_count "$LOG_BB" 'select(.ev=="learned" and .role=="context-curator")')"
check "(bb) role event body is the piped stdin" "did the work" "$(jq -r 'select(.ev=="role")|.body' "$LOG_BB")"
check "(bb) learned event text matches --learned" "[local] learned something useful this step" "$(jq -r 'select(.ev=="learned")|.text' "$LOG_BB")"
WS_BB2="$(new_workspace)"
LOG_BB2="$(init_log "$WS_BB2" test-section-no-learned)"
printf 'no learned flag here\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_BB2" \
"$CODEGEN_LOG" section context-curator >/dev/null
check "(bb) section without --learned emits exactly one role event" "1" "$(jq_count "$LOG_BB2" 'select(.ev=="role" and .role=="context-curator")')"
check "(bb) section without --learned emits zero learned events (never refuses)" "0" "$(jq_count "$LOG_BB2" 'select(.ev=="learned")')"
# ─────────────────────────────────────────────────────────────────────────────
# (cc) init refuses (exit 2) when CODEGEN_LOG_PATH is set — creates no new
# file and leaves .active byte-identical. Also proves the resolver-side fix:
# a guard resolving via CODEGEN_LOG_PATH still lands on the REAL pinned log
# even though a same-process init-under-pin attempt was made and rejected —
# this is the exact hijack this pitch closes (cycle-20260714_182153).
WS_CC="$(new_workspace)"
LOG_CC="$(init_log "$WS_CC" test-pin-refusal)"
ACTIVE_CC="$WS_CC/codegen/logging/.active"
ACTIVE_BEFORE_CC="$(cat "$ACTIVE_CC")"
FILES_BEFORE_CC="$(find "$WS_CC/codegen/logging" -name '*_cycle.jsonl' | sort)"
set +e
ERR_CC=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_CC" CODEGEN_LOG_PATH="$LOG_CC" \
"$CODEGEN_LOG" init --slug rival-typo-slug 2>&1)
RC_CC=$?
set -e
check "(cc) init under CODEGEN_LOG_PATH pin exits 2" "2" "$RC_CC"
assert_contains "(cc) refusal message names the pinned path" "$ERR_CC" "$LOG_CC"
FILES_AFTER_CC="$(find "$WS_CC/codegen/logging" -name '*_cycle.jsonl' | sort)"
check "(cc) refusal created no new *_cycle.jsonl file" "$FILES_BEFORE_CC" "$FILES_AFTER_CC"
check "(cc) refusal left .active byte-identical" "$ACTIVE_BEFORE_CC" "$(cat "$ACTIVE_CC")"
# ─────────────────────────────────────────────────────────────────────────────
# (dd) substance filter: whole-text placeholders and compliance-echo phrases
# are refused (exit 2, writes nothing); genuine content survives.
WS_DD="$(new_workspace)"
LOG_DD="$(init_log "$WS_DD" test-substance-filter)"
set +e
ERR_DD1=$(printf 'test body\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_DD" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- 2>&1)
RC_DD1=$?
set -e
check "(dd) placeholder body 'test body' exits 2" "2" "$RC_DD1"
assert_contains "(dd) placeholder body refusal names the check" "$ERR_DD1" "describes the check"
check "(dd) placeholder body refusal wrote zero events" "0" "$(jq_count "$LOG_DD" 'select(.ev=="role")' 2>/dev/null || echo 0)"
set +e
ERR_DD2=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_DD" \
"$CODEGEN_LOG" append --role developer-phoenix-backend --learned "[local] placeholder learning text that is at least forty characters long for testing" 2>&1)
RC_DD2=$?
set -e
check "(dd) compliance-echo --learned text exits 2" "2" "$RC_DD2"
assert_contains "(dd) compliance-echo refusal names the check" "$ERR_DD2" "describes the check"
printf 'a real body\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_DD" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- \
--learned "[local] placeholder-project-context-fix landed cleanly; the slug survives the filter" >/dev/null
check "(dd) genuine slug-shaped text survives the filter" "1" "$(jq_count "$LOG_DD" 'select(.ev=="learned")')"
# ─────────────────────────────────────────────────────────────────────────────
# (ee) --no-learning emits a structured no_learning event; mutually exclusive
# with --learned/--died/--verdict; append-only.
WS_EE="$(new_workspace)"
LOG_EE="$(init_log "$WS_EE" test-no-learning)"
printf 'refused this step\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_EE" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- >/dev/null
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_EE" \
"$CODEGEN_LOG" append --role developer-phoenix-backend --no-learning "refused: handoff named no files, reviewed zero code" >/dev/null
check "(ee) --no-learning emits exactly one no_learning event" "1" "$(jq_count "$LOG_EE" 'select(.ev=="no_learning" and .role=="developer-phoenix-backend")')"
check "(ee) --no-learning emits the supplied text" "refused: handoff named no files, reviewed zero code" "$(jq -r 'select(.ev=="no_learning")|.text' "$LOG_EE")"
check "(ee) --no-learning emits zero learned events" "0" "$(jq_count "$LOG_EE" 'select(.ev=="learned")')"
set +e
ERR_EE1=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_EE" \
"$CODEGEN_LOG" append --role developer-phoenix-backend --no-learning "x" --learned "y" 2>&1)
RC_EE1=$?
set -e
check "(ee) --no-learning + --learned exits 2" "2" "$RC_EE1"
assert_contains "(ee) mutual-exclusivity error message" "$ERR_EE1" "mutually exclusive"
set +e
ERR_EE2=$(printf 'x\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_EE" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- --no-learning "z" 2>&1)
RC_EE2=$?
set -e
check "(ee) --no-learning on section exits 2 (append-only)" "2" "$RC_EE2"
# ─────────────────────────────────────────────────────────────────────────────
# (ff) `show` default table render, --format md/html, --role drill-down,
# --full.
WS_FF="$(new_workspace)"
LOG_FF="$(init_log "$WS_FF" test-show-render)"
printf 'did the work\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_FF" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- --learned "[local] learned something real this step" >/dev/null
TABLE_FF=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_FF" \
"$CODEGEN_LOG" show --slug test-show-render)
assert_contains "(ff) table render shows the role row" "$TABLE_FF" "developer-phoenix-backend"
assert_contains "(ff) table render shows no anomalies" "$TABLE_FF" "no anomalies"
assert_contains "(ff) table render shows totals line" "$TABLE_FF" "role invocations"
MD_FF=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_FF" \
"$CODEGEN_LOG" show --slug test-show-render --format md)
assert_contains "(ff) md render has a markdown table header" "$MD_FF" "| # | Role |"
HTML_FF=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_FF" \
"$CODEGEN_LOG" show --slug test-show-render --format html)
assert_contains "(ff) html render contains a table tag" "$HTML_FF" "<table"
WS_FF2="$(new_workspace)"
LOG_FF2="$(init_log "$WS_FF2" test-show-html-escape)"
printf 'body with <script>&\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_FF2" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- --learned "[local] text with < and & escaped in html output" >/dev/null
HTML_ESCAPE_FF=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_FF2" \
"$CODEGEN_LOG" show --slug test-show-html-escape --format html --full)
assert_contains "(ff) html --full escapes body content (no raw <script>)" "$HTML_ESCAPE_FF" "<"
ROLE_FF=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_FF" \
"$CODEGEN_LOG" show --slug test-show-render --role developer-phoenix-backend)
assert_contains "(ff) --role drill-down shows the body" "$ROLE_FF" "did the work"
assert_contains "(ff) --role drill-down shows the learned text" "$ROLE_FF" "[local] learned something real this step"
FULL_FF=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_FF" \
"$CODEGEN_LOG" show --slug test-show-render --full)
assert_contains "(ff) --full shows role section marker" "$FULL_FF" "=== developer-phoenix-backend ==="
# ─────────────────────────────────────────────────────────────────────────────
# (gg) `show` anomaly detection: cycle-summary-only role (no ev:role body)
# and a role with neither learned nor no_learning both surface; clean cycle
# shows "no anomalies".
WS_GG="$(new_workspace)"
LOG_GG="$(init_log "$WS_GG" test-show-anomalies)"
STEM_GG="${LOG_GG%_cycle.jsonl}"
mkdir -p "$STEM_GG"
cat >"$STEM_GG/cycle-summary.jsonl" <<'SUMMARY'
{"cost_usd":0.1,"num_turns":3,"role":"context-curator","seq":1,"status":"success","transcript":"/tmp/c.jsonl"}
SUMMARY
ANOM_GG=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_GG" \
"$CODEGEN_LOG" show --slug test-show-anomalies)
assert_contains "(gg) invoked-but-no-body anomaly fires for context-curator" "$ANOM_GG" "context-curator: invoked but wrote no body"
assert_contains "(gg) no-learned/no_learning anomaly fires for context-curator" "$ANOM_GG" "no learned/no_learning event"
WS_GG2="$(new_workspace)"
LOG_GG2="$(init_log "$WS_GG2" test-show-clean)"
printf 'clean work\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_GG2" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- --learned "[local] clean cycle, real learning text here" >/dev/null
CLEAN_GG=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_GG2" \
"$CODEGEN_LOG" show --slug test-show-clean)
assert_contains "(gg) clean cycle prints literal no anomalies" "$CLEAN_GG" "no anomalies"
# ─────────────────────────────────────────────────────────────────────────────
# (hh) `show` degrades deterministically: no sibling summary dir -> "—" +
# stderr note, exit 0; unknown ev kind counted, never dropped.
WS_HH="$(new_workspace)"
LOG_HH="$(init_log "$WS_HH" test-show-no-summary)"
printf 'solo body\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_HH" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- --learned "[local] no summary sibling exists for this cycle" >/dev/null
OUT_HH=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_HH" \
"$CODEGEN_LOG" show --slug test-show-no-summary 2>/tmp/show_hh_stderr.txt)
RC_HH=$?
check "(hh) missing-sibling show exits 0" "0" "$RC_HH"
assert_contains "(hh) missing-sibling turns render as em dash" "$OUT_HH" "—"
assert_contains "(hh) missing-sibling stderr names the missing dir" "$(cat /tmp/show_hh_stderr.txt)" "no cycle-summary.jsonl sibling found"
rm -f /tmp/show_hh_stderr.txt
echo '{"ev":"mystery-kind","foo":"bar"}' >>"$LOG_HH"
UNKNOWN_HH=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_HH" \
"$CODEGEN_LOG" show --slug test-show-no-summary)
assert_contains "(hh) unknown ev kind promoted into anomaly list, not dropped" "$UNKNOWN_HH" "unknown event kind: mystery-kind (x1) — not in the schema"
assert_not_contains "(hh) unknown ev kind no longer prints as clean" "$UNKNOWN_HH" "no anomalies"
# ─────────────────────────────────────────────────────────────────────────────
# (ii) `show` error paths: bad --format exits 2; unknown --role exits 2;
# malformed log line exits non-zero (no partial render).
WS_II="$(new_workspace)"
LOG_II="$(init_log "$WS_II" test-show-errors)"
printf 'body\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_II" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- >/dev/null
set +e
ERR_II1=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_II" \
"$CODEGEN_LOG" show --slug test-show-errors --format zzz 2>&1)
RC_II1=$?
set -e
check "(ii) bad --format exits 2" "2" "$RC_II1"
assert_contains "(ii) bad --format error names the allowed values" "$ERR_II1" "table|md|html"
set +e
ERR_II2=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_II" \
"$CODEGEN_LOG" show --slug test-show-errors --role bogus-role-xyz 2>&1)
RC_II2=$?
set -e
check "(ii) unknown --role exits 2" "2" "$RC_II2"
assert_contains "(ii) unknown --role error names the role" "$ERR_II2" "bogus-role-xyz"
echo "not valid json" >>"$LOG_II"
set +e
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_II" \
"$CODEGEN_LOG" show --slug test-show-errors >/dev/null 2>&1
RC_II3=$?
set -e
check "(ii) malformed log line exits non-zero (not 0)" "1" "$([[ "$RC_II3" -ne 0 ]] && echo 1 || echo 0)"
# ─────────────────────────────────────────────────────────────────────────────
# (jj) `show` invoked-but-no-body anomaly under the ROLE-spine fallback (no
# ev:turn events, no cycle-summary.jsonl sibling): a role that only has a
# died event (invoked, then dropped before ever writing a body) must still
# trip "invoked but wrote no body" — this is the exact branch that was dead
# code before the fix (it only ever fired via .turn, which is always null
# on the role spine).
WS_JJ="$(new_workspace)"
LOG_JJ="$(init_log "$WS_JJ" test-show-role-spine-anomaly)"
printf 'clean work\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_JJ" \
"$CODEGEN_LOG" section --role developer-phoenix-backend --body @- --learned "[local] role-spine fallback fixture, real learning text" >/dev/null
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_JJ" \
"$CODEGEN_LOG" append --role context-curator --died interrupted --cause "session dropped before writing anything" >/dev/null
ANOM_JJ=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_JJ" \
"$CODEGEN_LOG" show --slug test-show-role-spine-anomaly)
assert_contains "(jj) role-spine fallback is actually in use (no ev:turn, no summary sibling)" "$ANOM_JJ" "spine: role"
assert_contains "(jj) invoked-but-no-body anomaly fires for context-curator under role-spine fallback" "$ANOM_JJ" "context-curator: invoked but wrote no body"
# ─────────────────────────────────────────────────────────────────────────────
# (oo) `append loop --files-to-touch @-` emits a structured files_to_touch
# event with a JSON array of relative paths. The loop is its only author.
WS_OO="$(new_workspace)"
LOG_OO="$(init_log "$WS_OO" test-files-to-touch)"
printf '["context/foo.md","lib/bar.ex"]' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_OO" \
"$CODEGEN_LOG" append loop --files-to-touch @- --slug test-files-to-touch >/dev/null
check "(oo) files-to-touch emits exactly one event" "1" "$(jq_count "$LOG_OO" 'select(.ev=="files_to_touch" and .role=="loop")')"
check "(oo) files-to-touch array count" "2" "$(jq -r 'select(.ev=="files_to_touch")|.files | length' "$LOG_OO")"
check "(oo) files-to-touch first file" "context/foo.md" "$(jq -r 'select(.ev=="files_to_touch")|.files[0]' "$LOG_OO")"
# (pp) --files-to-touch non-array JSON exits 2.
set +e
ERR_PP=$(printf '{"not":"an array"}' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_OO" \
"$CODEGEN_LOG" append loop --files-to-touch @- --slug test-files-to-touch 2>&1)
RC_PP=$?
set -e
check "(pp) files-to-touch non-array exits 2" "2" "$RC_PP"
# ─────────────────────────────────────────────────────────────────────────────
# (qq) `append <role> --files-modified @-` emits a structured files_modified
# event, authored by a developer role.
WS_QQ="$(new_workspace)"
LOG_QQ="$(init_log "$WS_QQ" test-files-modified)"
printf '["lib/bar.ex"]' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_QQ" \
"$CODEGEN_LOG" append developer-phoenix-backend --files-modified @- --slug test-files-modified >/dev/null
check "(qq) files-modified emits exactly one event" "1" "$(jq_count "$LOG_QQ" 'select(.ev=="files_modified" and .role=="developer-phoenix-backend")')"
check "(qq) files-modified array content" "lib/bar.ex" "$(jq -r 'select(.ev=="files_modified")|.files[0]' "$LOG_QQ")"
# (rr) --files-to-touch/--files-modified are mutually exclusive with each
# other and with --learned/--died/--verdict; and are append-only.
set +e
ERR_RR=$(printf '["a"]' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_QQ" \
"$CODEGEN_LOG" append loop --files-to-touch @- --learned "text" --slug test-files-modified 2>&1)
RC_RR=$?
set -e
check "(rr) --files-to-touch + --learned mutually exclusive exits 2" "2" "$RC_RR"
set +e
ERR_RR2=$(printf '["a"]' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_QQ" \
"$CODEGEN_LOG" section loop --files-to-touch @- --slug test-files-modified 2>&1)
RC_RR2=$?
set -e
check "(rr) --files-to-touch on section (not append) exits 2" "2" "$RC_RR2"
# ─────────────────────────────────────────────────────────────────────────────
# (vv) routing tag required on --learned: [local]/[shared] accepted verbatim
# (tag preserved in .text, not stripped); untagged text refused (exit 2,
# writes nothing — proves atomicity on `section` too: no role event lands
# either when the --learned tag check fails); leading whitespace tolerated;
# --no-learning stays exempt (no tag required).
WS_VV="$(new_workspace)"
LOG_VV="$(init_log "$WS_VV" test-routing-tag)"
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_VV" \
"$CODEGEN_LOG" append developer-phoenix-backend --learned "[local] real learning text here" >/dev/null
check "(vv) [local]-tagged --learned emits exactly one learned event" "1" "$(jq_count "$LOG_VV" 'select(.ev=="learned")')"
check "(vv) [local] tag preserved verbatim in .text" "[local] real learning text here" "$(jq -r 'select(.ev=="learned")|.text' "$LOG_VV")"
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_VV" \
"$CODEGEN_LOG" append developer-phoenix-backend --learned "[shared] cross-project idiom here" >/dev/null
check "(vv) [shared]-tagged --learned emits exactly two learned events total" "2" "$(jq_count "$LOG_VV" 'select(.ev=="learned")')"
set +e
ERR_VV1=$(env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_VV" \
"$CODEGEN_LOG" append developer-phoenix-backend --learned "untagged real learning text" 2>&1)
RC_VV1=$?
set -e
check "(vv) untagged --learned on append exits 2" "2" "$RC_VV1"
assert_contains "(vv) untagged refusal names both tags" "$ERR_VV1" "[local]"
assert_contains "(vv) untagged refusal names both tags" "$ERR_VV1" "[shared]"
check "(vv) untagged --learned on append writes zero NEW learned events" "2" "$(jq_count "$LOG_VV" 'select(.ev=="learned")')"
set +e
ERR_VV2=$(printf 'did real work\n' | env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_VV" \
"$CODEGEN_LOG" section developer-phoenix-backend --body @- --learned "untagged text" --slug test-routing-tag 2>&1)
RC_VV2=$?
set -e
check "(vv) untagged --learned on section exits 2" "2" "$RC_VV2"
check "(vv) untagged --learned on section wrote zero role events (atomic — even the body did not land)" "0" "$(jq_count "$LOG_VV" 'select(.ev=="role")')"
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_VV" \
"$CODEGEN_LOG" append developer-phoenix-backend --learned " [local] leading whitespace trimmed before tag check" >/dev/null
check "(vv) leading-whitespace-then-tag --learned accepted" "3" "$(jq_count "$LOG_VV" 'select(.ev=="learned")')"
env -u AGENT_TYPE -u CLAUDE_ROLE \
OCG_CODEGEN_DIR="$CODEGEN_ROOT" CODEGEN_BUILD_CWD="$WS_VV" \
"$CODEGEN_LOG" append developer-phoenix-backend --no-learning "untagged no-learning text is fine, no tag required" >/dev/null
check "(vv) --no-learning exempt from tag requirement" "1" "$(jq_count "$LOG_VV" 'select(.ev=="no_learning")')"
# ─────────────────────────────────────────────────────────────────────────────
echo ""
echo "Results: $pass passed, $fail failed"
if [[ "$fail" -gt 0 ]]; then
exit 1
fi
exit 0