create-egregore 0.21.4 → 0.21.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/assets/egregore-launcher.js +88 -14
  2. package/bin/cli.js +6 -3
  3. package/lib/api.js +26 -22
  4. package/lib/connect-runtime.js +13 -8
  5. package/lib/hosted-api.js +70 -0
  6. package/lib/local.js +3 -17
  7. package/lib/setup.js +6 -1
  8. package/package.json +2 -2
  9. package/runtime/claude/.claude/context/telemetry.md +8 -18
  10. package/runtime/claude/.claude/skills/emissary/SKILL.md +1 -1
  11. package/runtime/claude/.claude/skills/telemetry/SKILL.md +3 -3
  12. package/runtime/claude/.claude/skills/update/SKILL.md +46 -32
  13. package/runtime/claude/bin/handoff-run.sh +1 -1
  14. package/runtime/claude/bin/hosting-ops.sh +5 -243
  15. package/runtime/claude/bin/lib/framework-update.sh +3 -1
  16. package/runtime/claude/bin/lib/git-sync.sh +2 -2
  17. package/runtime/claude/bin/notify.sh +1 -1
  18. package/runtime/claude/bin/post-update.sh +13 -0
  19. package/runtime/claude/bin/publish-artifact.sh +1 -1
  20. package/runtime/claude/bin/telemetry.sh +7 -10
  21. package/runtime/claude/egregore_runtime/contracts.py +38 -0
  22. package/runtime/claude/egregore_runtime/member_authority.py +405 -0
  23. package/runtime/claude/egregore_runtime/upgrade.py +41 -2
  24. package/runtime/claude/manifest.json +1 -1
  25. package/runtime/codex/bin/handoff-run.sh +1 -1
  26. package/runtime/codex/bin/hosting-ops.sh +5 -243
  27. package/runtime/codex/bin/lib/framework-update.sh +3 -1
  28. package/runtime/codex/bin/lib/git-sync.sh +2 -2
  29. package/runtime/codex/bin/notify.sh +1 -1
  30. package/runtime/codex/bin/post-update.sh +13 -0
  31. package/runtime/codex/bin/publish-artifact.sh +1 -1
  32. package/runtime/codex/bin/telemetry.sh +7 -10
  33. package/runtime/codex/egregore_runtime/contracts.py +38 -0
  34. package/runtime/codex/egregore_runtime/member_authority.py +405 -0
  35. package/runtime/codex/egregore_runtime/upgrade.py +41 -2
  36. package/runtime/codex/manifest.json +1 -1
  37. package/runtime/pi/bin/handoff-run.sh +1 -1
  38. package/runtime/pi/bin/hosting-ops.sh +5 -243
  39. package/runtime/pi/bin/lib/framework-update.sh +3 -1
  40. package/runtime/pi/bin/lib/git-sync.sh +2 -2
  41. package/runtime/pi/bin/notify.sh +1 -1
  42. package/runtime/pi/bin/post-update.sh +13 -0
  43. package/runtime/pi/bin/publish-artifact.sh +1 -1
  44. package/runtime/pi/bin/telemetry.sh +7 -10
  45. package/runtime/pi/egregore_runtime/contracts.py +38 -0
  46. package/runtime/pi/egregore_runtime/member_authority.py +405 -0
  47. package/runtime/pi/egregore_runtime/upgrade.py +41 -2
  48. package/runtime/pi/manifest.json +1 -1
  49. package/runtime/prime/bin/handoff-run.sh +1 -1
  50. package/runtime/prime/bin/hosting-ops.sh +5 -243
  51. package/runtime/prime/bin/lib/framework-update.sh +3 -1
  52. package/runtime/prime/bin/lib/git-sync.sh +2 -2
  53. package/runtime/prime/bin/notify.sh +1 -1
  54. package/runtime/prime/bin/post-update.sh +13 -0
  55. package/runtime/prime/bin/publish-artifact.sh +1 -1
  56. package/runtime/prime/bin/telemetry.sh +7 -10
  57. package/runtime/prime/egregore_runtime/contracts.py +38 -0
  58. package/runtime/prime/egregore_runtime/member_authority.py +405 -0
  59. package/runtime/prime/egregore_runtime/upgrade.py +41 -2
  60. package/runtime/prime/manifest.json +1 -1
  61. package/runtime/claude/bin/build-sealed-dataroom.mjs +0 -722
  62. package/runtime/codex/bin/build-sealed-dataroom.mjs +0 -722
  63. package/runtime/pi/bin/build-sealed-dataroom.mjs +0 -722
  64. package/runtime/prime/bin/build-sealed-dataroom.mjs +0 -722
@@ -402,7 +402,7 @@ PID_MEMORY=$!
402
402
  if [ -f "$EMISSARY_CONFIG" ]; then
403
403
  EMISSARY_TOKEN=$(jq -r '.auth_token // empty' "$EMISSARY_CONFIG" 2>/dev/null)
404
404
  fi
405
- EMISSARY_API_URL="${EMISSARY_API_URL:-https://egregore-production-55f2.up.railway.app/api/v1/emissary}"
405
+ EMISSARY_API_URL="${EMISSARY_API_URL:-https://api.egregore.xyz/api/v1/emissary}"
406
406
 
407
407
  # /handoff is an internal team primitive, not an implicit public-emissary
408
408
  # composer. Use the emissary route only for an explicit recipient, and make
@@ -1,248 +1,10 @@
1
1
  #!/usr/bin/env bash
2
+ # Retained as an inert compatibility entry point for older installations.
2
3
  set -euo pipefail
3
4
  set +vx
4
- umask 077
5
- unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_COMMON_DIR
6
5
 
7
- SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
8
- CONFIG="${CONFIG:-$SCRIPT_DIR/egregore.json}"
9
- # shellcheck source=/dev/null
10
- source "$SCRIPT_DIR/bin/lib/config.sh"
11
-
12
- usage() {
13
- echo 'Usage: bash bin/hosting-ops.sh status|enable|wait-ready|users <slug>' >&2
14
- echo ' bash bin/hosting-ops.sh ssh <slug> [--] <remote command…>' >&2
15
- echo ' bash bin/hosting-ops.sh scp <slug> <local file> <remote path>' >&2
16
- echo ' bash bin/hosting-ops.sh deploy-template <slug> [--name <version>]' >&2
17
- echo ' bash bin/hosting-ops.sh restart-workspace <slug> <workspace id>' >&2
18
- echo ' bash bin/hosting-ops.sh user <slug> <github username>' >&2
19
- echo ' bash bin/hosting-ops.sh list' >&2
20
- exit 2
21
- }
22
-
23
- die() { printf 'hosting-ops: %s\n' "$*" >&2; exit 1; }
24
-
25
- [ "$#" -ge 1 ] || usage
26
- operation="$1"
27
- shift
28
- case "$operation" in
29
- list) [ "$#" -eq 0 ] || usage ;;
30
- status|enable|wait-ready|users) [ "$#" -eq 1 ] || usage ;;
31
- user|restart-workspace) [ "$#" -eq 2 ] && [ -n "$2" ] || usage ;;
32
- scp) [ "$#" -eq 3 ] && [ -n "$2" ] && [ -n "$3" ] || usage ;;
33
- ssh) [ "$#" -ge 2 ] || usage ;;
34
- deploy-template)
35
- [ "$#" -eq 1 ] || { [ "$#" -eq 3 ] && [ "$2" = --name ] && [ -n "$3" ]; } || usage
36
- ;;
37
- *) usage ;;
38
- esac
39
- slug="${1-}"
40
- if [ "$operation" != list ]; then
41
- # Slugs are URL path/query components, never shell source.
42
- case "$slug" in ''|*[!a-zA-Z0-9_-]*) usage ;; esac
43
- shift
44
- fi
45
- if [ "$operation" = ssh ]; then
46
- if [ "${1-}" = -- ]; then shift; fi
47
- [ "$#" -ge 1 ] && [ -n "$1" ] || usage
48
- # A quoted command is unchanged; separate words use SSH's usual space join.
49
- remote_command="$*"
50
- fi
51
- if [ "$operation" = restart-workspace ]; then
52
- case "$1" in *[!a-zA-Z0-9_-]*) usage ;; esac
53
- fi
54
- case "$operation" in
55
- ssh|scp|deploy-template|restart-workspace)
56
- command -v sshpass >/dev/null 2>&1 || die 'sshpass is required; install via brew install hudochenkov/sshpass/sshpass'
57
- ;;
58
- esac
59
-
60
- scratch=$(mktemp -d "${TMPDIR:-/tmp}/hosting-ops.XXXXXX")
61
- trap 'rm -rf -- "$scratch"' EXIT
62
- trap 'exit 1' HUP INT TERM
63
-
64
- gateway() {
65
- bash "$SCRIPT_DIR/bin/api-call.sh" "$@" --auth github || die 'API gateway request failed'
66
- }
67
-
68
- body_error() {
69
- jq -er 'if type == "object" then (.error // .detail // (if .status == "error" then "request failed" else empty end)) else empty end | select(. != false and . != "") | if type == "string" then . else tojson end' "$1" 2>/dev/null
70
- }
71
-
72
- check_body() {
73
- local detail
74
- jq -e 'type == "object"' "$1" >/dev/null 2>&1 || die 'invalid response body'
75
- if detail=$(body_error "$1"); then die "$detail"; fi
76
- }
77
-
78
- credentials() {
79
- local failure="no hosted VPS or missing credentials for $slug"
80
- bash "$SCRIPT_DIR/bin/api-call.sh" GET "/api/hosting/credentials/$slug" --auth github --out "$scratch/credentials.json" 2>/dev/null \
81
- || die "$failure"
82
- ip=$(jq -r '.ip | select(type == "string")' "$scratch/credentials.json" 2>/dev/null) || die "$failure"
83
- vps_password=$(jq -r '.password | select(type == "string")' "$scratch/credentials.json" 2>/dev/null) || die "$failure"
84
- [ -n "$ip" ] && [ -n "$vps_password" ] || die "$failure"
85
- }
86
-
87
- vps_ssh() {
88
- SSHPASS="$vps_password" sshpass -e ssh -o StrictHostKeyChecking=no "root@$ip" "$@"
89
- }
90
-
91
- vps_scp() {
92
- SSHPASS="$vps_password" sshpass -e scp -o StrictHostKeyChecking=no "$1" "root@$ip:$2"
93
- }
94
-
95
- coder_login() {
96
- # jq reads the password from stdin, never a --arg value.
97
- printf '%s' "$vps_password" | jq -Rs '{email:"admin@egregore.xyz", password:.}' > "$scratch/login.json"
98
- vps_ssh "curl -q -sS -H 'Content-Type: application/json' --data-binary @- http://localhost/api/v2/users/login" \
99
- < "$scratch/login.json" > "$scratch/login-response.json" 2>/dev/null || die 'Coder login failed'
100
- coder_token=$(jq -er '.session_token | select(type == "string" and length > 0)' "$scratch/login-response.json" 2>/dev/null) \
101
- || die 'Coder login failed'
102
- }
103
-
104
- curl_config_value() {
105
- # Curl config quoting, using builtins so secret values stay off exec argv.
106
- local value="$2"
107
- value="${value//\\/\\\\}"
108
- value="${value//\"/\\\"}"
109
- value="${value//$'\n'/\\n}"
110
- value="${value//$'\r'/\\r}"
111
- value="${value//$'\t'/\\t}"
112
- printf '%s = "%s"\n' "$1" "$value"
113
- }
114
-
115
- coder_request() {
116
- local code
117
- {
118
- curl_config_value header "Coder-Session-Token: $coder_token"
119
- curl_config_value url "http://localhost/api/v2$2"
120
- curl_config_value request "$1"
121
- if [ -n "${3-}" ]; then
122
- curl_config_value header 'Content-Type: application/json'
123
- curl_config_value data "$(cat "$3")"
124
- fi
125
- } > "$scratch/coder-curl.conf"
126
- vps_ssh "curl -q -sS -K - --write-out '\n%{http_code}'" < "$scratch/coder-curl.conf" \
127
- > "$scratch/coder-response" 2>/dev/null || die 'Coder request failed'
128
- code=$(tail -n 1 "$scratch/coder-response")
129
- case "$code" in 2[0-9][0-9]) ;; *) die 'Coder request failed' ;; esac
130
- sed '$d' "$scratch/coder-response" > "$scratch/coder.json"
131
- jq -e 'type == "object"' "$scratch/coder.json" >/dev/null 2>&1 || die 'Coder request failed'
132
- if body_error "$scratch/coder.json" >/dev/null; then die 'Coder request failed'; fi
133
- }
134
-
135
- deploy_template() {
136
- local version="$1" api_url
137
- vps_ssh 'mkdir -p /tmp/egregore-template' > /dev/null || die 'template directory creation failed'
138
- vps_scp "$SCRIPT_DIR/docker/egregore-template/main.tf" /tmp/egregore-template/main.tf > /dev/null || die 'template copy failed'
139
- coder_login
140
- vps_ssh 'cat /opt/egregore/org-config.json' > "$scratch/org-config.json" || die 'cannot read VPS org config'
141
- vps_ssh 'cat /opt/egregore/github-token 2>/dev/null' > "$scratch/github-token" || die 'cannot read VPS GitHub token'
142
- api_url=$(bash "$SCRIPT_DIR/bin/config-get.sh" api_url) || die 'cannot read api_url'
143
- [ -n "$api_url" ] || die 'api_url is not set'
144
- # A JSON object is also a YAML mapping, as accepted by --variables-file.
145
- # Do not use secret-valued --var flags: they leak on the remote argv too.
146
- jq -e --arg api_url "$api_url" --rawfile github_token "$scratch/github-token" '
147
- {egregore_api_key, api_url:$api_url, memory_url, fork_url,
148
- ghcr_token:"not-needed", github_token:($github_token | sub("\\n+$"; ""))}
149
- | if all(.[]; type == "string") then . else error("missing template variable") end
150
- ' "$scratch/org-config.json" > "$scratch/variables.json" 2>/dev/null || die 'VPS template variables are missing or invalid'
151
- {
152
- printf 'set -euo pipefail\nset +vx\numask 077\nunset CODER_SESSION_TOKEN\nexport CODER_URL=http://localhost\n'
153
- # Coder reads its session file; no secret is exported to child processes.
154
- printf 'coder_config=$(mktemp -d /tmp/egregore-coder-config.XXXXXX)\n'
155
- printf 'trap '\''rm -rf -- "$coder_config"'\'' EXIT\ntrap '\''exit 1'\'' HUP INT TERM\n'
156
- printf 'printf "%%s" %q > "$coder_config/session"\n' "$coder_token"
157
- printf 'vars_file=$(mktemp /tmp/egregore-template-vars.XXXXXX)\n'
158
- printf 'trap '\''rm -f -- "$vars_file"; rm -rf -- "$coder_config"'\'' EXIT\n'
159
- # printf is a Bash builtin, so these values never reach an exec argv.
160
- printf 'printf "%%s" %q > "$vars_file"\n' "$(cat "$scratch/variables.json")"
161
- printf 'coder --global-config "$coder_config" templates push Egregore --directory /tmp/egregore-template --name %q --variables-file "$vars_file" --yes\n' "$version"
162
- } > "$scratch/push.sh"
163
- vps_ssh bash -s < "$scratch/push.sh" || die 'template push failed'
164
- printf 'Template "Egregore" deployed: %s\n' "$version"
165
- }
166
-
167
- restart_workspace() {
168
- local workspace_id="$1" template_id active_version attempt stopped=false
169
- coder_login
170
- coder_request GET "/workspaces/$workspace_id"
171
- template_id=$(jq -er '.template_id | select(type == "string" and length > 0)' "$scratch/coder.json") \
172
- || die 'workspace has no template_id'
173
- printf '%s\n' '{"transition":"stop"}' > "$scratch/build.json"
174
- coder_request POST "/workspaces/$workspace_id/builds" "$scratch/build.json"
175
- for ((attempt=1; attempt<=24; attempt++)); do
176
- sleep 5
177
- coder_request GET "/workspaces/$workspace_id"
178
- if jq -e '.latest_build.status == "stopped"' "$scratch/coder.json" >/dev/null; then stopped=true; break; fi
179
- done
180
- [ "$stopped" = true ] || die 'workspace did not stop after 24 checks'
181
- coder_request GET "/templates/$template_id"
182
- active_version=$(jq -er '.active_version_id | select(type == "string" and length > 0)' "$scratch/coder.json") \
183
- || die 'template has no active_version_id'
184
- jq -n --arg version "$active_version" '{transition:"start", template_version_id:$version}' > "$scratch/build.json"
185
- coder_request POST "/workspaces/$workspace_id/builds" "$scratch/build.json"
186
- cat "$scratch/coder.json"
187
- }
188
-
189
- create_users() {
190
- local requested="${1-}" username detail response created=0 failed=0
191
- gateway GET "/api/admin/org/$slug" > "$scratch/org.json"
192
- check_body "$scratch/org.json"
193
- jq -er '.members | type == "array"' "$scratch/org.json" >/dev/null || die 'org response has no members'
194
- jq -r --arg requested "$requested" '.members[]
195
- | select(if $requested == "" then .status == "active" else .github_username == $requested end)
196
- | .github_username | select(type == "string" and length > 0)' "$scratch/org.json" > "$scratch/members"
197
- while IFS= read -r username; do
198
- jq -n --arg name "$username" '{username:$name}' > "$scratch/member.json"
199
- detail=''
200
- response="$scratch/member-response.json"
201
- if ! bash "$SCRIPT_DIR/bin/api-call.sh" POST "/api/hosting/user/$slug" --auth egregore --json-file "$scratch/member.json" \
202
- --error-out "$scratch/member-error.json" > "$response" 2>/dev/null; then
203
- response="$scratch/member-error.json"
204
- detail='API gateway request failed'
205
- fi
206
- if ! jq -e 'type == "object"' "$response" >/dev/null 2>&1; then
207
- detail="${detail:-invalid response body}"
208
- else
209
- # Only the bounded, operator-facing detail is published, never raw JSON.
210
- detail=$(jq -r --arg fallback "$detail" '(.detail | select(. != "")) // $fallback | if type == "string" then . else tojson end | .[:200]' "$response")
211
- fi
212
- if [ -n "$detail" ]; then
213
- printf '✗ %s — error: %s\n' "$username" "$detail"
214
- failed=$((failed + 1))
215
- else
216
- printf '✓ %s — Coder user created (workspace is created on first open)\n' "$username"
217
- created=$((created + 1))
218
- fi
219
- done < "$scratch/members"
220
- printf 'Done: %s created, %s failed\n' "$created" "$failed"
221
- [ "$failed" -eq 0 ]
222
- }
223
-
224
- case "$operation" in
225
- status) gateway GET "/api/hosting/status/$slug" ;;
226
- enable) gateway POST "/api/hosting/enable/$slug" ;;
227
- list) gateway GET /api/admin/health ;;
228
- wait-ready)
229
- poll_seconds="${HOSTING_OPS_POLL_SECONDS:-30}"
230
- case "$poll_seconds" in ''|*[!0-9]*) usage ;; esac
231
- for ((attempt=1; attempt<=10; attempt++)); do
232
- # Emit a progress line even when this attempt returns an error body.
233
- gateway GET "/api/hosting/status/$slug" > "$scratch/status.json"
234
- progress=$(jq -r '"ip: \(.ip // "unknown") — token_stored: \(if has("token_stored") then .token_stored else "unknown" end)"' "$scratch/status.json" 2>/dev/null) || die 'invalid status response'
235
- printf 'check %s/10 — %s\n' "$attempt" "$progress"
236
- check_body "$scratch/status.json"
237
- if jq -e '.coder_ready == true' "$scratch/status.json" >/dev/null; then exit 0; fi
238
- if [ "$attempt" -lt 10 ]; then sleep "$poll_seconds"; fi
239
- done
240
- die 'Coder did not become ready after 10 checks'
241
- ;;
242
- ssh) credentials; vps_ssh "$remote_command" || die 'SSH command failed' ;;
243
- scp) credentials; vps_scp "$1" "$2" || die 'SCP failed' ;;
244
- deploy-template) credentials; deploy_template "${2:-$slug-initial}" ;;
245
- restart-workspace) credentials; restart_workspace "$1" ;;
246
- users) create_users ;;
247
- user) create_users "$1" ;;
6
+ printf '%s\n' 'Hosted Coder workspaces have been retired. Use Egregore from your local workspace.' >&2
7
+ case "${1-}" in
8
+ --help|-h) exit 0 ;;
248
9
  esac
10
+ exit 1
@@ -33,7 +33,9 @@ _checkout_framework_paths() {
33
33
  # A path checkout does not merge, rebase, move HEAD, or consult the user's
34
34
  # pull/merge/rebase strategy. Tolerate each missing or failed path separately,
35
35
  # preserving the historical overlay behavior while reporting each skip once.
36
- for framework_path in bin/ .claude/commands/ .claude/skills/ .claude/hooks/ .claude/context/ .claude/agents/ .pi/ .prime/ loom/ CLAUDE.md skills/; do
36
+ # The Runtime adapters in bin/ import egregore_runtime/, so the core travels
37
+ # with them; one without the other leaves recall unable to start.
38
+ for framework_path in bin/ egregore_runtime/ .claude/commands/ .claude/skills/ .claude/hooks/ .claude/context/ .claude/agents/ .pi/ .prime/ loom/ CLAUDE.md skills/; do
37
39
  if ! git -C "$target_dir" checkout upstream/main -- "$framework_path" 2>/dev/null; then
38
40
  printf 'update-framework: skipped %s (absent from upstream/main or checkout failed)\n' "$framework_path" >&2
39
41
  fi
@@ -231,13 +231,13 @@ _apply_framework_update() {
231
231
  # committing — an overwrite of local edits is the one outcome the user has
232
232
  # to be able to see afterwards, and the commit is the only other record.
233
233
  local _changed
234
- _changed=$(git status --porcelain bin/ .claude/ .pi/ .prime/ loom/ CLAUDE.md skills/ 2>/dev/null)
234
+ _changed=$(git status --porcelain bin/ egregore_runtime/ .claude/ .pi/ .prime/ loom/ CLAUDE.md skills/ 2>/dev/null)
235
235
  if [ -n "$_changed" ]; then
236
236
  FRAMEWORK_UPDATED_COUNT=$(printf '%s\n' "$_changed" | grep -c . || echo 0)
237
237
  FRAMEWORK_UPDATED_FILES=$(printf '%s\n' "$_changed" | awk '{print $NF}' | head -3 | tr '\n' ' ')
238
238
  git add bin/ .claude/ loom/ CLAUDE.md skills/ 2>/dev/null
239
239
  local _runtime_path
240
- for _runtime_path in .pi .prime; do
240
+ for _runtime_path in egregore_runtime .pi .prime; do
241
241
  [ ! -e "$_runtime_path" ] || git add -- "$_runtime_path" 2>/dev/null
242
242
  done
243
243
  EGREGORE_FRAMEWORK_UPDATE=1 git commit -m "chore(sync): update framework from upstream" --quiet 2>/dev/null || true
@@ -32,7 +32,7 @@ SESSION_ID="$(sed -n '1p' "$SCRIPT_DIR/.egregore-session-id" 2>/dev/null || true
32
32
  SESSION_ID="${SESSION_ID:-no-session}"
33
33
  PROJECT_HASH="$(printf '%s' "$SCRIPT_DIR" | cksum | awk '{print $1}')"
34
34
  STATE_DIR="${EGREGORE_NOTIFY_STATE_DIR:-$HOME/.egregore/notifications/$PROJECT_HASH}"
35
- RELAY_URL="${EGREGORE_NOTIFY_RELAY_URL:-https://egregore-production-55f2.up.railway.app}"
35
+ RELAY_URL="${EGREGORE_NOTIFY_RELAY_URL:-https://api.egregore.xyz}"
36
36
  mkdir -p "$STATE_DIR"
37
37
  chmod 700 "$STATE_DIR" 2>/dev/null || true
38
38
 
@@ -30,6 +30,19 @@ if [ -d ".claude/skills" ] && [ -d ".claude/commands" ]; then
30
30
  fi
31
31
  fi
32
32
 
33
+ # --- Migration: Runtime core travels with its adapters ---
34
+ # Earlier overlays copied bin/ but not egregore_runtime/, leaving the Runtime
35
+ # adapters without the module they import. The overlay that ran this update
36
+ # was loaded before bin/ changed, so copy the core here: one /update is
37
+ # enough. A Runtime-managed installation owns these files and is left alone.
38
+ if [ ! -e "$SCRIPT_DIR/egregore_runtime" ] && \
39
+ git -C "$SCRIPT_DIR" cat-file -e 'upstream/main:egregore_runtime' 2>/dev/null && \
40
+ ! (source "$SCRIPT_DIR/bin/lib/runtime-owned.sh"; runtime_owns_framework); then
41
+ git -C "$SCRIPT_DIR" checkout upstream/main -- egregore_runtime/
42
+ echo " ✓ Added the Runtime core (egregore_runtime/)"
43
+ MIGRATED=1
44
+ fi
45
+
33
46
  # --- Future migrations go here ---
34
47
  # Each migration should:
35
48
  # 1. Check if it needs to run (idempotent guard)
@@ -132,7 +132,7 @@ fi
132
132
 
133
133
  # Public relay for OSS share endpoint. Unauthenticated: anything posted here is
134
134
  # readable by anyone holding the URL, for 7 days, with nothing in front of it.
135
- RELAY_URL="https://egregore-production-55f2.up.railway.app"
135
+ RELAY_URL="https://api.egregore.xyz"
136
136
 
137
137
  # --- Artifact hosting off-switch (compliance / privacy) ---
138
138
  # An instance can disable ALL egregore.xyz publishing via `bin/settings.sh hosting off`
@@ -38,7 +38,6 @@ MAX_BUFFER_BYTES=1048576
38
38
  MAX_EVENTS_AFTER_TRUNCATE=500
39
39
  SHARE_CONSENT_SECONDS=600
40
40
  SCHEMA_VERSION="egregore.telemetry/v1"
41
- LEGACY_RELAY_URL="https://egregore-production-55f2.up.railway.app/api/telemetry/relay"
42
41
 
43
42
  ALLOWED_METRICS_JSON='[
44
43
  "action","branch","class","command","connector","cost_usd","count",
@@ -516,7 +515,6 @@ cmd_export() {
516
515
 
517
516
  _resolve_share_target() {
518
517
  local requested_endpoint="$1"
519
- local allow_legacy_relay="$2"
520
518
  if [ -n "$requested_endpoint" ]; then
521
519
  printf '%s\n' "$requested_endpoint"
522
520
  return
@@ -530,13 +528,11 @@ _resolve_share_target() {
530
528
  fi
531
529
  if [ -n "$api_url" ] && [ -n "$api_key" ]; then
532
530
  printf '%s/api/telemetry/ingest\n' "${api_url%/}"
533
- elif [ "$allow_legacy_relay" = "true" ]; then
534
- printf '%s\n' "$LEGACY_RELAY_URL"
535
531
  fi
536
532
  }
537
533
 
538
534
  cmd_share() {
539
- local confirmation="" requested_endpoint="" allow_legacy_relay="false" json_output="false"
535
+ local confirmation="" requested_endpoint="" json_output="false"
540
536
  while [ "$#" -gt 0 ]; do
541
537
  case "$1" in
542
538
  --confirm)
@@ -548,8 +544,10 @@ cmd_share() {
548
544
  shift 2
549
545
  ;;
550
546
  --legacy-public-relay)
551
- allow_legacy_relay="true"
552
- shift
547
+ # The former public relay ran on a retired host; nothing is sent there.
548
+ echo "The former public relay is retired; nothing was sent." >&2
549
+ echo "Export locally, or name an explicit --endpoint." >&2
550
+ return 2
553
551
  ;;
554
552
  --json)
555
553
  json_output="true"
@@ -567,10 +565,9 @@ cmd_share() {
567
565
  fi
568
566
 
569
567
  local endpoint=""
570
- endpoint=$(_resolve_share_target "$requested_endpoint" "$allow_legacy_relay")
568
+ endpoint=$(_resolve_share_target "$requested_endpoint")
571
569
  if [ -z "$endpoint" ]; then
572
570
  echo "No shared telemetry sink is configured. Export locally, or name an explicit --endpoint." >&2
573
- echo "The former public relay remains available only with --legacy-public-relay." >&2
574
571
  return 2
575
572
  fi
576
573
  case "$endpoint" in
@@ -697,7 +694,7 @@ cmd_share() {
697
694
  local curl_args=(-s -o /dev/null -w '%{http_code}' -X POST "$endpoint"
698
695
  -H "Content-Type: application/x-ndjson" --data-binary "@$share_file" --max-time 15)
699
696
  local api_key="" configured_endpoint=""
700
- configured_endpoint=$(_resolve_share_target "" "false")
697
+ configured_endpoint=$(_resolve_share_target "")
701
698
  if [ -n "$configured_endpoint" ] && [ "$endpoint" = "$configured_endpoint" ] && [ -f "$ENV_FILE" ]; then
702
699
  api_key=$(grep '^EGREGORE_API_KEY=' "$ENV_FILE" 2>/dev/null | cut -d'=' -f2- || true)
703
700
  fi
@@ -152,6 +152,30 @@ class Entitlements(Serializable):
152
152
  return capability in self.capabilities
153
153
 
154
154
 
155
+ @dataclass(frozen=True, slots=True)
156
+ class ExecutionAuthority(Serializable):
157
+ """Non-secret attribution for a verified member mandate.
158
+
159
+ This records the original verified admission, not the latest authorization
160
+ decision. It is not a credential or permission. An authenticated resolver
161
+ must revalidate current authority before each protected operation. Spirit
162
+ ownership, funding and parent_actor_id confer none of this authority.
163
+ """
164
+
165
+ executor_actor_id: str
166
+ executor_kind: ActorKind
167
+ worker_id: str
168
+ member_account_id: str
169
+ member_actor_id: str
170
+ membership_id: str
171
+ org_id: str
172
+ grant_id: str
173
+ binding_id: str
174
+ audience: str
175
+ revision: str
176
+ expires_at: datetime
177
+
178
+
155
179
  @dataclass(frozen=True, slots=True)
156
180
  class ActorContext(Serializable):
157
181
  account: AccountIdentity | None
@@ -161,6 +185,13 @@ class ActorContext(Serializable):
161
185
  entitlements: Entitlements
162
186
  session_id: str
163
187
  harness: str
188
+ execution: ExecutionAuthority | None = None
189
+
190
+ def to_dict(self) -> dict[str, Any]:
191
+ result = Serializable.to_dict(self)
192
+ if self.execution is None:
193
+ result.pop("execution")
194
+ return result
164
195
 
165
196
 
166
197
  @dataclass(frozen=True, slots=True)
@@ -184,6 +215,13 @@ class SourceProvenance(Serializable):
184
215
  observed_at: datetime
185
216
  source_uri: str | None = None
186
217
  imported_by: str | None = None
218
+ execution: ExecutionAuthority | None = None
219
+
220
+ def to_dict(self) -> dict[str, Any]:
221
+ result = Serializable.to_dict(self)
222
+ if self.execution is None:
223
+ result.pop("execution")
224
+ return result
187
225
 
188
226
 
189
227
  @dataclass(frozen=True, slots=True)