create-egregore 0.21.0-runtime-mvp.23 → 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.23
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.23";
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");
@@ -766,9 +766,8 @@ function codexSupportMessage(egregoreDir) {
766
766
  " .codex/hooks.json",
767
767
  " .codex/hooks/",
768
768
  "",
769
- "Automatic repair did not complete. Retry with:",
770
- " npx -y create-egregore@latest --refresh-launcher",
771
- "Then rerun egregore.",
769
+ "Restore this instance using its installed runtime version, then rerun egregore.",
770
+ "A global CLI refresh does not repair instance files.",
772
771
  "Egregore workflows appear as slash commands. In Codex, invoke them with the matching $skill token or ask in natural language.",
773
772
  ].join("\n");
774
773
  }
@@ -799,7 +798,7 @@ function piSupportMessage(egregoreDir) {
799
798
  "Pi meta-harness support is missing from this Egregore checkout.",
800
799
  ` path: ${egregoreDir}`,
801
800
  "",
802
- "Run npx -y create-egregore@latest to install the packaged Pi runtime, then rerun egregore.",
801
+ "Restore this instance using its installed runtime version, then rerun egregore.",
803
802
  ].join("\n");
804
803
  }
805
804
 
@@ -818,31 +817,10 @@ function primeSupportMessage(egregoreDir) {
818
817
  "Prime Agent runtime support is missing from this Egregore checkout.",
819
818
  ` path: ${egregoreDir}`,
820
819
  "",
821
- "Run npx -y create-egregore@latest to install the packaged Prime Agent runtime, then rerun egregore.",
820
+ "Restore this instance using its installed runtime version, then rerun egregore.",
822
821
  ].join("\n");
823
822
  }
824
823
 
825
- function repairPortableRuntime(egregoreDir, runtime) {
826
- const label = runtimeLabel(runtime);
827
- process.stdout.write(`\n ${CYAN}↻${RESET} ${BOLD}Repairing ${label} support${RESET}${DIM} — updating this checkout${RESET}\n`);
828
- const result = spawnSync(
829
- "npx",
830
- ["-y", `${PUBLIC_NPX_PACKAGE}@latest`, "--refresh-launcher"],
831
- {
832
- cwd: egregoreDir,
833
- stdio: "inherit",
834
- env: { ...process.env, EGREGORE_RUNTIME_REPAIR: runtime },
835
- timeout: 3 * 60 * 1000,
836
- },
837
- );
838
- if (result.error || result.status !== 0) {
839
- process.stderr.write(` ${YELLOW}!${RESET} Automatic ${label} repair failed; the checkout was left in place.\n`);
840
- return false;
841
- }
842
- process.stdout.write(` ${GREEN}✓${RESET} ${label} support repaired\n\n`);
843
- return true;
844
- }
845
-
846
824
  // Minimal y/N line prompt for pre-launch decisions (the TUI select machinery
847
825
  // is torn down by the time launch() runs).
848
826
  function confirmLine(question) {
@@ -988,19 +966,15 @@ async function launch(inst, runtimeOverride) {
988
966
  }
989
967
  }
990
968
  if (runtime === "codex") {
991
- if (!hasCodexCoreSupport(inst.path)) {
992
- if (!repairPortableRuntime(inst.path, runtime) || !hasCodexCoreSupport(inst.path)) {
993
- die(codexSupportMessage(inst.path));
994
- }
995
- }
969
+ // Launching never changes an instance's runtime or release channel.
970
+ if (!hasCodexCoreSupport(inst.path)) die(codexSupportMessage(inst.path));
996
971
  const skillsInstalled = installCodexSkills(inst.path);
997
972
  const cardRendered = await renderCodexStartupCard(inst.path);
998
973
  if (!skillsInstalled || !cardRendered) {
999
974
  die(codexSupportMessage(inst.path));
1000
975
  }
1001
976
  } else if (runtime === "pi") {
1002
- if (!hasPiCoreSupport(inst.path) &&
1003
- (!repairPortableRuntime(inst.path, runtime) || !hasPiCoreSupport(inst.path))) {
977
+ if (!hasPiCoreSupport(inst.path)) {
1004
978
  die(piSupportMessage(inst.path));
1005
979
  }
1006
980
  }
@@ -1011,10 +985,7 @@ async function launch(inst, runtimeOverride) {
1011
985
  // check + the trust-flag probe, which shells out to the binary), then a
1012
986
  // single handoff line explains the remaining in-harness startup.
1013
987
  const stopLoader = startSpinner("Starting Prime Agent");
1014
- let coreOk = hasPrimeCoreSupport(inst.path);
1015
- if (!coreOk && repairPortableRuntime(inst.path, runtime)) {
1016
- coreOk = hasPrimeCoreSupport(inst.path);
1017
- }
988
+ const coreOk = hasPrimeCoreSupport(inst.path);
1018
989
  launchArgs = coreOk ? runtimeLaunchArgs(runtime, inst.path) : null;
1019
990
  stopLoader();
1020
991
  if (!coreOk) die(primeSupportMessage(inst.path));
@@ -1317,7 +1288,19 @@ function compareReleaseVersions(a, b) {
1317
1288
  if (left.prerelease === right.prerelease) return 0;
1318
1289
  if (!left.prerelease) return 1;
1319
1290
  if (!right.prerelease) return -1;
1320
- 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;
1321
1304
  }
1322
1305
 
1323
1306
  function cliUpdateState(latest) {
@@ -1883,9 +1866,10 @@ async function cmdConnect(args) {
1883
1866
  console.log(` ${DIM}${line}${RESET}`);
1884
1867
  }
1885
1868
  }
1869
+ // Connect is an instance-scoped overlay; never refresh other runtimes here.
1886
1870
  const runtimeRefresh = spawnSync(
1887
1871
  "npx",
1888
- ["-y", `${PUBLIC_NPX_PACKAGE}@latest`, "--refresh-launcher"],
1872
+ ["-y", `${PUBLIC_NPX_PACKAGE}@${VERSION}`, "--refresh-connect"],
1889
1873
  {
1890
1874
  cwd: inst.path,
1891
1875
  encoding: "utf-8",
@@ -1897,7 +1881,7 @@ async function cmdConnect(args) {
1897
1881
  console.log(` ${GREEN}✓${RESET} Connect features installed`);
1898
1882
  } else {
1899
1883
  console.log(` ${YELLOW}!${RESET} Connect features will retry on the next update`);
1900
- console.log(` ${DIM}npx -y ${PUBLIC_NPX_PACKAGE}@latest --refresh-launcher${RESET}`);
1884
+ console.log(` ${DIM}npx -y ${PUBLIC_NPX_PACKAGE}@${VERSION} --refresh-connect${RESET}`);
1901
1885
  }
1902
1886
  console.log("");
1903
1887
  console.log(` ${GREEN}✓${RESET} ${BOLD}${inst.slug}${RESET} is connected. Local files remain the source of truth.`);
@@ -2381,13 +2365,20 @@ function resolveUpgradeBridge(inst) {
2381
2365
  return null;
2382
2366
  }
2383
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
+
2384
2375
  function runUpgrade(inst, args, timeout = 60000) {
2385
2376
  const bridge = resolveUpgradeBridge(inst);
2386
2377
  if (!bridge) {
2387
2378
  return { status: 127, stdout: "", stderr: "Runtime update is unavailable — no upgrade engine in this egregore or the installed launcher package.\n", missing: true };
2388
2379
  }
2389
2380
  try {
2390
- 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 } });
2391
2382
  } catch (err) {
2392
2383
  return { status: 1, stdout: "", stderr: String(err) };
2393
2384
  }
@@ -2530,7 +2521,7 @@ function upgradeStatusAsync(inst, cb) {
2530
2521
  if (!bridge) return cb({ status: "unavailable", failure: { message: "No Runtime update engine is installed" } });
2531
2522
  let buf = ""; let err = "";
2532
2523
  try {
2533
- 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 } });
2534
2525
  child.stdout.on("data", (d) => { buf += d; });
2535
2526
  child.stderr.on("data", (d) => { err += d; });
2536
2527
  child.on("close", (code) => {
@@ -2667,12 +2658,18 @@ const UPGRADE_STATUS_LABEL = {
2667
2658
  // Settings-row truth for the Runtime update line. The row must say outright
2668
2659
  // whether this egregore runs the new Runtime — "no update staged" is
2669
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
+
2670
2667
  function runtimeUpdateRowValue(st, candidate) {
2671
2668
  if (st === undefined) return `${DIM}checking…${RESET}`;
2672
2669
  if (st === null) return `${DIM}unavailable${RESET}`;
2673
2670
  if (st.status === "active") {
2674
2671
  const v = st.active_version || "?";
2675
- 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}`;
2676
2673
  return `${GREEN}${v} active${RESET}${candidate ? `${DIM} · up to date${RESET}` : ""}`;
2677
2674
  }
2678
2675
  if (st.status === "unavailable") return `${YELLOW}update unavailable${RESET}`;
@@ -2710,8 +2707,10 @@ function formatUpgradeLines(inst, st, { candidate = "", retrieval = null } = {})
2710
2707
  lines.push(`Location: ${inst.path}`);
2711
2708
  lines.push("");
2712
2709
  const activeLabel = d.active_version ? `${d.active_version} active` : "not yet activated here";
2713
- lines.push(`Runtime: ${activeLabel}`
2714
- + (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}`);
2715
2714
  lines.push(`Status: ${UPGRADE_STATUS_LABEL[d.status] || d.status}`);
2716
2715
  if (d.candidate_digest) lines.push(`Package digest: sha256 ${String(d.candidate_digest).slice(0, 16)}…`);
2717
2716
  // A recorded failure only needs attention while the transaction is stuck
@@ -3189,7 +3188,7 @@ function renderUpgradePanel({ inst, data, candidate, retrieval, mode, notice, wi
3189
3188
  if (notice) lines.push(boxLine(notice, innerWidth, margin));
3190
3189
  const keys = [];
3191
3190
  if (!status || status === "none" || ["pending", "failed", "interrupted", "cancelled", "rolled-back"].includes(status)) keys.push(`${BOLD}p${RESET}${DIM} prepare${RESET}`);
3192
- 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}`);
3193
3192
  if (status === "ready-to-activate") keys.push(`${BOLD}a${RESET}${DIM} activate${RESET}`);
3194
3193
  if (status === "preparing") keys.push(`${BOLD}c${RESET}${DIM} cancel${RESET}`);
3195
3194
  if (status === "active") keys.push(`${BOLD}r${RESET}${DIM} roll back${RESET}`);
@@ -3952,9 +3951,7 @@ async function browser(args = {}) {
3952
3951
  render(); return;
3953
3952
  }
3954
3953
  const status = state.upgradeData && state.upgradeData.status;
3955
- const activeUpdatable = status === "active"
3956
- && state.upgradeCandidate
3957
- && state.upgradeCandidate !== (state.upgradeData && state.upgradeData.active_version);
3954
+ const activeUpdatable = runtimeUpdateAvailable(state.upgradeData, state.upgradeCandidate);
3958
3955
  if (chunk === "p" && (!status || activeUpdatable || ["none", "pending", "failed", "interrupted", "cancelled", "rolled-back"].includes(status))) {
3959
3956
  const staged = state.upgradeData
3960
3957
  && !["none", "cancelled", "rolled-back", "active"].includes(status)
package/lib/setup.js CHANGED
@@ -1132,7 +1132,7 @@ function codexSupportMessage(egregoreDir) {
1132
1132
  " .codex/hooks.json",
1133
1133
  " .codex/hooks/",
1134
1134
  "",
1135
- "Run npx -y create-egregore@latest to install the packaged Codex runtime, then rerun egregore.",
1135
+ "Restore this instance using its installed runtime version, then rerun egregore.",
1136
1136
  "Egregore workflows appear as slash commands. In Codex, invoke them with the matching $skill token or ask in natural language.",
1137
1137
  ].join("\n");
1138
1138
  }
@@ -1146,7 +1146,7 @@ function piSupportMessage(egregoreDir) {
1146
1146
  "Pi meta-harness support is missing from this Egregore checkout.",
1147
1147
  ` path: ${egregoreDir}`,
1148
1148
  "",
1149
- "Run npx -y create-egregore@latest to install the packaged Pi runtime, then rerun egregore.",
1149
+ "Restore this instance using its installed runtime version, then rerun egregore.",
1150
1150
  "Pi loads Egregore through project-local .pi resources and the shared portable skill corpus.",
1151
1151
  ].join("\n");
1152
1152
  }
@@ -1156,7 +1156,7 @@ function primeSupportMessage(egregoreDir) {
1156
1156
  "Prime Agent runtime support is missing from this Egregore checkout.",
1157
1157
  ` path: ${egregoreDir}`,
1158
1158
  "",
1159
- "Run npx -y create-egregore@latest to install the packaged Prime Agent runtime, then rerun egregore.",
1159
+ "Restore this instance using its installed runtime version, then rerun egregore.",
1160
1160
  "Prime Agent loads Egregore through project-local .prime/agent resources and the shared portable skill corpus.",
1161
1161
  ].join("\n");
1162
1162
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-egregore",
3
- "version": "0.21.0-runtime-mvp.23",
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(