aether-colony 5.3.1 → 5.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aether/aether-utils.sh +181 -5
- package/.aether/commands/build.yaml +35 -0
- package/.aether/commands/entomb.yaml +1 -1
- package/.aether/commands/init.yaml +29 -12
- package/.aether/commands/oracle.yaml +70 -0
- package/.aether/commands/patrol.yaml +2 -2
- package/.aether/commands/run.yaml +3 -3
- package/.aether/commands/swarm.yaml +1 -1
- package/.aether/docs/command-playbooks/build-complete.md +41 -8
- package/.aether/docs/command-playbooks/build-full.md +7 -7
- package/.aether/docs/command-playbooks/build-prep.md +1 -1
- package/.aether/docs/command-playbooks/continue-advance.md +33 -0
- package/.aether/docs/command-playbooks/continue-finalize.md +15 -1
- package/.aether/docs/command-playbooks/continue-full.md +15 -1
- package/.aether/docs/source-of-truth-map.md +10 -10
- package/.aether/docs/structural-learning-stack.md +283 -0
- package/.aether/utils/consolidation-seal.sh +196 -0
- package/.aether/utils/consolidation.sh +127 -0
- package/.aether/utils/curation-ants/archivist.sh +97 -0
- package/.aether/utils/curation-ants/critic.sh +214 -0
- package/.aether/utils/curation-ants/herald.sh +102 -0
- package/.aether/utils/curation-ants/janitor.sh +121 -0
- package/.aether/utils/curation-ants/librarian.sh +99 -0
- package/.aether/utils/curation-ants/nurse.sh +153 -0
- package/.aether/utils/curation-ants/orchestrator.sh +181 -0
- package/.aether/utils/curation-ants/scribe.sh +164 -0
- package/.aether/utils/curation-ants/sentinel.sh +119 -0
- package/.aether/utils/event-bus.sh +301 -0
- package/.aether/utils/graph.sh +559 -0
- package/.aether/utils/instinct-store.sh +401 -0
- package/.aether/utils/learning.sh +79 -7
- package/.aether/utils/session.sh +13 -0
- package/.aether/utils/state-api.sh +1 -1
- package/.aether/utils/trust-scoring.sh +347 -0
- package/.aether/utils/worktree.sh +97 -0
- package/.claude/commands/ant/entomb.md +1 -1
- package/.claude/commands/ant/init.md +29 -12
- package/.claude/commands/ant/oracle.md +35 -0
- package/.claude/commands/ant/patrol.md +2 -2
- package/.claude/commands/ant/run.md +3 -3
- package/.claude/commands/ant/swarm.md +1 -1
- package/.opencode/commands/ant/build.md +35 -0
- package/.opencode/commands/ant/init.md +29 -12
- package/.opencode/commands/ant/oracle.md +35 -0
- package/.opencode/commands/ant/patrol.md +2 -2
- package/.opencode/commands/ant/run.md +3 -3
- package/CHANGELOG.md +83 -0
- package/README.md +34 -37
- package/bin/lib/update-transaction.js +8 -3
- package/bin/npx-entry.js +0 -0
- package/package.json +1 -1
- package/.aether/agents/aether-ambassador.md +0 -140
- package/.aether/agents/aether-archaeologist.md +0 -108
- package/.aether/agents/aether-architect.md +0 -133
- package/.aether/agents/aether-auditor.md +0 -144
- package/.aether/agents/aether-builder.md +0 -184
- package/.aether/agents/aether-chaos.md +0 -115
- package/.aether/agents/aether-chronicler.md +0 -122
- package/.aether/agents/aether-gatekeeper.md +0 -116
- package/.aether/agents/aether-includer.md +0 -117
- package/.aether/agents/aether-keeper.md +0 -177
- package/.aether/agents/aether-measurer.md +0 -128
- package/.aether/agents/aether-oracle.md +0 -137
- package/.aether/agents/aether-probe.md +0 -133
- package/.aether/agents/aether-queen.md +0 -286
- package/.aether/agents/aether-route-setter.md +0 -130
- package/.aether/agents/aether-sage.md +0 -106
- package/.aether/agents/aether-scout.md +0 -101
- package/.aether/agents/aether-surveyor-disciplines.md +0 -391
- package/.aether/agents/aether-surveyor-nest.md +0 -329
- package/.aether/agents/aether-surveyor-pathogens.md +0 -264
- package/.aether/agents/aether-surveyor-provisions.md +0 -334
- package/.aether/agents/aether-tracker.md +0 -137
- package/.aether/agents/aether-watcher.md +0 -174
- package/.aether/agents/aether-weaver.md +0 -130
- package/.aether/commands/claude/archaeology.md +0 -334
- package/.aether/commands/claude/build.md +0 -65
- package/.aether/commands/claude/chaos.md +0 -336
- package/.aether/commands/claude/colonize.md +0 -259
- package/.aether/commands/claude/continue.md +0 -60
- package/.aether/commands/claude/council.md +0 -507
- package/.aether/commands/claude/data-clean.md +0 -81
- package/.aether/commands/claude/dream.md +0 -268
- package/.aether/commands/claude/entomb.md +0 -498
- package/.aether/commands/claude/export-signals.md +0 -57
- package/.aether/commands/claude/feedback.md +0 -96
- package/.aether/commands/claude/flag.md +0 -151
- package/.aether/commands/claude/flags.md +0 -169
- package/.aether/commands/claude/focus.md +0 -76
- package/.aether/commands/claude/help.md +0 -154
- package/.aether/commands/claude/history.md +0 -140
- package/.aether/commands/claude/import-signals.md +0 -71
- package/.aether/commands/claude/init.md +0 -505
- package/.aether/commands/claude/insert-phase.md +0 -105
- package/.aether/commands/claude/interpret.md +0 -278
- package/.aether/commands/claude/lay-eggs.md +0 -210
- package/.aether/commands/claude/maturity.md +0 -113
- package/.aether/commands/claude/memory-details.md +0 -77
- package/.aether/commands/claude/migrate-state.md +0 -171
- package/.aether/commands/claude/oracle.md +0 -642
- package/.aether/commands/claude/organize.md +0 -232
- package/.aether/commands/claude/patrol.md +0 -620
- package/.aether/commands/claude/pause-colony.md +0 -233
- package/.aether/commands/claude/phase.md +0 -115
- package/.aether/commands/claude/pheromones.md +0 -156
- package/.aether/commands/claude/plan.md +0 -693
- package/.aether/commands/claude/preferences.md +0 -65
- package/.aether/commands/claude/quick.md +0 -100
- package/.aether/commands/claude/redirect.md +0 -76
- package/.aether/commands/claude/resume-colony.md +0 -197
- package/.aether/commands/claude/resume.md +0 -388
- package/.aether/commands/claude/run.md +0 -231
- package/.aether/commands/claude/seal.md +0 -774
- package/.aether/commands/claude/skill-create.md +0 -286
- package/.aether/commands/claude/status.md +0 -410
- package/.aether/commands/claude/swarm.md +0 -349
- package/.aether/commands/claude/tunnels.md +0 -426
- package/.aether/commands/claude/update.md +0 -132
- package/.aether/commands/claude/verify-castes.md +0 -143
- package/.aether/commands/claude/watch.md +0 -239
- package/.aether/commands/opencode/archaeology.md +0 -331
- package/.aether/commands/opencode/build.md +0 -1168
- package/.aether/commands/opencode/chaos.md +0 -329
- package/.aether/commands/opencode/colonize.md +0 -195
- package/.aether/commands/opencode/continue.md +0 -1436
- package/.aether/commands/opencode/council.md +0 -437
- package/.aether/commands/opencode/data-clean.md +0 -77
- package/.aether/commands/opencode/dream.md +0 -260
- package/.aether/commands/opencode/entomb.md +0 -377
- package/.aether/commands/opencode/export-signals.md +0 -54
- package/.aether/commands/opencode/feedback.md +0 -99
- package/.aether/commands/opencode/flag.md +0 -149
- package/.aether/commands/opencode/flags.md +0 -167
- package/.aether/commands/opencode/focus.md +0 -73
- package/.aether/commands/opencode/help.md +0 -157
- package/.aether/commands/opencode/history.md +0 -136
- package/.aether/commands/opencode/import-signals.md +0 -68
- package/.aether/commands/opencode/init.md +0 -518
- package/.aether/commands/opencode/insert-phase.md +0 -111
- package/.aether/commands/opencode/interpret.md +0 -272
- package/.aether/commands/opencode/lay-eggs.md +0 -213
- package/.aether/commands/opencode/maturity.md +0 -108
- package/.aether/commands/opencode/memory-details.md +0 -83
- package/.aether/commands/opencode/migrate-state.md +0 -165
- package/.aether/commands/opencode/oracle.md +0 -593
- package/.aether/commands/opencode/organize.md +0 -226
- package/.aether/commands/opencode/patrol.md +0 -626
- package/.aether/commands/opencode/pause-colony.md +0 -203
- package/.aether/commands/opencode/phase.md +0 -113
- package/.aether/commands/opencode/pheromones.md +0 -162
- package/.aether/commands/opencode/plan.md +0 -684
- package/.aether/commands/opencode/preferences.md +0 -71
- package/.aether/commands/opencode/quick.md +0 -91
- package/.aether/commands/opencode/redirect.md +0 -84
- package/.aether/commands/opencode/resume-colony.md +0 -190
- package/.aether/commands/opencode/resume.md +0 -394
- package/.aether/commands/opencode/run.md +0 -237
- package/.aether/commands/opencode/seal.md +0 -452
- package/.aether/commands/opencode/skill-create.md +0 -63
- package/.aether/commands/opencode/status.md +0 -307
- package/.aether/commands/opencode/swarm.md +0 -15
- package/.aether/commands/opencode/tunnels.md +0 -400
- package/.aether/commands/opencode/update.md +0 -127
- package/.aether/commands/opencode/verify-castes.md +0 -139
- package/.aether/commands/opencode/watch.md +0 -227
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Curation Sentinel — Memory Health Monitoring
|
|
3
|
+
# Checks health of all memory stores and reports issues.
|
|
4
|
+
#
|
|
5
|
+
# Functions:
|
|
6
|
+
# _curation_sentinel
|
|
7
|
+
#
|
|
8
|
+
# These functions are sourced by aether-utils.sh at startup.
|
|
9
|
+
# All shared infrastructure (json_ok, json_err, COLONY_DATA_DIR, DATA_DIR,
|
|
10
|
+
# error constants) is available when sourced.
|
|
11
|
+
|
|
12
|
+
# ============================================================================
|
|
13
|
+
# _curation_sentinel
|
|
14
|
+
# Check health of all memory stores.
|
|
15
|
+
# Usage: curation-sentinel
|
|
16
|
+
#
|
|
17
|
+
# Output: json_ok with {checks:[{store,status,details}], healthy:N, issues:N}
|
|
18
|
+
# ============================================================================
|
|
19
|
+
_curation_sentinel() {
|
|
20
|
+
local cs_data_dir="${COLONY_DATA_DIR:-${DATA_DIR:-}}"
|
|
21
|
+
if [[ -z "$cs_data_dir" ]]; then
|
|
22
|
+
json_err "$E_VALIDATION_FAILED" "curation-sentinel: COLONY_DATA_DIR is not set"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
local cs_checks_json="[]"
|
|
26
|
+
local cs_healthy=0
|
|
27
|
+
local cs_issues=0
|
|
28
|
+
|
|
29
|
+
# Helper: append a check entry
|
|
30
|
+
# status "healthy" increments healthy; "optional_missing" is neutral;
|
|
31
|
+
# all other statuses (missing, corrupt, empty) increment issues.
|
|
32
|
+
_cs_add_check() {
|
|
33
|
+
local store="$1"
|
|
34
|
+
local status="$2"
|
|
35
|
+
local details="$3"
|
|
36
|
+
|
|
37
|
+
cs_checks_json=$(echo "$cs_checks_json" | jq \
|
|
38
|
+
--arg store "$store" \
|
|
39
|
+
--arg status "$status" \
|
|
40
|
+
--arg details "$details" \
|
|
41
|
+
'. += [{store:$store, status:$status, details:$details}]')
|
|
42
|
+
|
|
43
|
+
if [[ "$status" == "healthy" ]]; then
|
|
44
|
+
cs_healthy=$(( cs_healthy + 1 ))
|
|
45
|
+
elif [[ "$status" != "optional_missing" ]]; then
|
|
46
|
+
cs_issues=$(( cs_issues + 1 ))
|
|
47
|
+
fi
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Helper: check a JSON file
|
|
51
|
+
_cs_check_json_file() {
|
|
52
|
+
local store="$1"
|
|
53
|
+
local filepath="$2"
|
|
54
|
+
local required="${3:-false}"
|
|
55
|
+
|
|
56
|
+
if [[ ! -f "$filepath" ]]; then
|
|
57
|
+
if [[ "$required" == "true" ]]; then
|
|
58
|
+
_cs_add_check "$store" "missing" "Required file not found: $filepath"
|
|
59
|
+
else
|
|
60
|
+
_cs_add_check "$store" "optional_missing" "Optional file not found: $filepath"
|
|
61
|
+
fi
|
|
62
|
+
return
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
if [[ ! -s "$filepath" ]]; then
|
|
66
|
+
_cs_add_check "$store" "empty" "File exists but is empty: $filepath"
|
|
67
|
+
return
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
if ! jq empty "$filepath" 2>/dev/null; then
|
|
71
|
+
_cs_add_check "$store" "corrupt" "File contains invalid JSON: $filepath"
|
|
72
|
+
return
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
_cs_add_check "$store" "healthy" "OK"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# 1. learning-observations.json
|
|
79
|
+
_cs_check_json_file "learning-observations" \
|
|
80
|
+
"$cs_data_dir/learning-observations.json" "false"
|
|
81
|
+
|
|
82
|
+
# 2. instincts.json (optional)
|
|
83
|
+
_cs_check_json_file "instincts" \
|
|
84
|
+
"$cs_data_dir/instincts.json" "false"
|
|
85
|
+
|
|
86
|
+
# 3. instinct-graph.json (optional)
|
|
87
|
+
_cs_check_json_file "instinct-graph" \
|
|
88
|
+
"$cs_data_dir/instinct-graph.json" "false"
|
|
89
|
+
|
|
90
|
+
# 4. event-bus.jsonl (optional — check last line if exists)
|
|
91
|
+
local eb_file="$cs_data_dir/event-bus.jsonl"
|
|
92
|
+
if [[ ! -f "$eb_file" ]]; then
|
|
93
|
+
_cs_add_check "event-bus" "optional_missing" "Optional file not found: $eb_file"
|
|
94
|
+
elif [[ ! -s "$eb_file" ]]; then
|
|
95
|
+
_cs_add_check "event-bus" "healthy" "File is empty (no events)"
|
|
96
|
+
else
|
|
97
|
+
local last_line
|
|
98
|
+
last_line=$(tail -1 "$eb_file")
|
|
99
|
+
if echo "$last_line" | jq empty 2>/dev/null; then
|
|
100
|
+
_cs_add_check "event-bus" "healthy" "OK"
|
|
101
|
+
else
|
|
102
|
+
_cs_add_check "event-bus" "corrupt" "Last line is not valid JSON"
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# 5. pheromones.json (required)
|
|
107
|
+
_cs_check_json_file "pheromones" \
|
|
108
|
+
"$cs_data_dir/pheromones.json" "true"
|
|
109
|
+
|
|
110
|
+
# 6. COLONY_STATE.json (required)
|
|
111
|
+
_cs_check_json_file "COLONY_STATE" \
|
|
112
|
+
"$cs_data_dir/COLONY_STATE.json" "true"
|
|
113
|
+
|
|
114
|
+
json_ok "$(jq -nc \
|
|
115
|
+
--argjson checks "$cs_checks_json" \
|
|
116
|
+
--argjson healthy "$cs_healthy" \
|
|
117
|
+
--argjson issues "$cs_issues" \
|
|
118
|
+
'{checks:$checks, healthy:$healthy, issues:$issues}')"
|
|
119
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Event bus utility functions for the Aether Structural Learning Stack
|
|
3
|
+
# Provides: _event_publish, _event_subscribe, _event_cleanup, _event_replay
|
|
4
|
+
#
|
|
5
|
+
# These functions are sourced by aether-utils.sh at startup.
|
|
6
|
+
# All shared infrastructure (json_ok, json_err, json_warn, atomic_write, acquire_lock,
|
|
7
|
+
# release_lock, feature_enabled, LOCK_DIR, DATA_DIR, SCRIPT_DIR, error constants) is available.
|
|
8
|
+
|
|
9
|
+
# Default TTL for events in days
|
|
10
|
+
_EVENT_BUS_DEFAULT_TTL=30
|
|
11
|
+
_EVENT_BUS_DEFAULT_LIMIT=50
|
|
12
|
+
|
|
13
|
+
# ============================================================================
|
|
14
|
+
# _event_publish
|
|
15
|
+
# Publish an event to the JSONL event bus.
|
|
16
|
+
# Usage: event-publish --topic <topic> --payload <json> [--source <src>] [--ttl <days>]
|
|
17
|
+
# ============================================================================
|
|
18
|
+
_event_publish() {
|
|
19
|
+
local ep_topic=""
|
|
20
|
+
local ep_payload=""
|
|
21
|
+
local ep_source="system"
|
|
22
|
+
local ep_ttl="$_EVENT_BUS_DEFAULT_TTL"
|
|
23
|
+
|
|
24
|
+
# Parse arguments
|
|
25
|
+
while [[ $# -gt 0 ]]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
--topic)
|
|
28
|
+
ep_topic="${2:-}"
|
|
29
|
+
shift 2
|
|
30
|
+
;;
|
|
31
|
+
--payload)
|
|
32
|
+
ep_payload="${2:-}"
|
|
33
|
+
shift 2
|
|
34
|
+
;;
|
|
35
|
+
--source)
|
|
36
|
+
ep_source="${2:-system}"
|
|
37
|
+
shift 2
|
|
38
|
+
;;
|
|
39
|
+
--ttl)
|
|
40
|
+
ep_ttl="${2:-$_EVENT_BUS_DEFAULT_TTL}"
|
|
41
|
+
shift 2
|
|
42
|
+
;;
|
|
43
|
+
*)
|
|
44
|
+
shift
|
|
45
|
+
;;
|
|
46
|
+
esac
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
[[ -z "$ep_topic" ]] && json_err "$E_VALIDATION_FAILED" "event-publish requires --topic"
|
|
50
|
+
[[ -z "$ep_payload" ]] && json_err "$E_VALIDATION_FAILED" "event-publish requires --payload"
|
|
51
|
+
|
|
52
|
+
# Validate payload is valid JSON
|
|
53
|
+
echo "$ep_payload" | jq empty 2>/dev/null \
|
|
54
|
+
|| json_err "$E_JSON_INVALID" "event-publish --payload must be valid JSON"
|
|
55
|
+
|
|
56
|
+
mkdir -p "$COLONY_DATA_DIR"
|
|
57
|
+
local bus_file="$COLONY_DATA_DIR/event-bus.jsonl"
|
|
58
|
+
|
|
59
|
+
# Generate unique ID and timestamps
|
|
60
|
+
local ep_id
|
|
61
|
+
ep_id="evt_$(date +%s)_$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' \n')"
|
|
62
|
+
local ep_ts
|
|
63
|
+
ep_ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
64
|
+
local ep_expires
|
|
65
|
+
ep_expires=$(date -u -v+"${ep_ttl}"d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|
|
66
|
+
|| date -u -d "+${ep_ttl} days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|
|
67
|
+
|| echo "2099-01-01T00:00:00Z")
|
|
68
|
+
|
|
69
|
+
# Build event JSON line
|
|
70
|
+
local ep_line
|
|
71
|
+
ep_line=$(jq -nc \
|
|
72
|
+
--arg id "$ep_id" \
|
|
73
|
+
--arg topic "$ep_topic" \
|
|
74
|
+
--argjson payload "$ep_payload" \
|
|
75
|
+
--arg source "$ep_source" \
|
|
76
|
+
--arg ts "$ep_ts" \
|
|
77
|
+
--argjson ttl_days "$ep_ttl" \
|
|
78
|
+
--arg expires_at "$ep_expires" \
|
|
79
|
+
'{id:$id,topic:$topic,payload:$payload,source:$source,timestamp:$ts,ttl_days:$ttl_days,expires_at:$expires_at}')
|
|
80
|
+
|
|
81
|
+
# Acquire lock for safe concurrent append
|
|
82
|
+
acquire_lock "event-bus" 5 2>/dev/null \
|
|
83
|
+
|| json_err "$E_LOCK_FAILED" "event-publish: failed to acquire lock"
|
|
84
|
+
trap 'release_lock "event-bus" 2>/dev/null || true' EXIT
|
|
85
|
+
|
|
86
|
+
echo "$ep_line" >> "$bus_file"
|
|
87
|
+
|
|
88
|
+
release_lock "event-bus" 2>/dev/null || true
|
|
89
|
+
|
|
90
|
+
json_ok "$(jq -nc \
|
|
91
|
+
--arg event_id "$ep_id" \
|
|
92
|
+
--arg topic "$ep_topic" \
|
|
93
|
+
--argjson ttl_days "$ep_ttl" \
|
|
94
|
+
'{event_id:$event_id,topic:$topic,ttl_days:$ttl_days}')"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# ============================================================================
|
|
98
|
+
# _event_subscribe
|
|
99
|
+
# Read events matching a topic pattern from the JSONL bus.
|
|
100
|
+
# Usage: event-subscribe --topic <pattern> [--since <ISO-8601>] [--limit <N>]
|
|
101
|
+
# Pattern supports exact match or prefix with trailing '*' (e.g., "learning.*")
|
|
102
|
+
# ============================================================================
|
|
103
|
+
_event_subscribe() {
|
|
104
|
+
local es_topic=""
|
|
105
|
+
local es_since=""
|
|
106
|
+
local es_limit="$_EVENT_BUS_DEFAULT_LIMIT"
|
|
107
|
+
|
|
108
|
+
while [[ $# -gt 0 ]]; do
|
|
109
|
+
case "$1" in
|
|
110
|
+
--topic)
|
|
111
|
+
es_topic="${2:-}"
|
|
112
|
+
shift 2
|
|
113
|
+
;;
|
|
114
|
+
--since)
|
|
115
|
+
es_since="${2:-}"
|
|
116
|
+
shift 2
|
|
117
|
+
;;
|
|
118
|
+
--limit)
|
|
119
|
+
es_limit="${2:-$_EVENT_BUS_DEFAULT_LIMIT}"
|
|
120
|
+
shift 2
|
|
121
|
+
;;
|
|
122
|
+
*)
|
|
123
|
+
shift
|
|
124
|
+
;;
|
|
125
|
+
esac
|
|
126
|
+
done
|
|
127
|
+
|
|
128
|
+
[[ -z "$es_topic" ]] && json_err "$E_VALIDATION_FAILED" "event-subscribe requires --topic"
|
|
129
|
+
|
|
130
|
+
local bus_file="$COLONY_DATA_DIR/event-bus.jsonl"
|
|
131
|
+
local now_ts
|
|
132
|
+
now_ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
133
|
+
|
|
134
|
+
# If bus file does not exist, return empty result
|
|
135
|
+
if [[ ! -f "$bus_file" ]]; then
|
|
136
|
+
json_ok "$(jq -nc \
|
|
137
|
+
--arg pattern "$es_topic" \
|
|
138
|
+
'{events:[],count:0,topic_pattern:$pattern}')"
|
|
139
|
+
return 0
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# Determine if pattern is prefix match (ends with *) or exact match
|
|
143
|
+
local es_jq_filter
|
|
144
|
+
if [[ "$es_topic" == *"*" ]]; then
|
|
145
|
+
local es_prefix="${es_topic%\*}"
|
|
146
|
+
es_jq_filter="startswith(\"$es_prefix\")"
|
|
147
|
+
else
|
|
148
|
+
es_jq_filter=". == \"$es_topic\""
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
# Build jq filter: topic match + not expired + since filter
|
|
152
|
+
local es_jq_expr
|
|
153
|
+
es_jq_expr=". | select(.topic | $es_jq_filter) | select(.expires_at > \"$now_ts\")"
|
|
154
|
+
if [[ -n "$es_since" ]]; then
|
|
155
|
+
es_jq_expr="$es_jq_expr | select(.timestamp >= \"$es_since\")"
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
local es_events
|
|
159
|
+
es_events=$(jq -sc \
|
|
160
|
+
--argjson limit "$es_limit" \
|
|
161
|
+
"[.[] | $es_jq_expr] | .[:(\$limit)]" \
|
|
162
|
+
"$bus_file" 2>/dev/null || echo "[]")
|
|
163
|
+
|
|
164
|
+
local es_count
|
|
165
|
+
es_count=$(echo "$es_events" | jq 'length')
|
|
166
|
+
|
|
167
|
+
json_ok "$(jq -nc \
|
|
168
|
+
--argjson events "$es_events" \
|
|
169
|
+
--argjson count "$es_count" \
|
|
170
|
+
--arg topic_pattern "$es_topic" \
|
|
171
|
+
'{events:$events,count:$count,topic_pattern:$topic_pattern}')"
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# ============================================================================
|
|
175
|
+
# _event_cleanup
|
|
176
|
+
# Remove expired events from the JSONL bus.
|
|
177
|
+
# Usage: event-cleanup [--dry-run]
|
|
178
|
+
# ============================================================================
|
|
179
|
+
_event_cleanup() {
|
|
180
|
+
local ec_dry_run="false"
|
|
181
|
+
|
|
182
|
+
while [[ $# -gt 0 ]]; do
|
|
183
|
+
case "$1" in
|
|
184
|
+
--dry-run)
|
|
185
|
+
ec_dry_run="true"
|
|
186
|
+
shift
|
|
187
|
+
;;
|
|
188
|
+
*)
|
|
189
|
+
shift
|
|
190
|
+
;;
|
|
191
|
+
esac
|
|
192
|
+
done
|
|
193
|
+
|
|
194
|
+
local bus_file="$COLONY_DATA_DIR/event-bus.jsonl"
|
|
195
|
+
local now_ts
|
|
196
|
+
now_ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
197
|
+
|
|
198
|
+
# If bus file does not exist, nothing to clean
|
|
199
|
+
if [[ ! -f "$bus_file" ]]; then
|
|
200
|
+
json_ok "$(jq -nc \
|
|
201
|
+
--argjson dry_run "$ec_dry_run" \
|
|
202
|
+
'{removed:0,remaining:0,dry_run:$dry_run}')"
|
|
203
|
+
return 0
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
local ec_total
|
|
207
|
+
ec_total=$(wc -l < "$bus_file" | tr -d ' ')
|
|
208
|
+
|
|
209
|
+
local ec_kept
|
|
210
|
+
ec_kept=$(jq -c "select(.expires_at > \"$now_ts\")" "$bus_file" 2>/dev/null || true)
|
|
211
|
+
|
|
212
|
+
local ec_kept_count=0
|
|
213
|
+
[[ -n "$ec_kept" ]] && ec_kept_count=$(echo "$ec_kept" | wc -l | tr -d ' ')
|
|
214
|
+
|
|
215
|
+
local ec_removed=$(( ec_total - ec_kept_count ))
|
|
216
|
+
|
|
217
|
+
if [[ "$ec_dry_run" == "false" ]]; then
|
|
218
|
+
# Acquire lock for safe atomic rewrite
|
|
219
|
+
acquire_lock "event-bus" 5 2>/dev/null \
|
|
220
|
+
|| json_err "$E_LOCK_FAILED" "event-cleanup: failed to acquire lock"
|
|
221
|
+
trap 'release_lock "event-bus" 2>/dev/null || true' EXIT
|
|
222
|
+
|
|
223
|
+
if [[ -n "$ec_kept" ]]; then
|
|
224
|
+
atomic_write "$bus_file" "$ec_kept"
|
|
225
|
+
else
|
|
226
|
+
# All events expired — write empty file (touch creates it, or truncate)
|
|
227
|
+
: > "$bus_file"
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
release_lock "event-bus" 2>/dev/null || true
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
json_ok "$(jq -nc \
|
|
234
|
+
--argjson removed "$ec_removed" \
|
|
235
|
+
--argjson remaining "$ec_kept_count" \
|
|
236
|
+
--argjson dry_run "$ec_dry_run" \
|
|
237
|
+
'{removed:$removed,remaining:$remaining,dry_run:$dry_run}')"
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# ============================================================================
|
|
241
|
+
# _event_replay
|
|
242
|
+
# Replay events for a topic from a given timestamp.
|
|
243
|
+
# Usage: event-replay --topic <topic> --since <ISO-8601> [--limit <N>]
|
|
244
|
+
# ============================================================================
|
|
245
|
+
_event_replay() {
|
|
246
|
+
local er_topic=""
|
|
247
|
+
local er_since=""
|
|
248
|
+
local er_limit="$_EVENT_BUS_DEFAULT_LIMIT"
|
|
249
|
+
|
|
250
|
+
while [[ $# -gt 0 ]]; do
|
|
251
|
+
case "$1" in
|
|
252
|
+
--topic)
|
|
253
|
+
er_topic="${2:-}"
|
|
254
|
+
shift 2
|
|
255
|
+
;;
|
|
256
|
+
--since)
|
|
257
|
+
er_since="${2:-}"
|
|
258
|
+
shift 2
|
|
259
|
+
;;
|
|
260
|
+
--limit)
|
|
261
|
+
er_limit="${2:-$_EVENT_BUS_DEFAULT_LIMIT}"
|
|
262
|
+
shift 2
|
|
263
|
+
;;
|
|
264
|
+
*)
|
|
265
|
+
shift
|
|
266
|
+
;;
|
|
267
|
+
esac
|
|
268
|
+
done
|
|
269
|
+
|
|
270
|
+
[[ -z "$er_topic" ]] && json_err "$E_VALIDATION_FAILED" "event-replay requires --topic"
|
|
271
|
+
[[ -z "$er_since" ]] && json_err "$E_VALIDATION_FAILED" "event-replay requires --since"
|
|
272
|
+
|
|
273
|
+
local bus_file="$COLONY_DATA_DIR/event-bus.jsonl"
|
|
274
|
+
local now_ts
|
|
275
|
+
now_ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
276
|
+
|
|
277
|
+
if [[ ! -f "$bus_file" ]]; then
|
|
278
|
+
json_ok "$(jq -nc \
|
|
279
|
+
--arg replayed_from "$er_since" \
|
|
280
|
+
'{events:[],count:0,replayed_from:$replayed_from}')"
|
|
281
|
+
return 0
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
local er_events
|
|
285
|
+
er_events=$(jq -sc \
|
|
286
|
+
--arg topic "$er_topic" \
|
|
287
|
+
--arg since "$er_since" \
|
|
288
|
+
--arg now "$now_ts" \
|
|
289
|
+
--argjson limit "$er_limit" \
|
|
290
|
+
'[.[] | select(.topic == $topic) | select(.expires_at > $now) | select(.timestamp >= $since)] | sort_by(.timestamp) | .[:$limit]' \
|
|
291
|
+
"$bus_file" 2>/dev/null || echo "[]")
|
|
292
|
+
|
|
293
|
+
local er_count
|
|
294
|
+
er_count=$(echo "$er_events" | jq 'length')
|
|
295
|
+
|
|
296
|
+
json_ok "$(jq -nc \
|
|
297
|
+
--argjson events "$er_events" \
|
|
298
|
+
--argjson count "$er_count" \
|
|
299
|
+
--arg replayed_from "$er_since" \
|
|
300
|
+
'{events:$events,count:$count,replayed_from:$replayed_from}')"
|
|
301
|
+
}
|