create-egregore 0.21.0-runtime-mvp.24 → 0.21.0-runtime-mvp.25

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // egregore-launcher-version: 0.21.0-runtime-mvp.24
2
+ // egregore-launcher-version: 0.21.0-runtime-mvp.25
3
3
  //
4
4
  // egregore — launch and browse Egregore instances from your terminal.
5
5
  // Installed by create-egregore. Reads ~/.egregore/instances.json.
@@ -35,7 +35,7 @@ const crypto = require("node:crypto");
35
35
  const { spawnSync, spawn } = require("node:child_process");
36
36
  const readline = require("node:readline");
37
37
 
38
- const VERSION = "0.21.0-runtime-mvp.24";
38
+ const VERSION = "0.21.0-runtime-mvp.25";
39
39
  const REGISTRY = path.join(os.homedir(), ".egregore", "instances.json");
40
40
  const DEFAULT_FILE = path.join(os.homedir(), ".egregore", "default");
41
41
  const UPDATE_CACHE_FILE = path.join(os.homedir(), ".egregore", "update-check.json");
@@ -1288,7 +1288,19 @@ function compareReleaseVersions(a, b) {
1288
1288
  if (left.prerelease === right.prerelease) return 0;
1289
1289
  if (!left.prerelease) return 1;
1290
1290
  if (!right.prerelease) return -1;
1291
- return left.prerelease.localeCompare(right.prerelease);
1291
+ const leftIds = left.prerelease.split(".");
1292
+ const rightIds = right.prerelease.split(".");
1293
+ for (let i = 0; i < Math.max(leftIds.length, rightIds.length); i++) {
1294
+ if (leftIds[i] === undefined) return -1;
1295
+ if (rightIds[i] === undefined) return 1;
1296
+ if (leftIds[i] === rightIds[i]) continue;
1297
+ const leftNumeric = /^\d+$/.test(leftIds[i]);
1298
+ const rightNumeric = /^\d+$/.test(rightIds[i]);
1299
+ if (leftNumeric && rightNumeric) return BigInt(leftIds[i]) > BigInt(rightIds[i]) ? 1 : -1;
1300
+ if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1;
1301
+ return leftIds[i] > rightIds[i] ? 1 : -1;
1302
+ }
1303
+ return 0;
1292
1304
  }
1293
1305
 
1294
1306
  function cliUpdateState(latest) {
@@ -2353,13 +2365,20 @@ function resolveUpgradeBridge(inst) {
2353
2365
  return null;
2354
2366
  }
2355
2367
 
2368
+ // Snapshot the bridge before starting Bash: older instances still have a
2369
+ // non-exec bridge that activation overwrites while its shell is running.
2370
+ // Keep $0 and all arguments intact for root resolution and exact forwarding.
2371
+ function upgradeBridgeArgs(bridge, args) {
2372
+ return ["-c", fs.readFileSync(bridge.script, "utf-8"), bridge.script, ...args];
2373
+ }
2374
+
2356
2375
  function runUpgrade(inst, args, timeout = 60000) {
2357
2376
  const bridge = resolveUpgradeBridge(inst);
2358
2377
  if (!bridge) {
2359
2378
  return { status: 127, stdout: "", stderr: "Runtime update is unavailable — no upgrade engine in this egregore or the installed launcher package.\n", missing: true };
2360
2379
  }
2361
2380
  try {
2362
- return spawnSync("bash", [bridge.script, ...args], { cwd: inst.path, stdio: "pipe", encoding: "utf-8", timeout, env: { ...process.env, ...bridge.env } });
2381
+ return spawnSync("bash", upgradeBridgeArgs(bridge, args), { cwd: inst.path, stdio: "pipe", encoding: "utf-8", timeout, env: { ...process.env, ...bridge.env } });
2363
2382
  } catch (err) {
2364
2383
  return { status: 1, stdout: "", stderr: String(err) };
2365
2384
  }
@@ -2502,7 +2521,7 @@ function upgradeStatusAsync(inst, cb) {
2502
2521
  if (!bridge) return cb({ status: "unavailable", failure: { message: "No Runtime update engine is installed" } });
2503
2522
  let buf = ""; let err = "";
2504
2523
  try {
2505
- const child = spawn("bash", [bridge.script, "status"], { cwd: inst.path, stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, ...bridge.env } });
2524
+ const child = spawn("bash", upgradeBridgeArgs(bridge, ["status"]), { cwd: inst.path, stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, ...bridge.env } });
2506
2525
  child.stdout.on("data", (d) => { buf += d; });
2507
2526
  child.stderr.on("data", (d) => { err += d; });
2508
2527
  child.on("close", (code) => {
@@ -2639,12 +2658,18 @@ const UPGRADE_STATUS_LABEL = {
2639
2658
  // Settings-row truth for the Runtime update line. The row must say outright
2640
2659
  // whether this egregore runs the new Runtime — "no update staged" is
2641
2660
  // transaction state, not the condition the user needs to see.
2661
+ function runtimeUpdateAvailable(st, candidate) {
2662
+ return st?.status === "active"
2663
+ && RUNTIME_MVP_VERSION_RE.test(candidate || "")
2664
+ && compareReleaseVersions(candidate, st.active_version) > 0;
2665
+ }
2666
+
2642
2667
  function runtimeUpdateRowValue(st, candidate) {
2643
2668
  if (st === undefined) return `${DIM}checking…${RESET}`;
2644
2669
  if (st === null) return `${DIM}unavailable${RESET}`;
2645
2670
  if (st.status === "active") {
2646
2671
  const v = st.active_version || "?";
2647
- if (candidate && candidate !== v) return `${v} active ${YELLOW}· update available ${candidate}${RESET}`;
2672
+ if (runtimeUpdateAvailable(st, candidate)) return `${v} active ${YELLOW}· update available ${candidate}${RESET}`;
2648
2673
  return `${GREEN}${v} active${RESET}${candidate ? `${DIM} · up to date${RESET}` : ""}`;
2649
2674
  }
2650
2675
  if (st.status === "unavailable") return `${YELLOW}update unavailable${RESET}`;
@@ -2682,8 +2707,10 @@ function formatUpgradeLines(inst, st, { candidate = "", retrieval = null } = {})
2682
2707
  lines.push(`Location: ${inst.path}`);
2683
2708
  lines.push("");
2684
2709
  const activeLabel = d.active_version ? `${d.active_version} active` : "not yet activated here";
2685
- lines.push(`Runtime: ${activeLabel}`
2686
- + (d.candidate_version ? ` → candidate ${d.candidate_version}` : candidate ? ` → candidate ${candidate} available` : ""));
2710
+ const candidateLabel = d.status === "active"
2711
+ ? (runtimeUpdateAvailable(d, candidate) ? ` → candidate ${candidate} available` : "")
2712
+ : (d.candidate_version ? ` → candidate ${d.candidate_version}` : candidate ? ` → candidate ${candidate} available` : "");
2713
+ lines.push(`Runtime: ${activeLabel}${candidateLabel}`);
2687
2714
  lines.push(`Status: ${UPGRADE_STATUS_LABEL[d.status] || d.status}`);
2688
2715
  if (d.candidate_digest) lines.push(`Package digest: sha256 ${String(d.candidate_digest).slice(0, 16)}…`);
2689
2716
  // A recorded failure only needs attention while the transaction is stuck
@@ -3161,7 +3188,7 @@ function renderUpgradePanel({ inst, data, candidate, retrieval, mode, notice, wi
3161
3188
  if (notice) lines.push(boxLine(notice, innerWidth, margin));
3162
3189
  const keys = [];
3163
3190
  if (!status || status === "none" || ["pending", "failed", "interrupted", "cancelled", "rolled-back"].includes(status)) keys.push(`${BOLD}p${RESET}${DIM} prepare${RESET}`);
3164
- if (status === "active" && candidate && candidate !== (data && data.active_version)) keys.push(`${BOLD}p${RESET}${DIM} prepare update${RESET}`);
3191
+ if (runtimeUpdateAvailable(data, candidate)) keys.push(`${BOLD}p${RESET}${DIM} prepare update${RESET}`);
3165
3192
  if (status === "ready-to-activate") keys.push(`${BOLD}a${RESET}${DIM} activate${RESET}`);
3166
3193
  if (status === "preparing") keys.push(`${BOLD}c${RESET}${DIM} cancel${RESET}`);
3167
3194
  if (status === "active") keys.push(`${BOLD}r${RESET}${DIM} roll back${RESET}`);
@@ -3924,9 +3951,7 @@ async function browser(args = {}) {
3924
3951
  render(); return;
3925
3952
  }
3926
3953
  const status = state.upgradeData && state.upgradeData.status;
3927
- const activeUpdatable = status === "active"
3928
- && state.upgradeCandidate
3929
- && state.upgradeCandidate !== (state.upgradeData && state.upgradeData.active_version);
3954
+ const activeUpdatable = runtimeUpdateAvailable(state.upgradeData, state.upgradeCandidate);
3930
3955
  if (chunk === "p" && (!status || activeUpdatable || ["none", "pending", "failed", "interrupted", "cancelled", "rolled-back"].includes(status))) {
3931
3956
  const staged = state.upgradeData
3932
3957
  && !["none", "cancelled", "rolled-back", "active"].includes(status)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-egregore",
3
- "version": "0.21.0-runtime-mvp.24",
3
+ "version": "0.21.0-runtime-mvp.25",
4
4
  "description": "Set up Egregore for your team in one command — installs the egregore launcher and your choice of Claude Code, Codex, or Pi.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -36,17 +36,10 @@ BASE=$(bash -c 'SCRIPT_DIR="$PWD"; CONFIG="$PWD/egregore.json"; . "$PWD/bin/lib/
36
36
 
37
37
  Stop before mutation if it cannot be resolved.
38
38
 
39
- 3. Run the product-distribution checkpoint when its registry exists — the
40
- guard is real: installed instances do not ship the distribution engine,
41
- and its absence there is correct, not an error to chase:
42
-
43
- ```bash
44
- if [ -f bin/capability-distribution.mjs ] && [ -f capability-distribution.json ]; then
45
- bash bin/node-run.sh bin/capability-distribution.mjs validate --root .
46
- bash bin/node-run.sh bin/capability-distribution.mjs changes --root . --base "origin/$BASE" --json
47
- bash bin/node-run.sh bin/capability-distribution.mjs runtime-skill-audit --root . --strict-contract
48
- fi
49
- ```
39
+ 3. The shared `bin/agent.sh save` preflight runs distribution validation,
40
+ the change report, and the strict Runtime-skill audit before any memory or
41
+ project mutation. Both engine and registry must exist; installed public
42
+ instances intentionally omit the development engine.
50
43
 
51
44
  Ask one placement question only for a genuinely new/unclassified component or
52
45
  an explicit placement change. Ordinary edits to an existing component do not
@@ -397,6 +397,14 @@ cmd_save() {
397
397
  [ -n "$message" ] || message="chore(save): $topic"
398
398
  base="$(resolve_base_branch)" || die "save stopped before Git changes"
399
399
 
400
+ # Shared invariant across harnesses, before memory or project mutations.
401
+ # Installed public instances intentionally omit the development engine.
402
+ if [ -f "$SCRIPT_DIR/bin/capability-distribution.mjs" ] && [ -f "$SCRIPT_DIR/capability-distribution.json" ]; then
403
+ bash "$SCRIPT_DIR/bin/node-run.sh" "$SCRIPT_DIR/bin/capability-distribution.mjs" validate --root "$SCRIPT_DIR" || die "distribution validation failed"
404
+ bash "$SCRIPT_DIR/bin/node-run.sh" "$SCRIPT_DIR/bin/capability-distribution.mjs" changes --root "$SCRIPT_DIR" --base "origin/$base" --json || die "distribution change check failed"
405
+ bash "$SCRIPT_DIR/bin/node-run.sh" "$SCRIPT_DIR/bin/capability-distribution.mjs" runtime-skill-audit --root "$SCRIPT_DIR" --strict-contract || die "runtime skill contract failed"
406
+ fi
407
+
400
408
  if [ -d "$MEMORY_DIR" ]; then
401
409
  save_memory "$message" "."
402
410
  echo "memory: synced"
@@ -22,10 +22,12 @@ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
22
22
  # tree, while the target instance is set explicitly — never inferred.
23
23
  INSTANCE_ROOT="${EGREGORE_INSTANCE_ROOT:-$SCRIPT_DIR}"
24
24
 
25
+ # Activation and rollback can replace this script. Hand off the process so
26
+ # Bash never resumes reading changed bytes after the Runtime returns.
25
27
  _runtime() {
26
28
  EGREGORE_ROOT="$INSTANCE_ROOT" \
27
29
  PYTHONPATH="$SCRIPT_DIR${PYTHONPATH:+:$PYTHONPATH}" \
28
- python3 -m egregore_runtime.harness_cli "$@"
30
+ exec python3 -m egregore_runtime.harness_cli "$@"
29
31
  }
30
32
 
31
33
  _ensure_identity() {
@@ -230,15 +230,12 @@ class EgregoreRuntime:
230
230
  "this document is admin-marked; Egregore serves it to organization admins only"
231
231
  )
232
232
  stable_artifact_id = canonical_artifact_id(content, path)
233
- if stable_artifact_id != artifact_id:
234
- stable_decision = self.authorize(
235
- actor, Permission.READ, (stable_artifact_id,)
233
+ stable_decision = self.authorize(actor, Permission.READ, (stable_artifact_id,))
234
+ if not stable_decision.allowed:
235
+ raise AuthorizationDenied(
236
+ "canonical artifact identity is outside the actor read scope"
236
237
  )
237
- if not stable_decision.allowed:
238
- raise AuthorizationDenied(
239
- "canonical artifact identity is outside the actor read scope"
240
- )
241
- artifact_id = stable_artifact_id
238
+ artifact_id = stable_artifact_id
242
239
  try:
243
240
  self.telemetry.emit(
244
241
  TelemetryEvent(
@@ -42,13 +42,10 @@ esac
42
42
  - Stop with "everything is already saved" only when there are no repo or
43
43
  memory changes and either the branch has no commits to integrate
44
44
  (`AHEAD=0`) or an open integration PR already exists.
45
- 4. Run the product distribution checkpoint before any commit or push when
46
- `capability-distribution.json` exists:
47
-
48
- ```bash
49
- bash bin/node-run.sh bin/capability-distribution.mjs validate --root .
50
- bash bin/node-run.sh bin/capability-distribution.mjs changes --root . --base "origin/$BASE" --json
51
- ```
45
+ 4. The shared `bin/agent.sh save` preflight runs distribution validation,
46
+ the change report, and the strict Runtime-skill audit before any memory or
47
+ project mutation. It runs only when both the registry and development
48
+ engine exist; public installed instances intentionally omit that engine.
52
49
 
53
50
  Inspect the diff as well as the receipt. Ask when this work introduces a new
54
51
  or unclassified Egregore component, or changes a component's placement
@@ -397,6 +397,14 @@ cmd_save() {
397
397
  [ -n "$message" ] || message="chore(save): $topic"
398
398
  base="$(resolve_base_branch)" || die "save stopped before Git changes"
399
399
 
400
+ # Shared invariant across harnesses, before memory or project mutations.
401
+ # Installed public instances intentionally omit the development engine.
402
+ if [ -f "$SCRIPT_DIR/bin/capability-distribution.mjs" ] && [ -f "$SCRIPT_DIR/capability-distribution.json" ]; then
403
+ bash "$SCRIPT_DIR/bin/node-run.sh" "$SCRIPT_DIR/bin/capability-distribution.mjs" validate --root "$SCRIPT_DIR" || die "distribution validation failed"
404
+ bash "$SCRIPT_DIR/bin/node-run.sh" "$SCRIPT_DIR/bin/capability-distribution.mjs" changes --root "$SCRIPT_DIR" --base "origin/$base" --json || die "distribution change check failed"
405
+ bash "$SCRIPT_DIR/bin/node-run.sh" "$SCRIPT_DIR/bin/capability-distribution.mjs" runtime-skill-audit --root "$SCRIPT_DIR" --strict-contract || die "runtime skill contract failed"
406
+ fi
407
+
400
408
  if [ -d "$MEMORY_DIR" ]; then
401
409
  save_memory "$message" "."
402
410
  echo "memory: synced"
@@ -22,10 +22,12 @@ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
22
22
  # tree, while the target instance is set explicitly — never inferred.
23
23
  INSTANCE_ROOT="${EGREGORE_INSTANCE_ROOT:-$SCRIPT_DIR}"
24
24
 
25
+ # Activation and rollback can replace this script. Hand off the process so
26
+ # Bash never resumes reading changed bytes after the Runtime returns.
25
27
  _runtime() {
26
28
  EGREGORE_ROOT="$INSTANCE_ROOT" \
27
29
  PYTHONPATH="$SCRIPT_DIR${PYTHONPATH:+:$PYTHONPATH}" \
28
- python3 -m egregore_runtime.harness_cli "$@"
30
+ exec python3 -m egregore_runtime.harness_cli "$@"
29
31
  }
30
32
 
31
33
  _ensure_identity() {
@@ -230,15 +230,12 @@ class EgregoreRuntime:
230
230
  "this document is admin-marked; Egregore serves it to organization admins only"
231
231
  )
232
232
  stable_artifact_id = canonical_artifact_id(content, path)
233
- if stable_artifact_id != artifact_id:
234
- stable_decision = self.authorize(
235
- actor, Permission.READ, (stable_artifact_id,)
233
+ stable_decision = self.authorize(actor, Permission.READ, (stable_artifact_id,))
234
+ if not stable_decision.allowed:
235
+ raise AuthorizationDenied(
236
+ "canonical artifact identity is outside the actor read scope"
236
237
  )
237
- if not stable_decision.allowed:
238
- raise AuthorizationDenied(
239
- "canonical artifact identity is outside the actor read scope"
240
- )
241
- artifact_id = stable_artifact_id
238
+ artifact_id = stable_artifact_id
242
239
  try:
243
240
  self.telemetry.emit(
244
241
  TelemetryEvent(
@@ -42,13 +42,10 @@ esac
42
42
  - Stop with "everything is already saved" only when there are no repo or
43
43
  memory changes and either the branch has no commits to integrate
44
44
  (`AHEAD=0`) or an open integration PR already exists.
45
- 4. Run the product distribution checkpoint before any commit or push when
46
- `capability-distribution.json` exists:
47
-
48
- ```bash
49
- bash bin/node-run.sh bin/capability-distribution.mjs validate --root .
50
- bash bin/node-run.sh bin/capability-distribution.mjs changes --root . --base "origin/$BASE" --json
51
- ```
45
+ 4. The shared `bin/agent.sh save` preflight runs distribution validation,
46
+ the change report, and the strict Runtime-skill audit before any memory or
47
+ project mutation. It runs only when both the registry and development
48
+ engine exist; public installed instances intentionally omit that engine.
52
49
 
53
50
  Inspect the diff as well as the receipt. Ask when this work introduces a new
54
51
  or unclassified Egregore component, or changes a component's placement
@@ -397,6 +397,14 @@ cmd_save() {
397
397
  [ -n "$message" ] || message="chore(save): $topic"
398
398
  base="$(resolve_base_branch)" || die "save stopped before Git changes"
399
399
 
400
+ # Shared invariant across harnesses, before memory or project mutations.
401
+ # Installed public instances intentionally omit the development engine.
402
+ if [ -f "$SCRIPT_DIR/bin/capability-distribution.mjs" ] && [ -f "$SCRIPT_DIR/capability-distribution.json" ]; then
403
+ bash "$SCRIPT_DIR/bin/node-run.sh" "$SCRIPT_DIR/bin/capability-distribution.mjs" validate --root "$SCRIPT_DIR" || die "distribution validation failed"
404
+ bash "$SCRIPT_DIR/bin/node-run.sh" "$SCRIPT_DIR/bin/capability-distribution.mjs" changes --root "$SCRIPT_DIR" --base "origin/$base" --json || die "distribution change check failed"
405
+ bash "$SCRIPT_DIR/bin/node-run.sh" "$SCRIPT_DIR/bin/capability-distribution.mjs" runtime-skill-audit --root "$SCRIPT_DIR" --strict-contract || die "runtime skill contract failed"
406
+ fi
407
+
400
408
  if [ -d "$MEMORY_DIR" ]; then
401
409
  save_memory "$message" "."
402
410
  echo "memory: synced"
@@ -22,10 +22,12 @@ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
22
22
  # tree, while the target instance is set explicitly — never inferred.
23
23
  INSTANCE_ROOT="${EGREGORE_INSTANCE_ROOT:-$SCRIPT_DIR}"
24
24
 
25
+ # Activation and rollback can replace this script. Hand off the process so
26
+ # Bash never resumes reading changed bytes after the Runtime returns.
25
27
  _runtime() {
26
28
  EGREGORE_ROOT="$INSTANCE_ROOT" \
27
29
  PYTHONPATH="$SCRIPT_DIR${PYTHONPATH:+:$PYTHONPATH}" \
28
- python3 -m egregore_runtime.harness_cli "$@"
30
+ exec python3 -m egregore_runtime.harness_cli "$@"
29
31
  }
30
32
 
31
33
  _ensure_identity() {
@@ -230,15 +230,12 @@ class EgregoreRuntime:
230
230
  "this document is admin-marked; Egregore serves it to organization admins only"
231
231
  )
232
232
  stable_artifact_id = canonical_artifact_id(content, path)
233
- if stable_artifact_id != artifact_id:
234
- stable_decision = self.authorize(
235
- actor, Permission.READ, (stable_artifact_id,)
233
+ stable_decision = self.authorize(actor, Permission.READ, (stable_artifact_id,))
234
+ if not stable_decision.allowed:
235
+ raise AuthorizationDenied(
236
+ "canonical artifact identity is outside the actor read scope"
236
237
  )
237
- if not stable_decision.allowed:
238
- raise AuthorizationDenied(
239
- "canonical artifact identity is outside the actor read scope"
240
- )
241
- artifact_id = stable_artifact_id
238
+ artifact_id = stable_artifact_id
242
239
  try:
243
240
  self.telemetry.emit(
244
241
  TelemetryEvent(
@@ -42,13 +42,10 @@ esac
42
42
  - Stop with "everything is already saved" only when there are no repo or
43
43
  memory changes and either the branch has no commits to integrate
44
44
  (`AHEAD=0`) or an open integration PR already exists.
45
- 4. Run the product distribution checkpoint before any commit or push when
46
- `capability-distribution.json` exists:
47
-
48
- ```bash
49
- bash bin/node-run.sh bin/capability-distribution.mjs validate --root .
50
- bash bin/node-run.sh bin/capability-distribution.mjs changes --root . --base "origin/$BASE" --json
51
- ```
45
+ 4. The shared `bin/agent.sh save` preflight runs distribution validation,
46
+ the change report, and the strict Runtime-skill audit before any memory or
47
+ project mutation. It runs only when both the registry and development
48
+ engine exist; public installed instances intentionally omit that engine.
52
49
 
53
50
  Inspect the diff as well as the receipt. Ask when this work introduces a new
54
51
  or unclassified Egregore component, or changes a component's placement
@@ -397,6 +397,14 @@ cmd_save() {
397
397
  [ -n "$message" ] || message="chore(save): $topic"
398
398
  base="$(resolve_base_branch)" || die "save stopped before Git changes"
399
399
 
400
+ # Shared invariant across harnesses, before memory or project mutations.
401
+ # Installed public instances intentionally omit the development engine.
402
+ if [ -f "$SCRIPT_DIR/bin/capability-distribution.mjs" ] && [ -f "$SCRIPT_DIR/capability-distribution.json" ]; then
403
+ bash "$SCRIPT_DIR/bin/node-run.sh" "$SCRIPT_DIR/bin/capability-distribution.mjs" validate --root "$SCRIPT_DIR" || die "distribution validation failed"
404
+ bash "$SCRIPT_DIR/bin/node-run.sh" "$SCRIPT_DIR/bin/capability-distribution.mjs" changes --root "$SCRIPT_DIR" --base "origin/$base" --json || die "distribution change check failed"
405
+ bash "$SCRIPT_DIR/bin/node-run.sh" "$SCRIPT_DIR/bin/capability-distribution.mjs" runtime-skill-audit --root "$SCRIPT_DIR" --strict-contract || die "runtime skill contract failed"
406
+ fi
407
+
400
408
  if [ -d "$MEMORY_DIR" ]; then
401
409
  save_memory "$message" "."
402
410
  echo "memory: synced"
@@ -22,10 +22,12 @@ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
22
22
  # tree, while the target instance is set explicitly — never inferred.
23
23
  INSTANCE_ROOT="${EGREGORE_INSTANCE_ROOT:-$SCRIPT_DIR}"
24
24
 
25
+ # Activation and rollback can replace this script. Hand off the process so
26
+ # Bash never resumes reading changed bytes after the Runtime returns.
25
27
  _runtime() {
26
28
  EGREGORE_ROOT="$INSTANCE_ROOT" \
27
29
  PYTHONPATH="$SCRIPT_DIR${PYTHONPATH:+:$PYTHONPATH}" \
28
- python3 -m egregore_runtime.harness_cli "$@"
30
+ exec python3 -m egregore_runtime.harness_cli "$@"
29
31
  }
30
32
 
31
33
  _ensure_identity() {
@@ -230,15 +230,12 @@ class EgregoreRuntime:
230
230
  "this document is admin-marked; Egregore serves it to organization admins only"
231
231
  )
232
232
  stable_artifact_id = canonical_artifact_id(content, path)
233
- if stable_artifact_id != artifact_id:
234
- stable_decision = self.authorize(
235
- actor, Permission.READ, (stable_artifact_id,)
233
+ stable_decision = self.authorize(actor, Permission.READ, (stable_artifact_id,))
234
+ if not stable_decision.allowed:
235
+ raise AuthorizationDenied(
236
+ "canonical artifact identity is outside the actor read scope"
236
237
  )
237
- if not stable_decision.allowed:
238
- raise AuthorizationDenied(
239
- "canonical artifact identity is outside the actor read scope"
240
- )
241
- artifact_id = stable_artifact_id
238
+ artifact_id = stable_artifact_id
242
239
  try:
243
240
  self.telemetry.emit(
244
241
  TelemetryEvent(