Repository navigation
Expand file tree
/
Copy pathflows-rig.sh
More file actions
executable file
·1524 lines (1452 loc) · 61.6 KB
/
Copy pathflows-rig.sh
File metadata and controls
executable file
·1524 lines (1452 loc) · 61.6 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
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/sh
# flows-rig.sh: build, place and run basal's isolated test rig, ckdev-flows.
#
# The rig is a private subc daemon with its own port, its own XDG homes under
# ~/.local/share/cortexkit/ckdev-flows/ and its own ckdev-* binaries, every
# one built here from a named revision. It runs the credentials vault,
# Fusiform, Broca, entorhinal, prefrontal-core, prefrontal-routing, ck-basal and a
# rig-only callosum stub that answers consent cards as the operator. It never
# reads, writes or starts anything belonging to the production daemon or to
# another rig.
#
# Usage:
# script/flows-rig.sh build --prefrontal-rev <rev> [--subc-rev <rev>]
# [--broca-rev <rev>] [--credentials-rev <rev>]
# [--commons-rev <rev>] [--entorhinal-rev <rev>]
# [--fusiform-rev <rev>]
# [--sibling-lock <repo>]... [--dry-run]
# script/flows-rig.sh place [--from-stage <dir>] [--dry-run]
# script/flows-rig.sh config [--dry-run]
# script/flows-rig.sh start [--dry-run]
# script/flows-rig.sh status [--dry-run]
# script/flows-rig.sh stop [--dry-run]
# script/flows-rig.sh manifest [--dry-run]
# script/flows-rig.sh credential --key-file <path> [--dry-run]
# script/flows-rig.sh test [--models] [--dry-run]
#
# --dry-run prints every command the subcommand would run and every file it
# would write, with the file's content, and changes nothing.
#
# Subcommands:
# build clone each repository at the named revision and build it.
# --prefrontal-rev is required; the others default to their
# checkout's HEAD. basal is always built from this checkout's
# HEAD, without uncommitted changes. The source checkouts are
# only read. A build that changes a tracked file fails, except
# that --sibling-lock <repo> lets that repository's Cargo.lock
# change the versions of path dependencies on a sibling clone
# (claustrum builds against ../subconscious by path); the
# manifest then records its cargo_lock as sibling-refreshed.
# place sign every binary by script/signing.sh under a ckdev-*
# identifier (basal keeps its production identifiers) and place it
# in bin/. The rig's ck-basal is built
# with the rig-kill-hook feature, which the contract suite's
# crash case needs.
# config write subc.jsonc, bootstrap the key-file vault and grant only
# Broca's read of apikey:openai and routing's llm-provider list.
# Temporarily start the rig to read its served Fusiform catalog,
# then pin alfonso-routing.jsonc to openai/gpt-6-luna. Refuse a
# catalog without that model. No production config is copied.
# Luna's only score is a labeled rig fixture (elo 1, eq/speed 0),
# not measured quality; the stack manifest records that fact.
# credential ingest the isolated API key with provider_ids: ["openai"],
# delete the input file after deposit and confirm exactly one
# active credential. The operator saves it at
# ~/.local/share/cortexkit/ckdev-flows/home/.secrets/openai.key.
# start, status, stop
# run, inspect (pids, identifiers, open stores) and stop the
# daemon. status fails if any open store lies outside the rig.
# manifest write results/<timestamp>/stack.json: every repository's
# commit and each binary's sha256 and signing identifier.
# test start the rig if needed, write a manifest and run the live
# contract suite (basal-rig-contract) against the real
# prefrontal-core, with contract.json and contract.log beside
# the manifest. It fails if any check failed.
# Without --models, report the model cases as not run by name.
# --models requires that credential and agent-owned flows through
# core's relay (prefrontal 73c66ff1f or later): minimal call first,
# routing/journal, result/usage settlement, classify, token-cap
# refusal, crash recovery and no tools. Three tiny calls at most
# in normal execution; each manifest caps all its scheduled runs
# in a day, for an aggregate allowance of 4,112 tokens per suite
# (including the unscoped negative flow if enforcement regresses).
# Core registers each flow its own subc scope, and the scope's
# carrier list names the only principals the daemon lets open routes
# under it. Each send's Broca checkpoint must match core's
# flow_scope selector, attest reserved:basal as the first sender
# and freeze the flow_id. A direct client opening the same selector
# must be refused as a non-carrier. That proves an outsider can't
# use the scope; it doesn't read the carrier list, which no store
# exposes. That the list is exactly [reserved:basal] is tested in
# the prefrontal repository at cba528a11, in
# crates/prefrontal-core-module/src/scope_owner.rs:
# flow_scope_registration_is_basal_only,
# registered_flow_scope_wire_vector_pins_attributes_and_the_entire_carrier_list,
# registered_global_flow_scope_wire_vector_pins_no_agent_and_the_entire_carrier_list.
# Hook builds also send once without scope and require Broca's
# flow_scope_required refusal with no RunStarted or run_index row.
# Any first-call refusal is reported verbatim and stops the model
# cases; it is never retried with another provider or model.
#
# place --from-stage <dir> places ck-basal and ck-basal-worker from a stage
# directory script/stage.sh wrote, byte for byte and under their production
# identifiers, instead of the rig's own build of them; every other binary is
# the rig's build. The stage must be of the commit the rig built basal at.
# A staged ck-basal has neither rig-only switch, so test reports the crash and
# unscoped-send cases as not run; a plain place puts the rig's build back.
#
# What the rig isolates:
# - Everything lives under ~/.local/share/cortexkit/ckdev-flows/: src/ (one
# clone per repository at the commit built), build/ (cargo output and the
# build record), bin/ (the placed, signed binaries), config/, data/ and
# runtime/ (the rig's three XDG homes), home/ (HOME and working directory
# of every rig process), logs/ and results/<timestamp>/.
# - The daemon listens on port 8791; production's listens on 8757. config
# and start refuse if 8791 is taken.
# - Every rig process starts from an empty environment: the login name,
# HOME set to home/, the rig's XDG homes, SUBC_CONNECTION_FILE naming the
# rig's connection file, and a PATH with every CortexKit directory
# removed, so no production variable or file under the real home
# reaches a rig module.
# - The script refuses any path that resolves, before or after following
# symlinks, under ~/.local/share/cortexkit/ but outside ckdev-flows/.
# - The vault starts empty with its own key file, so the macOS keychain is
# never touched. Every auth command names the rig data, key and connection
# paths explicitly. Fusiform's store and HTTPS catalog polling are isolated
# too; no production auth-methods.json is ever copied into the rig.
#
# Model cases need core's agent-owned flow scope registration at approval:
# prefrontal 73c66ff1f or later, plus a basal build that opens scoped Broca
# routes. The scope checks require Broca's flow_scope_required enforcement;
# every refusal is checked, never assumed. Any scope_owner_mismatch must not
# be bypassed. Other repos use HEAD:
# script/flows-rig.sh build --prefrontal-rev 73c66ff1f --sibling-lock claustrum
set -eu
ROOT=$(cd "$(dirname "$0")/.." && pwd -P)
# The signing policy stage.sh signs production binaries with.
# shellcheck source=script/signing.sh
. "$ROOT/script/signing.sh"
WORKSPACE=${CORTEXKIT_WORKSPACE:-$HOME/Work/Projects/CortexKit}
CK_SHARE="$HOME/.local/share/cortexkit"
CK_CONFIG="$HOME/.config/cortexkit"
RIG="$CK_SHARE/ckdev-flows"
BIN="$RIG/bin"
SRC="$RIG/src"
TARGETS="$RIG/build/target"
STACK="$RIG/build/stack.tsv"
# Where the placed ck-basal and ck-basal-worker came from: the rig's build
# (with the kill switch) or a stage. `place` writes it; `test`, `status` and
# the manifest read it.
BASAL_SOURCE="$RIG/build/basal-source.tsv"
LOGS="$RIG/logs"
RESULTS="$RIG/results"
CONFIG_HOME="$RIG/config"
DATA_HOME="$RIG/data"
RUNTIME_DIR="$RIG/runtime"
# HOME for every rig process. A module that falls back to $HOME for any path
# (OpenCode's auth.json, a ~/.config file, a dot-directory) then lands inside
# the rig instead of reaching the user's real files.
RIG_HOME="$RIG/home"
CONN="$RUNTIME_DIR/subc-connection.json"
PIDFILE="$RUNTIME_DIR/ckdev-subc.pid"
SUBC_CONFIG="$CONFIG_HOME/cortexkit/subc.jsonc"
ROUTING_CONFIG="$CONFIG_HOME/cortexkit/alfonso-routing.jsonc"
BROCA_INDEX="$DATA_HOME/cortexkit/broca/run-index.db"
# The vault's data directory is where the daemon's storage convention puts
# module `claustrum`. Its master key must live outside that directory (the
# vault refuses a key beside its own store), so it sits in the config home.
VAULT_DIR="$DATA_HOME/cortexkit/claustrum"
VAULT_KEY="$CONFIG_HOME/claustrum/master.key"
# The stores the contract suite reads (read-only), and the file whose content
# names the runtime boundary at which the rig build of ck-basal kills itself
# once (crates/basal-module/src/rig_kill.rs); the suite writes it.
CORE_STORE="$DATA_HOME/cortexkit/prefrontal-core/store.db"
BASAL_STORE="$DATA_HOME/cortexkit/basal/store.db"
MACHINE_ID="$DATA_HOME/cortexkit/machine-id"
KILL_FILE="$RUNTIME_DIR/basal-kill-at"
UNSCOPED_FILE="$RUNTIME_DIR/basal-unscoped-send"
# The contract suite: a client of the rig, not a module, so it is run from
# the build output rather than placed in bin/.
CONTRACT="$TARGETS/basal/release/basal-rig-contract"
# Where the contract suite's projects live: one git repository per run, which
# the rig creates under its own home and registers in its entorhinal (see
# ensure_project).
PROJECTS="$RIG_HOME/projects"
# Clear of production's daemon (8757) and of the older isolation rig under
# ckdev-rig/ (8799, plus 8377 and 8378 for one of its modules).
PORT=8791
DRY=0
say() { printf '%s\n' "$*"; }
die() { printf 'flows-rig: %s\n' "$*" >&2; exit 1; }
usage() {
awk '/^# Usage:/ { on = 1 } /^set -eu/ { exit } on' "$0" | sed 's/^# \{0,1\}//' >&2
exit 2
}
# ---------------------------------------------------------------- guards
# The physical form of a path, following symlinks in every existing parent,
# so a symlink inside the rig cannot point a write at production.
physical() {
p=$1
rest=""
while [ ! -e "$p" ] && [ "$p" != "/" ]; do
rest="/$(basename "$p")$rest"
p=$(dirname "$p")
done
if [ -d "$p" ]; then
printf '%s%s\n' "$(cd -P "$p" && pwd -P)" "$rest"
else
printf '%s/%s%s\n' "$(cd -P "$(dirname "$p")" && pwd -P)" "$(basename "$p")" "$rest"
fi
}
# Refuse a path under ~/.local/share/cortexkit/ that is not inside the rig,
# both as written and after resolving symlinks.
guard_path() {
for form in "$1" "$(physical "$1")"; do
case "$form" in
"$RIG" | "$RIG"/* | "$RIG_PHYSICAL" | "$RIG_PHYSICAL"/*) ;;
"$CK_SHARE" | "$CK_SHARE"/* | "$CK_SHARE_PHYSICAL" | "$CK_SHARE_PHYSICAL"/*)
die "refusing: $1 resolves to $form, under $CK_SHARE but outside $RIG" ;;
esac
done
}
guard_all_paths() {
CK_SHARE_PHYSICAL=$(physical "$CK_SHARE")
RIG_PHYSICAL=$(physical "$RIG")
case "$RIG_PHYSICAL" in
"$CK_SHARE_PHYSICAL"/ckdev-flows) ;;
*) die "refusing: the rig root $RIG resolves to $RIG_PHYSICAL" ;;
esac
for path in "$BIN" "$SRC" "$TARGETS" "$STACK" "$BASAL_SOURCE" "$LOGS" "$RESULTS" \
"$CONFIG_HOME" "$DATA_HOME" "$RUNTIME_DIR" "$CONN" "$PIDFILE" \
"$SUBC_CONFIG" "$ROUTING_CONFIG" "$BROCA_INDEX" "$VAULT_DIR" "$VAULT_KEY" "$RIG_HOME" "$CORE_STORE" \
"$BASAL_STORE" "$MACHINE_ID" "$KILL_FILE" "$UNSCOPED_FILE" "$CONTRACT" "$PROJECTS"; do
guard_path "$path"
done
}
# The pid listening on the rig port, if any.
port_listener() {
lsof -nP -iTCP:"$PORT" -sTCP:LISTEN -t 2>/dev/null | head -n 1 || true
}
# For subcommands that would start a daemon: the port must be free.
guard_port_free() {
listener=$(port_listener)
[ -z "$listener" ] || die "refusing: port $PORT is already taken by pid $listener"
}
# For subcommands that talk to a running rig: the port is free or held by the
# rig's own daemon, never by anything else.
guard_port_ours() {
listener=$(port_listener)
[ -z "$listener" ] || [ "$listener" = "${1:-}" ] \
|| die "refusing: port $PORT is held by pid $listener, which is not the rig daemon"
}
# ---------------------------------------------------------------- helpers
run() {
if [ "$DRY" = 1 ]; then
say "+ $*"
else
"$@"
fi
}
# Run a command in a directory (cargo picks its toolchain file from there).
run_in() {
dir=$1
shift
if [ "$DRY" = 1 ]; then
say "+ (cd $dir) $*"
else
(cd "$dir" && "$@")
fi
}
# Write stdin to a file through a temp name and a rename.
write_file() {
dest=$1
guard_path "$dest"
if [ "$DRY" = 1 ]; then
say "+ write $dest:"
sed 's/^/| /'
return
fi
mkdir -p "$(dirname "$dest")"
tmp=$(mktemp "$(dirname "$dest")/.write.XXXXXX")
cat > "$tmp"
mv -f "$tmp" "$dest"
}
# PATH with every entry under the CortexKit data or config tree removed, so a
# rig process that runs a program by name can never reach a production binary.
rig_path() {
# The trailing newline matters: read drops a final line that lacks one.
printf '%s\n' "$PATH" | tr ':' '\n' | while IFS= read -r entry; do
case "$entry" in
"$CK_SHARE" | "$CK_SHARE"/* | "$CK_CONFIG" | "$CK_CONFIG"/* | "") ;;
*) printf '%s\n' "$entry" ;;
esac
done | paste -s -d: -
}
# Run a command in the rig's environment: an empty environment plus the rig's
# own HOME (also the working directory), its three XDG homes, its connection
# file, a temp dir inside its runtime dir, and the user's name. Nothing
# inherited from the caller can redirect a module at production state
# (CK_MASTER_KEY_PATH, BROCA_STATE_ROOT, SUBC_PORT, the real HOME, ...).
# rig_env_exec replaces the calling (sub)shell, so `(rig_env_exec cmd) &`
# leaves $! naming cmd itself.
rig_env() {
(rig_env_exec "$@")
}
rig_env_exec() {
cd "$RIG_HOME" || die "no rig home at $RIG_HOME; run start first"
# Close every descriptor above stderr first. Whatever launched this script
# (an agent's shell tool, a terminal multiplexer) may hold files open, and a
# daemon inheriting them would keep files outside the rig open for its whole
# life and pass them on to every module it spawns.
exec python3 -c 'import os, sys
os.closerange(3, min(os.sysconf("SC_OPEN_MAX"), 1 << 16))
os.execvp(sys.argv[1], sys.argv[1:])' \
env -i \
HOME="$RIG_HOME" USER="${USER:-}" LOGNAME="${LOGNAME:-}" LANG="${LANG:-en_US.UTF-8}" \
PATH="$(rig_path)" \
TMPDIR="$RUNTIME_DIR/tmp" \
XDG_CONFIG_HOME="$CONFIG_HOME" \
XDG_DATA_HOME="$DATA_HOME" \
XDG_RUNTIME_DIR="$RUNTIME_DIR" \
SUBC_CONNECTION_FILE="$CONN" \
"$@"
}
# `ck` against the rig. SUBC_CONNECTION_FILE is exclusive in ck's discovery
# (it never falls back to another daemon), and any `daemon: <path>` line ck
# prints must name the rig's connection file. ck prints that line only after
# a module mutation; for read verbs, check_daemon_identity compares pids.
rig_ck() {
out=$(rig_env "$BIN/ckdev-ck" "$@" 2>&1) || {
printf '%s\n' "$out"
die "ck $* failed"
}
printf '%s\n' "$out"
named=$(printf '%s\n' "$out" | sed -n 's/^daemon: \(\/.*\)$/\1/p')
if [ -n "$named" ] && [ "$named" != "$CONN" ]; then
die "ck $* answered from $named, not the rig's $CONN"
fi
}
# ck's auth face is linked only inside the rig. All three paths are explicit
# even for offline bootstrap, so neither CLI discovery nor a keychain fallback
# can ever select the operator's real vault.
rig_auth() {
# Follow final symlinks too: a key-file or connection-file symlink must not
# make explicit rig flags a disguised reference to production.
python3 - "$RIG_PHYSICAL" "$VAULT_DIR" "$VAULT_KEY" "$CONN" <<'PY' || die "auth paths escape the rig"
import os, sys
root = sys.argv[1] + os.sep
for path in sys.argv[2:]:
if not os.path.realpath(path).startswith(root):
sys.exit("flows-rig: auth path resolves outside the rig: " + path)
PY
run rig_env PATH="$BIN:$(rig_path)" "$BIN/ckdev-ck" auth "$@" \
--data-dir "$VAULT_DIR" --key-path "$VAULT_KEY" --subc "$CONN"
}
conn_pid() {
python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["pid"])' "$CONN" 2>/dev/null || true
}
identifier_of() {
codesign_identifier "$1"
}
# The placement mode `place` recorded: "rig-build" or "staged".
basal_mode() {
if [ -f "$BASAL_SOURCE" ]; then
cut -f1 "$BASAL_SOURCE"
else
printf 'rig-build\n'
fi
}
# The signing identifier a placed basal binary must carry: always its
# production one (ck-basal, ck-basal-worker). Only the rig's file names change
# to ckdev-, so the processes can't be mistaken for the production module;
# macOS and basal's own checks identify code by its signing identifier, which
# the file name doesn't affect.
basal_identifier() {
printf '%s\n' "$1"
}
# The repositories the rig builds from: name, source checkout, Cargo-built.
# The clones under $SRC keep these names because the repositories reach each
# other by relative path (../subconscious, ../commons, ../claustrum).
repos() {
cat <<EOF
subconscious $WORKSPACE/subconscious
commons $WORKSPACE/commons
broca $WORKSPACE/broca
claustrum $WORKSPACE/claustrum
entorhinal $WORKSPACE/entorhinal
fusiform $WORKSPACE/fusiform
prefrontal $WORKSPACE/prefrontal
basal $ROOT
EOF
}
# Every placed binary: rig name, repository, cargo binary, placed file name.
# Every placed file is named ckdev-*, including basal's two even when their
# bytes come from a production stage. That works because ck-basal looks for
# its worker beside itself as "<its own file name>-worker", so ckdev-basal
# finds ckdev-basal-worker.
binaries() {
cat <<'EOF'
subc subconscious ck-subc ckdev-subc
ck subconscious ck ckdev-ck
broca broca ck-broca ckdev-broca
claustrum claustrum ck-claustrum ckdev-claustrum
auth claustrum ck-auth ckdev-auth
entorhinal entorhinal ck-entorhinal ckdev-entorhinal
fusiform fusiform ck-fusiform ckdev-fusiform
models fusiform ck-models ckdev-models
prefrontal-core prefrontal ck-prefrontal-core ckdev-prefrontal-core
prefrontal-routing prefrontal ck-prefrontal-routing ckdev-prefrontal-routing
basal basal ck-basal ckdev-basal
basal-worker basal ck-basal-worker ckdev-basal-worker
callosum basal ck-callosum-stub ckdev-callosum
EOF
}
# The supervised modules: subc module id and placed file name.
modules() {
cat <<'EOF'
claustrum ckdev-claustrum
broca ckdev-broca
fusiform ckdev-fusiform
entorhinal ckdev-entorhinal
prefrontal-core ckdev-prefrontal-core
prefrontal-routing ckdev-prefrontal-routing
basal ckdev-basal
callosum ckdev-callosum
EOF
}
all_placed() {
for file in $(binaries | cut -f4); do
[ -f "$BIN/$file" ] || return 1
done
}
daemon_pid() {
[ -f "$PIDFILE" ] || return 0
pid=$(cat "$PIDFILE")
if kill -0 "$pid" 2>/dev/null; then
printf '%s\n' "$pid"
fi
}
# ---------------------------------------------------------------- build
cmd_build() {
prefrontal_rev=""
subc_rev=HEAD
broca_rev=HEAD
credentials_rev=HEAD
commons_rev=HEAD
entorhinal_rev=HEAD
fusiform_rev=HEAD
sibling_lock=""
while [ $# -gt 0 ]; do
case "$1" in
--sibling-lock) [ $# -ge 2 ] || usage; sibling_lock="$sibling_lock $2"; shift 2 ;;
--prefrontal-rev) [ $# -ge 2 ] || usage; prefrontal_rev=$2; shift 2 ;;
--subc-rev) [ $# -ge 2 ] || usage; subc_rev=$2; shift 2 ;;
--broca-rev) [ $# -ge 2 ] || usage; broca_rev=$2; shift 2 ;;
--credentials-rev) [ $# -ge 2 ] || usage; credentials_rev=$2; shift 2 ;;
--commons-rev) [ $# -ge 2 ] || usage; commons_rev=$2; shift 2 ;;
--entorhinal-rev) [ $# -ge 2 ] || usage; entorhinal_rev=$2; shift 2 ;;
--fusiform-rev) [ $# -ge 2 ] || usage; fusiform_rev=$2; shift 2 ;;
*) usage ;;
esac
done
# prefrontal has no default: every rig result must name the core and
# routing it tested, and a silent HEAD would name whatever happened to be
# checked out.
[ -n "$prefrontal_rev" ] || die "build needs --prefrontal-rev <rev>; it has no default"
if [ -n "$(git -C "$ROOT" status --porcelain --untracked-files=no)" ]; then
say "note: $ROOT has uncommitted changes; the rig builds its HEAD commit only"
fi
# Forget the previous build before starting this one. The record is written
# again only when every crate has built, so after a failed build `place`
# refuses instead of placing a mix of new and old binaries.
run rm -f "$STACK"
stack=""
for line in $(repos | tr '\t' '|'); do
name=${line%%|*}
source=${line#*|}
case "$name" in
subconscious) rev=$subc_rev ;;
commons) rev=$commons_rev ;;
broca) rev=$broca_rev ;;
claustrum) rev=$credentials_rev ;;
entorhinal) rev=$entorhinal_rev ;;
fusiform) rev=$fusiform_rev ;;
prefrontal) rev=$prefrontal_rev ;;
basal) rev=HEAD ;;
esac
[ -d "$source" ] || die "no checkout of $name at $source"
sha=$(git -C "$source" rev-parse --verify --quiet "$rev^{commit}") \
|| die "$name: cannot resolve $rev in $source"
say "$name: $rev -> $sha ($source)"
clone_at "$name" "$source" "$sha"
stack="$stack$name $source $rev $sha
"
done
cargo_build subconscious "" -p subc-core --bin ck-subc --bin ck
cargo_build broca "" -p broca-module-serve --bin ck-broca
cargo_build claustrum "" -p credentials-module --bin ck-claustrum --bin ck-auth
cargo_build entorhinal "" -p entorhinal-module --bin ck-entorhinal
cargo_build fusiform "" -p fusiform-module --bin ck-fusiform
cargo_build fusiform "" -p fusiform-cli --bin ck-models
prefrontal_sha=$(printf '%s' "$stack" | awk -F'\t' '$1=="prefrontal"{print $4}')
# prefrontal's build scripts embed the revision they were told; the clone
# is at that exact commit with no local changes, so it is not dirty.
cargo_build prefrontal "CK_BUILD_GIT_SHA=$prefrontal_sha CK_BUILD_GIT_DIRTY=false" \
-p prefrontal-core-module --bin ck-prefrontal-core
cargo_build prefrontal "CK_BUILD_GIT_SHA=$prefrontal_sha CK_BUILD_GIT_DIRTY=false" \
-p prefrontal-routing-module --bin ck-prefrontal-routing
# The rig's ck-basal carries the one-shot kill switch the contract suite's
# crash case arms; a production build never enables this feature.
# basal's binaries embed their revision too, as script/stage.sh builds them.
basal_sha=$(printf '%s' "$stack" | awk -F'\t' '$1=="basal"{print $4}')
basal_env="CK_BUILD_GIT_SHA=$basal_sha CK_BUILD_GIT_DIRTY=false"
cargo_build basal "$basal_env" -p basal-module --features rig-kill-hook --bin ck-basal
cargo_build basal "$basal_env" -p basal-worker --bin ck-basal-worker
cargo_build basal "" -p basal-rig --bin ck-callosum-stub --bin basal-rig-contract
# A build that rewrote a tracked file (a Cargo.lock refreshed against a
# sibling at another revision) did not build exactly the named commit.
# The one exception is a repository named with --sibling-lock: its
# Cargo.lock may change, but only the versions of path dependencies on a
# sibling clone. That happens when the repository was built in production
# against a sibling checkout at an earlier revision than the rig's. The
# change is saved beside the clone, recorded in the stack, and undone.
locks=""
for line in $(repos | tr '\t' '|'); do
name=${line%%|*}
[ "$DRY" = 0 ] || continue
changed=$(git -C "$SRC/$name" status --porcelain --untracked-files=no)
[ -n "$changed" ] || continue
case " $sibling_lock " in
*" $name "*)
if [ "$changed" = " M Cargo.lock" ] && sibling_versions_only "$SRC/$name"; then
git -C "$SRC/$name" diff Cargo.lock > "$SRC/$name.sibling-lock.diff"
git -C "$SRC/$name" checkout --quiet -- Cargo.lock
say "$name: Cargo.lock refreshed against sibling path dependencies; diff in $SRC/$name.sibling-lock.diff"
locks="$locks $name"
continue
fi
;;
esac
git -C "$SRC/$name" status --short --untracked-files=no >&2
die "$name: the build changed tracked files in $SRC/$name"
done
stack=$(printf '%s' "$stack" | while IFS=' ' read -r n s r c; do
# Leading-paren patterns: /bin/sh's bash 3.2 misparses a bare pattern's
# closing paren inside a command substitution.
case " $locks " in (*" $n "*) l=sibling-refreshed ;; (*) l=committed ;; esac
printf '%s\t%s\t%s\t%s\t%s\n' "$n" "$s" "$r" "$c" "$l"
done)
printf 'repo\tsource\trequested\tcommit\tcargo_lock\n%s\n' "$stack" | write_file "$STACK"
say "built; next: $0 place"
}
# sibling_versions_only <clone>: succeed if the clone's Cargo.lock differs from
# its commit only in the version of packages that have no `source`, which are
# path dependencies, and adds or removes no package.
sibling_versions_only() {
git -C "$1" show HEAD:Cargo.lock > "$1.committed-lock"
status=0
python3 - "$1.committed-lock" "$1/Cargo.lock" <<'PY' || status=$?
import sys
def blocks(path):
out = {}
for chunk in open(path).read().split("[[package]]")[1:]:
fields = dict(
line.split(" = ", 1) for line in chunk.strip().splitlines()
if " = " in line and not line.startswith(" ")
)
out[(fields["name"], fields.get("source"))] = (fields, chunk)
return out
old = blocks(sys.argv[1])
new = blocks(sys.argv[2])
if old.keys() != new.keys():
sys.exit(1)
for key, (fields, chunk) in new.items():
if chunk == old[key][1]:
continue
if key[1] is not None:
sys.exit(1)
if chunk.replace(fields["version"], old[key][0]["version"], 1) != old[key][1]:
sys.exit(1)
PY
rm -f "$1.committed-lock"
return "$status"
}
# Bring $SRC/<name> to exactly <sha> without touching the source checkout:
# a separate clone, detached at the commit, with no local changes.
clone_at() {
name=$1
source=$2
sha=$3
dest="$SRC/$name"
guard_path "$dest"
if [ -d "$dest/.git" ]; then
# The clone is the rig's own; origin only says where to fetch from, and
# basal's source moves between worktrees.
run git -C "$dest" remote set-url origin "$source"
run git -C "$dest" fetch --quiet origin
else
run mkdir -p "$SRC"
run git clone --quiet --no-checkout "$source" "$dest"
fi
if [ "$DRY" = 0 ] && ! git -C "$dest" cat-file -e "$sha^{commit}" 2>/dev/null; then
git -C "$dest" fetch --quiet origin "$sha"
fi
run git -C "$dest" checkout --quiet --force --detach "$sha"
if [ "$DRY" = 0 ] && [ -n "$(git -C "$dest" status --porcelain --untracked-files=no)" ]; then
die "$dest has local changes after checkout"
fi
}
# cargo_build <repo> "<VAR=value ...>" <cargo args>: a release build in the
# clone, into a target directory of the rig's own.
cargo_build() {
repo=$1
extra_env=$2
shift 2
# shellcheck disable=SC2086 # extra_env is a list of VAR=value words
run_in "$SRC/$repo" env CARGO_TARGET_DIR="$TARGETS/$repo" $extra_env \
cargo build --release "$@"
}
# ---------------------------------------------------------------- place
cmd_place() {
stage=""
while [ $# -gt 0 ]; do
case "$1" in
--from-stage) [ $# -ge 2 ] || usage; stage=$2; shift 2 ;;
*) usage ;;
esac
done
[ -z "$(daemon_pid)" ] || die "refusing: the rig daemon is running; stop it first"
[ "$DRY" = 1 ] || [ -f "$STACK" ] || die "nothing built yet; run build first"
revision=""
if [ -n "$stage" ]; then
stage=$(cd "$stage" 2>/dev/null && pwd -P) || die "no stage directory at $stage"
# stage.sh's own check of a stage: both sidecars and both signatures,
# under the production identifiers the fleet's placement would use.
verified=$(sh "$ROOT/script/stage.sh" --verify "$stage") \
|| die "the stage at $stage fails stage.sh --verify"
revision=$(printf '%s\n' "$verified" | sed -n 's/^revision //p')
if [ "$DRY" = 0 ]; then
built=$(awk -F'\t' '$1=="basal"{print $4}' "$STACK")
# The callosum stub, the contract suite and the worker's gate script
# come from the rig's build, so it must be of the staged commit.
[ "$revision" = "$built" ] \
|| die "the stage is of $revision but the rig built basal at $built; build the rig at the staged commit first"
fi
fi
run mkdir -p "$BIN"
for line in $(binaries | tr '\t' '|'); do
name=$(printf '%s' "$line" | cut -d'|' -f1)
repo=$(printf '%s' "$line" | cut -d'|' -f2)
cargo_bin=$(printf '%s' "$line" | cut -d'|' -f3)
file=$(printf '%s' "$line" | cut -d'|' -f4)
case "$name" in
basal | basal-worker)
if [ -n "$stage" ]; then
place_staged "$name" "$stage/$cargo_bin" "$BIN/$file" "$cargo_bin"
continue
fi ;;
esac
place_one "$name" "$TARGETS/$repo/release/$cargo_bin" "$BIN/$file"
done
run ln -sf ckdev-auth "$BIN/ck-auth"
if [ -n "$stage" ]; then
printf 'staged\t%s\t%s\n' "$stage" "$revision" | write_file "$BASAL_SOURCE"
else
printf 'rig-build\n' | write_file "$BASAL_SOURCE"
fi
worker_identifier=ck-basal-worker
# The worker runs flow code, so it must also pass basal's own gate at its
# final path: hardened runtime, no entitlements, the checked-in Seatbelt
# profile embedded, and a live probe that confinement denies a file read,
# a socket connect and a program launch. The gate script comes from the
# basal clone, so it checks against the profile of the commit that was built.
worker="$BIN/ckdev-basal-worker"
if [ "$DRY" = 1 ]; then
say "+ BASAL_WORKER_IDENTIFIER=$worker_identifier sh $SRC/basal/script/sign-worker.sh verify $worker"
elif ! BASAL_WORKER_IDENTIFIER=$worker_identifier sh "$SRC/basal/script/sign-worker.sh" verify "$worker"; then
rm -f "$worker"
die "the placed worker failed basal's confinement gate and was removed"
fi
say "placed; next: $0 config"
}
# Sign a copy under a temp name with an explicit identifier (codesign would
# otherwise derive one from the temp name), check it, rename it into place,
# and check the final file again.
place_one() {
name=$1
built=$2
dest=$3
identifier="ckdev-$name"
case "$name" in
basal | basal-worker) identifier="ck-$name" ;;
esac
guard_path "$dest"
if [ "$DRY" = 1 ]; then
say "+ cp $built $BIN/.place.XXXXXX; chmod 0755 $BIN/.place.XXXXXX"
say "+ sign_hardened $BIN/.place.XXXXXX $identifier (script/signing.sh)"
say "+ verify_hardened $BIN/.place.XXXXXX $identifier"
say "+ mv -f $BIN/.place.XXXXXX $dest"
say "+ verify_hardened $dest $identifier"
return
fi
[ -f "$built" ] || die "$name: no built binary at $built"
tmp=$(mktemp "$BIN/.place.XXXXXX")
cp "$built" "$tmp"
# mktemp creates the file 0600 and cp keeps that mode on an existing file.
chmod 0755 "$tmp"
# The production signing policy: production modules run with the hardened
# runtime, and basal's worker gate refuses a worker without it, so the rig
# matches both.
sign_hardened "$tmp" "$identifier" 2>/dev/null
if ! verify_hardened "$tmp" "$identifier"; then
rm -f "$tmp"
die "$name: the signed copy fails the signing policy"
fi
mv -f "$tmp" "$dest"
verify_hardened "$dest" "$identifier" || die "$name: $dest fails the signing policy"
say "placed $dest ($identifier)"
}
# place_staged <name> <staged file> <dest> <identifier>: copy a staged binary
# into the rig unchanged (never re-signed: the rig runs the bytes production
# placement would install), and check at the final path that the bytes match the stage's
# sidecar and that the signature passes under its production identifier.
place_staged() {
name=$1
staged=$2
dest=$3
identifier=$4
guard_path "$dest"
if [ "$DRY" = 1 ]; then
say "+ cp $staged $BIN/.place.XXXXXX; chmod 0755 $BIN/.place.XXXXXX; mv -f $BIN/.place.XXXXXX $dest"
say "+ sha256 of $dest must equal $staged.sha256; verify_hardened $dest $identifier"
return
fi
tmp=$(mktemp "$BIN/.place.XXXXXX")
cp "$staged" "$tmp"
chmod 0755 "$tmp"
mv -f "$tmp" "$dest"
want=$(awk '{ print $1 }' "$staged.sha256")
got=$(shasum -a 256 "$dest" | awk '{ print $1 }')
[ "$got" = "$want" ] || die "$name: $dest is $got, but the stage's sidecar says $want"
verify_hardened "$dest" "$identifier" || die "$name: $dest fails the signing policy"
say "placed $dest from the stage ($identifier, sha256 $got)"
}
# ---------------------------------------------------------------- config
cmd_config() {
[ $# -eq 0 ] || usage
guard_port_free
[ -z "$(daemon_pid)" ] || die "refusing: the rig daemon is running; stop it first"
# From scratch: the whole file is generated here, never merged with a
# previous one. ck-bus and nats-server are left out (the bus needs its own
# NATS server, operator JWT and vault signing); prefrontal-core then
# reports "bus: connecting" in its health, which is expected on this rig.
write_file "$SUBC_CONFIG" <<EOF
{
// ckdev-flows: basal's isolated rig, written by script/flows-rig.sh config.
// Regenerate it with that command rather than editing it by hand.
"version": 1,
"port": $PORT,
"storage": {
"backend": "sqlite",
// Every module's store is <data_home>/cortexkit/<module id>/store.db.
"data_home": "$DATA_HOME"
},
"modules": {
// The credentials vault, keyed by a master key file of the rig's
// own (never the macOS keychain, never a production key).
"claustrum": {
"program": "$BIN/ckdev-claustrum",
"args": [],
"env": { "CK_MASTER_KEY_PATH": "$VAULT_KEY" },
"enabled": true,
"reserved": true,
"launch_nonce_env": false
},
"broca": {
"program": "$BIN/ckdev-broca",
"args": [],
"env": { "BROCA_STATE_ROOT": "$DATA_HOME/cortexkit/broca" },
"enabled": true,
"reserved": true,
"launch_nonce_env": false
},
// The daemon supplies data/cortexkit/fusiform/store.db to this module.
"fusiform": {
"program": "$BIN/ckdev-fusiform",
"args": [],
"env": {},
"enabled": true
},
// The project-identity/v1 provider. prefrontal-core declares that
// capability required, and the daemon refuses every route to core until
// a provider has registered. Its store is data/cortexkit/entorhinal/.
"entorhinal": {
"program": "$BIN/ckdev-entorhinal",
"args": [],
"env": {},
"enabled": true
},
// Core runs its projects registry consumer against the rig's entorhinal,
// as in production.
// Never set PREFRONTAL_CORE_DIAGNOSTICS here: it opens core's test seams,
// which exist for prefrontal's end-to-end harness only.
"prefrontal-core": {
"program": "$BIN/ckdev-prefrontal-core",
"args": [],
"env": {},
"enabled": true,
"reserved": true,
"launch_nonce_env": false
},
"prefrontal-routing": {
"program": "$BIN/ckdev-prefrontal-routing",
"args": [],
"env": {},
"enabled": true,
"reserved": true,
"launch_nonce_env": false
},
// Reserved, so its routes carry the principal reserved:basal. The rig's
// build reads the two arming files for crash and unscoped-send checks;
// production ck-basal has neither switch and ignores both variables.
"basal": {
"program": "$BIN/ckdev-basal",
"args": [],
"env": { "BASAL_RIG_KILL_FILE": "$KILL_FILE", "BASAL_RIG_UNSCOPED_FILE": "$UNSCOPED_FILE" },
"enabled": true,
"reserved": true
},
// Rig only, never in a production config: a stub under the reserved id
// callosum, which core and basal both treat as the operator. It answers
// the contract suite's consent cards (crates/basal-rig).
"callosum": {
"program": "$BIN/ckdev-callosum",
"args": [],
"env": {},
"enabled": true,
"reserved": true
}
}
}
EOF
run mkdir -p -m 700 "$RIG_HOME" "$RUNTIME_DIR/tmp" "$(dirname "$VAULT_KEY")"
rig_auth bootstrap
if [ "$DRY" = 1 ]; then
say "+ start the temporary rig for authenticated vault grants and its served catalog"
else
cmd_start
fi
# --subc requires a live connection file for grants. Bootstrap is the one
# offline command; grant writes go through the running vault's admin route.
config_status=0
configure_live || config_status=$?
if [ "$DRY" = 1 ]; then
say "+ stop the temporary rig"
else
# Stop even when an authenticated grant or catalog read was refused.
cmd_stop
fi
[ "$config_status" = 0 ] || die "cannot configure the rig's vault grants and served Luna pin"
say "configured; next: $0 start"
}
configure_live() {
# Grants are vault records, not subc JSON fields. The CLI refuses duplicate
# grants, so read their metadata and install only the missing authorities.
# An unexpected existing grant is a configuration error, never silently kept.
if [ "$DRY" = 1 ]; then
rig_auth grants
say "+ grant each missing authority; refuse any other existing grant"
rig_auth grant --principal reserved:broca --selector-kind exact --selector apikey:openai --operation read
rig_auth grant --principal reserved:prefrontal-routing --selector-kind category --selector llm-provider --operation list
else
grant_rows=$(rig_auth grants) || return 1
missing=$(printf '%s\n' "$grant_rows" | missing_grants) || return 1
for row in $missing; do
principal=${row%%|*}; row=${row#*|}
kind=${row%%|*}; row=${row#*|}
selector=${row%%|*}; operation=${row#*|}
rig_auth grant --principal "reserved:$principal" --selector-kind "$kind" \
--selector "$selector" --operation "$operation" || return 1
done
fi
if [ "$DRY" = 1 ]; then
say "+ read the served catalog: ckdev-models get --subc $CONN --json"
say "+ write $ROUTING_CONFIG: model_routing.exclude = [every served provider, -openai/gpt-6-luna]"
say "+ fail if openai/gpt-6-luna is absent"
else
pin_routing
fi
}
missing_grants() {
python3 -c 'import sys
lines = sys.stdin.read().splitlines()
allowed = {("reserved", "broca", "exact", "apikey:openai", "read"),
("reserved", "prefrontal-routing", "category", "llm-provider", "list")}
if lines == ["no grants"]:
existing = set()
elif lines and lines[0].startswith("KIND "):
existing = {tuple(line.split()[:5]) for line in lines[1:] if line.strip()}
else:
sys.exit("flows-rig: missing grant inventory header")
if not existing <= allowed:
sys.exit("flows-rig: rig vault has unexpected grants; inspect with explicit rig auth flags")
for row in sorted(allowed - existing):
print("|".join(row[1:]))'
}
pin_routing() {
catalog=$(rig_env "$BIN/ckdev-models" get --subc "$CONN" --json) || return 1
policy=$(printf '%s\n' "$catalog" | python3 -c 'import json, sys
catalog = json.load(sys.stdin)
models = catalog["models"]
if "openai/gpt-6-luna" not in models:
sys.exit("flows-rig: served Fusiform catalog lacks openai/gpt-6-luna")
providers = sorted({key.split("/", 1)[0] for key in models})
policy = {"model_routing": {"exclude": providers + ["-openai/gpt-6-luna"],
"models": {"openai/gpt-6-luna": {"elo": 1, "eq": 0, "speed": 0}}}}
text = json.dumps(policy, indent=2)
entry = " \"openai/gpt-6-luna\": {"
comment = (" // Rig fixture values, not measured model quality. An empty routing store\n"
" // discovers Luna as unscored and never selects it. As the only eligible\n"
" // model, elo=1 clears iq=20; no EQ or speed score is demanded.\n")
print(text.replace(entry, comment + entry))') || return 1
printf '%s\n' "$policy" | write_file "$ROUTING_CONFIG"
}
# ---------------------------------------------------------------- credential
cmd_credential() {
[ $# -eq 2 ] && [ "$1" = --key-file ] || usage
key_file=$2
# realpath also follows the final component: never ingest or delete a
# production file through a symlink in the rig's secrets directory.
key_file=$(python3 -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$key_file")
case "$key_file" in
"$RIG_PHYSICAL"/*) ;;
*) die "the payload file must be inside $RIG" ;;
esac
if [ "$DRY" = 1 ]; then
rig_auth put --id apikey:openai --provider-id openai --payload-file "$key_file"
run rm -f "$key_file"
rig_auth status
say "+ verify exactly one credential: active apikey:openai, provider_ids [openai]"
return
fi
[ -f "$key_file" ] || die "no input key file at $key_file"
pid=$(daemon_pid)
[ -n "$pid" ] || die "start the rig before depositing its credential"
check_daemon_identity "$pid"
rig_auth put --id apikey:openai --provider-id openai --payload-file "$key_file"
rm -f "$key_file"
rig_auth status
require_model_credential
}
require_model_credential() {
listing=$(rig_auth list) || { say "cannot read the rig credential inventory" >&2; return 1; }
printf '%s\n' "$listing" | python3 -c 'import sys
lines = sys.stdin.read().splitlines()
start = next((i for i, line in enumerate(lines) if line.startswith("STATE ")), None)
if start is None:
sys.exit("flows-rig: missing credential inventory header")
rows = []
for line in lines[start+1:]:
if not line.strip(): break
rows.append(line.split())