@junghanacs/entwurf 0.15.0 → 0.16.0
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/AGENTS.md +1 -0
- package/CHANGELOG.md +299 -0
- package/DELIVERY.md +4 -3
- package/README.md +99 -67
- package/VERIFY.md +13 -7
- package/docs/acp-backend-rail.md +1 -1
- package/docs/external-mcp-host.md +147 -9
- package/docs/setup-clean-host.md +221 -16
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +65 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +17 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +172 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +152 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +271 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +988 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-receive-omp.js +574 -0
- package/mcp/entwurf-bridge/dist/scripts/omp-receive-facts.js +84 -0
- package/mcp/entwurf-bridge/src/index.ts +65 -19
- package/mcp/entwurf-bridge/tsconfig.build.json +19 -3
- package/mcp/tsconfig.json +10 -0
- package/package.json +14 -4
- package/pi/entwurf-capabilities.json +1 -0
- package/pi/meta-bridge-omp/entwurf-meta-omp/package.json +7 -0
- package/pi/omp-receive/entwurf-receive-omp/package.json +7 -0
- package/pi-extensions/entwurf-control.ts +12 -9
- package/pi-extensions/lib/entwurf-self-address.ts +18 -7
- package/pi-extensions/lib/meta-sender-identity.ts +1 -1
- package/pi-extensions/lib/meta-session.ts +219 -5
- package/pi-extensions/lib/mux-fresh-call.ts +171 -17
- package/pi-extensions/lib/omp-fresh-preflight.ts +275 -0
- package/pi-extensions/meta-bridge-omp.ts +1244 -0
- package/pi-extensions/meta-bridge-receive-omp.ts +666 -0
- package/run.sh +1041 -174
- package/scripts/agy-bridge.sh +4 -2
- package/scripts/check-copilot-birth-hook.ts +700 -23
- package/scripts/check-entwurf-self-address.ts +31 -0
- package/scripts/check-fresh-cut-gate.sh +12 -4
- package/scripts/check-gate-qualification.ts +8 -2
- package/scripts/check-harness-admission-parity.ts +143 -0
- package/scripts/check-meta-doctor-oracle.sh +20 -0
- package/scripts/check-omp-birth-hook.ts +1049 -0
- package/scripts/check-omp-fresh-preflight.ts +181 -0
- package/scripts/check-omp-receive-arm.ts +516 -0
- package/scripts/check-setup-qualification.sh +126 -0
- package/scripts/copilot-bridge-doctor.sh +104 -71
- package/scripts/copilot-bridge-install.sh +180 -20
- package/scripts/copilot-bridge-oracle.sh +223 -0
- package/scripts/copilot-bridge-uninstall.sh +137 -0
- package/scripts/dev-bin.sh +50 -9
- package/scripts/fake-copilot-vendor.sh +67 -0
- package/scripts/inventory-verification-surface.ts +1 -1
- package/scripts/meta-bridge-hook-log.sh +9 -1
- package/scripts/mutants/copilot-birth.json +96 -5
- package/scripts/mutants/mux-fresh-call.json +22 -22
- package/scripts/mutants/omp-birth.json +173 -0
- package/scripts/mutants/omp-fresh.json +300 -0
- package/scripts/mutants/omp-receive.json +135 -0
- package/scripts/mutants/pi-package-ownership.json +92 -0
- package/scripts/mutants/self-address.json +34 -0
- package/scripts/mutants/setup-verdict.json +148 -0
- package/scripts/mutants/source-install.json +32 -0
- package/scripts/omp-bridge-doctor.sh +315 -0
- package/scripts/omp-bridge-install.sh +221 -0
- package/scripts/omp-bridge-oracle.sh +154 -0
- package/scripts/omp-bridge-uninstall.sh +57 -0
- package/scripts/omp-mcp-bridge.sh +320 -0
- package/scripts/omp-mcp-config.py +392 -0
- package/scripts/omp-receive-doctor.sh +246 -0
- package/scripts/omp-receive-facts.ts +106 -0
- package/scripts/omp-receive-install.sh +228 -0
- package/scripts/omp-receive-uninstall.sh +60 -0
- package/scripts/omp-tool-surface.py +349 -0
- package/scripts/pi_settings_io.py +22 -0
- package/scripts/raw-omp-measure/README.md +420 -0
- package/scripts/raw-omp-measure/probe-extension.ts +76 -0
- package/scripts/raw-omp-measure/probe-receive-surface.ts +250 -0
- package/scripts/raw-omp-measure/source-audit.md +414 -0
- package/scripts/register-pi-package.py +525 -7
- package/scripts/register-pi-provider.py +162 -12
- package/scripts/smoke-agy-install-state.sh +86 -30
- package/scripts/smoke-agy-statusline-state.sh +9 -6
- package/scripts/smoke-omp-bridge-state.sh +221 -0
- package/scripts/smoke-omp-fresh-live.ts +497 -0
- package/scripts/smoke-omp-mcp-state.sh +327 -0
- package/scripts/smoke-omp-receive-live.ts +340 -0
- package/scripts/smoke-omp-receive-state.sh +196 -0
- package/scripts/smoke-setup-verdict.sh +291 -0
- package/scripts/smoke-user-scope-citizen.sh +388 -1
- package/scripts/tsconfig.json +2 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* omp-receive-facts — the read-only projection of the OMP RECEIVE rail (#87 bundle B).
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS AS ITS OWN SURFACE. `meta-facts` projects the certified RECORD store
|
|
5
|
+
* and says nothing about receiver markers (measured: its keys are storeDir / citizens /
|
|
6
|
+
* defects). The receive doctor, the install-state smoke and the LIVE acceptance all need
|
|
7
|
+
* the same question answered — "which omp citizens have a LIVE doorbell right now?" — and
|
|
8
|
+
* a doctor that answered it with `ls` or a grep would be claiming an active receiver from
|
|
9
|
+
* a filename. So the answer comes from the PRODUCTION reader, `readMetaReceiverMarker`,
|
|
10
|
+
* exactly as the record count comes from the production certifier.
|
|
11
|
+
*
|
|
12
|
+
* IT REPORTS BOTH READINGS, AND THAT IS THE POINT. `verifyOwner:true` is the live answer
|
|
13
|
+
* dispatch will get; `verifyOwner:false` is the file as written. Reporting only the first
|
|
14
|
+
* would make a crashed session's leftover marker indistinguishable from no marker at all,
|
|
15
|
+
* and the operator needs to tell "nothing armed" from "something armed and then died".
|
|
16
|
+
*
|
|
17
|
+
* READ-ONLY: it opens no session, writes no marker, and never repairs what it finds.
|
|
18
|
+
*/
|
|
19
|
+
import * as fs from "node:fs";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
import { type MetaReceiverMarker, ompMetaRoots, readMetaReceiverMarker } from "../pi-extensions/lib/meta-session.ts";
|
|
22
|
+
|
|
23
|
+
interface ReceiverFact {
|
|
24
|
+
gardenId: string;
|
|
25
|
+
backend: string;
|
|
26
|
+
nativeSessionId: string;
|
|
27
|
+
ownerPid: number;
|
|
28
|
+
ownerKind: string;
|
|
29
|
+
armProvenance: string;
|
|
30
|
+
updatedAt: string;
|
|
31
|
+
/** The reading dispatch gets: the owner pid is still the same live process. */
|
|
32
|
+
ownerLive: boolean;
|
|
33
|
+
/** Mailbox bodies the doorbell has already announced and the model has not drained. */
|
|
34
|
+
unreadDelivered: number;
|
|
35
|
+
/** Bodies enqueued but not yet announced — a doorbell that has not fired yet. */
|
|
36
|
+
freshUnannounced: number;
|
|
37
|
+
mailboxDir: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function countSuffix(dir: string, suffix: string): number {
|
|
41
|
+
try {
|
|
42
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith(suffix)).length;
|
|
43
|
+
} catch {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function main(): void {
|
|
49
|
+
const roots = ompMetaRoots();
|
|
50
|
+
const facts: ReceiverFact[] = [];
|
|
51
|
+
const unreadable: string[] = [];
|
|
52
|
+
let entries: string[] = [];
|
|
53
|
+
try {
|
|
54
|
+
entries = fs.readdirSync(roots.receiversDir).filter((f) => f.endsWith(".json"));
|
|
55
|
+
} catch {
|
|
56
|
+
entries = [];
|
|
57
|
+
}
|
|
58
|
+
for (const entry of entries.sort()) {
|
|
59
|
+
const gardenId = entry.slice(0, -".json".length);
|
|
60
|
+
let written: MetaReceiverMarker | null = null;
|
|
61
|
+
try {
|
|
62
|
+
written = readMetaReceiverMarker({
|
|
63
|
+
gardenId,
|
|
64
|
+
receiversDir: roots.receiversDir,
|
|
65
|
+
verifyOwner: false,
|
|
66
|
+
});
|
|
67
|
+
} catch {
|
|
68
|
+
written = null;
|
|
69
|
+
}
|
|
70
|
+
if (!written) {
|
|
71
|
+
unreadable.push(entry);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (written.backend !== "omp") continue;
|
|
75
|
+
const live = readMetaReceiverMarker({ gardenId, receiversDir: roots.receiversDir, verifyOwner: true }) !== null;
|
|
76
|
+
const mailboxDir = path.join(roots.mailboxDir, gardenId);
|
|
77
|
+
facts.push({
|
|
78
|
+
gardenId: written.gardenId,
|
|
79
|
+
backend: written.backend,
|
|
80
|
+
nativeSessionId: written.nativeSessionId,
|
|
81
|
+
ownerPid: written.ownerPid,
|
|
82
|
+
ownerKind: written.ownerKind,
|
|
83
|
+
armProvenance: written.armProvenance,
|
|
84
|
+
updatedAt: written.updatedAt,
|
|
85
|
+
ownerLive: live,
|
|
86
|
+
unreadDelivered: countSuffix(mailboxDir, ".msg.delivered"),
|
|
87
|
+
freshUnannounced: countSuffix(mailboxDir, ".msg"),
|
|
88
|
+
mailboxDir,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
process.stdout.write(
|
|
92
|
+
`${JSON.stringify(
|
|
93
|
+
{
|
|
94
|
+
schemaVersion: 1,
|
|
95
|
+
receiversDir: roots.receiversDir,
|
|
96
|
+
mailboxDir: roots.mailboxDir,
|
|
97
|
+
receivers: facts,
|
|
98
|
+
unreadableMarkers: unreadable,
|
|
99
|
+
},
|
|
100
|
+
null,
|
|
101
|
+
2,
|
|
102
|
+
)}\n`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
main();
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# omp-receive-install.sh — operator-grade global installer for the OMP RECEIVER
|
|
3
|
+
# extension (#87 bundle B). The fourth thing an omp citizen needs, and deliberately its
|
|
4
|
+
# own installer: birth, the MCP hand and receive are three independent atoms with three
|
|
5
|
+
# independent failure modes, and one installer owning all of them would let a broken
|
|
6
|
+
# birth withhold a doorbell (or worse, imply one).
|
|
7
|
+
#
|
|
8
|
+
# WHY A SECOND UNIT AND NOT A BRANCH INSIDE THE BIRTH UNIT. There is no second PROCESS
|
|
9
|
+
# here — an omp extension runs in-process, so both units live in the operator's TUI — but
|
|
10
|
+
# there are two independent CAPABILITIES, and the gate that proves birth mints no
|
|
11
|
+
# receiver (`check-omp-receive-arm` / `[QK:OMP-BIRTH-DOES-NOT-ARM-RECEIVER]`) stays true
|
|
12
|
+
# only while they are separable. An operator may install birth and decline receive; the
|
|
13
|
+
# inverse below is what makes that an honest offer rather than a slogan.
|
|
14
|
+
#
|
|
15
|
+
# THE UNIT DOES NOT ASSUME IT LOADS SECOND. `[LIVE 2026-08-30]` extension handlers run in
|
|
16
|
+
# directory-name collation order, and a discovered unit sorting before the birth unit saw
|
|
17
|
+
# no sender marker at its own `session_start` (20ms early). `entwurf-receive-omp` happens
|
|
18
|
+
# to sort after `entwurf-meta-omp`, so today it would arm on the first try — by accident.
|
|
19
|
+
# The unit therefore carries a bounded vendor-owned retry instead of relying on that, and
|
|
20
|
+
# this installer must never be "fixed" by renaming a directory.
|
|
21
|
+
#
|
|
22
|
+
# WHERE IT PLACES IT. `<agent-dir>/extensions/<unit>/index.{ts,js}`, the same native
|
|
23
|
+
# discovery rule the birth unit uses (`discovery/helpers.ts:625-712`). The agent dir is
|
|
24
|
+
# resolved by the shared oracle, which REFUSES rather than guesses when an inherited
|
|
25
|
+
# `PI_*` knob makes it ambiguous (ledger M6).
|
|
26
|
+
#
|
|
27
|
+
# Platform: Linux only, same fence as the Claude, Copilot and omp-birth installers.
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
|
|
30
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
31
|
+
REPO="$(cd "$HERE/.." && pwd)"
|
|
32
|
+
UNIT="entwurf-receive-omp"
|
|
33
|
+
SRC="$REPO/pi/omp-receive"
|
|
34
|
+
# `ENTWURF_OMP_RECEIVE_ASM` exists for the gate: check-omp-receive-arm drives this script for real,
|
|
35
|
+
# into a temp dir, with --assemble-only. A gate that re-implemented the assembly would be
|
|
36
|
+
# testing its own copy of the logic instead of the shipped one.
|
|
37
|
+
ASM="${ENTWURF_OMP_RECEIVE_ASM:-${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/omp-receive/.assembled}"
|
|
38
|
+
STATE_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/omp-receive"
|
|
39
|
+
STATE_FILE="$STATE_DIR/install-state.json"
|
|
40
|
+
ASSEMBLE_ONLY=0
|
|
41
|
+
|
|
42
|
+
die() { echo "[omp-receive-install] $*" >&2; exit 1; }
|
|
43
|
+
|
|
44
|
+
for arg in "$@"; do
|
|
45
|
+
case "$arg" in
|
|
46
|
+
--assemble-only) ASSEMBLE_ONLY=1 ;;
|
|
47
|
+
*) die "unknown argument: $arg (--assemble-only)" ;;
|
|
48
|
+
esac
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
[ "$(uname -s)" = "Linux" ] || die "Linux only; $(uname -s) is not a certified axis for this install."
|
|
52
|
+
command -v python3 >/dev/null 2>&1 || die "python3 is required and is not on PATH."
|
|
53
|
+
|
|
54
|
+
# shellcheck source=scripts/omp-bridge-oracle.sh
|
|
55
|
+
. "$HERE/omp-bridge-oracle.sh"
|
|
56
|
+
|
|
57
|
+
if [ "$ASSEMBLE_ONLY" -eq 1 ]; then
|
|
58
|
+
# A GATE-ONLY path that writes no ownership state, so it must never touch the LIVE
|
|
59
|
+
# assembly: without a state, a rebuilt live assembly would drift from what the state
|
|
60
|
+
# records. Same refusal the Copilot installer carries, for the same reason.
|
|
61
|
+
DEFAULT_ASM="${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/omp-receive/.assembled"
|
|
62
|
+
[ -n "${ENTWURF_OMP_RECEIVE_ASM:-}" ] \
|
|
63
|
+
|| die "--assemble-only requires an explicit ENTWURF_OMP_RECEIVE_ASM (a temp dir): this gate-only path writes no ownership state and must not rebuild the live assembly."
|
|
64
|
+
if [ "$(python3 -c 'import os,sys;print(os.path.abspath(sys.argv[1]))' "$ENTWURF_OMP_RECEIVE_ASM")" = \
|
|
65
|
+
"$(python3 -c 'import os,sys;print(os.path.abspath(sys.argv[1]))' "$DEFAULT_ASM")" ]; then
|
|
66
|
+
die "--assemble-only refuses the DEFAULT live assembly path ($DEFAULT_ASM): pass a temp ENTWURF_OMP_RECEIVE_ASM instead."
|
|
67
|
+
fi
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
AGENT_DIR=""
|
|
71
|
+
UNIT_DIR=""
|
|
72
|
+
if [ "$ASSEMBLE_ONLY" -eq 0 ]; then
|
|
73
|
+
command -v omp >/dev/null 2>&1 || die "the 'omp' CLI is not on PATH. entwurf never installs a harness — install omp first, then re-run."
|
|
74
|
+
AGENT_DIR="$(omp_agent_dir)" || die "refusing: the omp agent directory this host reads is ambiguous (see above)."
|
|
75
|
+
UNIT_DIR="$AGENT_DIR/extensions/$UNIT"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
UNIT_VERSION="$(python3 -c "import json,sys;print(json.load(open(sys.argv[1]))['version'])" "$SRC/$UNIT/package.json")" \
|
|
79
|
+
|| die "could not read the shipped unit version from $SRC/$UNIT/package.json"
|
|
80
|
+
case "$UNIT_VERSION" in
|
|
81
|
+
""|*[![:graph:]]*) die "shipped unit version ${UNIT_VERSION:-<empty>} is empty or carries whitespace/control characters — it travels through space-separated fact transports." ;;
|
|
82
|
+
esac
|
|
83
|
+
|
|
84
|
+
# Same install-shape split as the Claude and Copilot installers: an installed package
|
|
85
|
+
# lives below node_modules where Node refuses strip-types on `.ts`, so it ships the
|
|
86
|
+
# tsc-emitted closure; a dev clone ships the `.ts` source. omp itself reads both (its
|
|
87
|
+
# discovery prefers index.ts and falls back to index.js, `discovery/helpers.ts:700-710`).
|
|
88
|
+
case "$REPO" in
|
|
89
|
+
*/node_modules/@junghanacs/entwurf)
|
|
90
|
+
ENTRY_NAME="index.js"
|
|
91
|
+
ENTRY_SRC="$REPO/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-receive-omp.js"
|
|
92
|
+
LIB_SRC="$REPO/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
|
|
93
|
+
LIB_EXT="js" ;;
|
|
94
|
+
*)
|
|
95
|
+
ENTRY_NAME="index.ts"
|
|
96
|
+
ENTRY_SRC="$REPO/pi-extensions/meta-bridge-receive-omp.ts"
|
|
97
|
+
LIB_SRC="$REPO/pi-extensions/lib/meta-session.ts"
|
|
98
|
+
LIB_EXT="ts" ;;
|
|
99
|
+
esac
|
|
100
|
+
|
|
101
|
+
[ -f "$ENTRY_SRC" ] || die "receive entry artifact missing: $ENTRY_SRC (run 'pnpm run build-bridge' in a dev clone, or reinstall the package)."
|
|
102
|
+
[ -f "$LIB_SRC" ] || die "entry lib artifact missing: $LIB_SRC (same build-bridge dist closure)."
|
|
103
|
+
|
|
104
|
+
write_state() { # $1 = ownedUnitDir (true|false)
|
|
105
|
+
mkdir -p "$STATE_DIR"
|
|
106
|
+
STATE_FILE_ENV="$STATE_FILE" UNIT_DIR_ENV="$UNIT_DIR" ASM_ENV="$ASM" \
|
|
107
|
+
UNIT_VERSION_ENV="$UNIT_VERSION" ENTRY_NAME_ENV="$ENTRY_NAME" OWNED_ENV="$1" python3 - <<'PY' \
|
|
108
|
+
|| die "could not write the ownership state at $STATE_FILE"
|
|
109
|
+
import datetime, json, os, tempfile
|
|
110
|
+
state_file = os.environ["STATE_FILE_ENV"]
|
|
111
|
+
state = {
|
|
112
|
+
"schemaVersion": 1,
|
|
113
|
+
"unitDir": os.path.abspath(os.environ["UNIT_DIR_ENV"]),
|
|
114
|
+
"assemblyPath": os.path.abspath(os.environ["ASM_ENV"]),
|
|
115
|
+
"unitVersion": os.environ["UNIT_VERSION_ENV"],
|
|
116
|
+
"entryName": os.environ["ENTRY_NAME_ENV"],
|
|
117
|
+
"ownedUnitDir": os.environ["OWNED_ENV"] == "true",
|
|
118
|
+
"installedAt": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
119
|
+
}
|
|
120
|
+
fd, tmp = tempfile.mkstemp(dir=os.path.dirname(state_file), prefix=".install-state.")
|
|
121
|
+
with os.fdopen(fd, "w") as fh:
|
|
122
|
+
json.dump(state, fh, indent=2)
|
|
123
|
+
fh.write("\n")
|
|
124
|
+
os.replace(tmp, state_file)
|
|
125
|
+
PY
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# ── 0. ownership preflight (READ-ONLY) ───────────────────────────────────────
|
|
129
|
+
# Every destructive step below is licensed HERE, and the only licence is ownership STATE.
|
|
130
|
+
# Anything that already exists at our path and cannot be proven ours refuses with zero
|
|
131
|
+
# writes — a structural shape is not a proof of ownership (#87 B2). A SYMLINK at the unit
|
|
132
|
+
# path is always a refusal too: writing through it would mutate somebody else's tree.
|
|
133
|
+
if [ "$ASSEMBLE_ONLY" -eq 0 ]; then
|
|
134
|
+
if [ -L "$STATE_FILE" ]; then
|
|
135
|
+
die "ownership state $STATE_FILE is a symlink — refusing to trust or overwrite it."
|
|
136
|
+
fi
|
|
137
|
+
if [ -L "$UNIT_DIR" ]; then
|
|
138
|
+
die "$UNIT_DIR is a SYMLINK. entwurf never writes through a link into a tree it does not own — remove it by hand if it is stale, then re-run."
|
|
139
|
+
fi
|
|
140
|
+
if [ -f "$STATE_FILE" ]; then
|
|
141
|
+
omp_state_read "$STATE_FILE" "$UNIT_DIR" "$ASM" >/dev/null \
|
|
142
|
+
|| die "ownership state $STATE_FILE is corrupt or names a different installation (see above) — refusing with zero writes."
|
|
143
|
+
echo "[omp-receive-install] ownership state matches this installation (reinstall/repair)"
|
|
144
|
+
elif [ -e "$UNIT_DIR" ]; then
|
|
145
|
+
# NO SHAPE-BASED ADOPTION (#87 B2). A structural oracle answers "is this a complete
|
|
146
|
+
# unit", never "is this OURS": it compares no bytes, rejects no extra files and
|
|
147
|
+
# establishes no provenance. Adopting on that answer meant moving a stranger's
|
|
148
|
+
# directory aside, publishing over it and DELETING the preimage — and the inverse
|
|
149
|
+
# then `rm -rf`s the path on the same unproven claim. A foreign or hand-made
|
|
150
|
+
# extension with these filenames, or our own unit plus operator files beside it, was
|
|
151
|
+
# destroyed unrecoverably. Ownership state is the only proof of ownership, so a
|
|
152
|
+
# no-state path refuses with zero writes.
|
|
153
|
+
die "$UNIT_DIR already exists and entwurf holds NO ownership state for it. A directory that merely LOOKS like our unit is not proof that it is ours — adopting it would overwrite it and delete the preimage with no inverse. Refusing with zero writes: inspect it, and if it is stale remove it by hand, then re-run."
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
# ── 1. assemble a self-contained unit ────────────────────────────────────────
|
|
158
|
+
# ATOMIC: assemble beside the live artifact and swap only once it is complete, so a
|
|
159
|
+
# failure never leaves a half-written extension for omp to import on the next launch.
|
|
160
|
+
STAGE="$ASM.staging.$$"
|
|
161
|
+
rm -rf "$STAGE"
|
|
162
|
+
trap 'rm -rf "$STAGE"' EXIT
|
|
163
|
+
mkdir -p "$STAGE/$UNIT/lib"
|
|
164
|
+
cp "$SRC/$UNIT/package.json" "$STAGE/$UNIT/package.json"
|
|
165
|
+
cp "$ENTRY_SRC" "$STAGE/$UNIT/$ENTRY_NAME"
|
|
166
|
+
cp "$LIB_SRC" "$STAGE/$UNIT/lib/meta-session.$LIB_EXT"
|
|
167
|
+
cp "$REPO/pi-extensions/lib/session-id.js" "$STAGE/$UNIT/lib/session-id.js"
|
|
168
|
+
# The capability registry must travel at the unit ROOT: metaCapabilitiesFilePath()
|
|
169
|
+
# resolves it via `../` from lib/ in the bundle layout. Without it every mint throws.
|
|
170
|
+
cp "$REPO/pi/entwurf-capabilities.json" "$STAGE/$UNIT/entwurf-capabilities.json"
|
|
171
|
+
|
|
172
|
+
omp_assembly_valid "$STAGE" "$UNIT" || die "the freshly staged assembly fails the structural oracle (see above) — refusing to publish it."
|
|
173
|
+
|
|
174
|
+
PREV=""
|
|
175
|
+
if [ -d "$ASM" ]; then
|
|
176
|
+
PREV="$ASM.previous.$$"
|
|
177
|
+
mv "$ASM" "$PREV" || die "could not move the previous assembly aside ($ASM)."
|
|
178
|
+
fi
|
|
179
|
+
mkdir -p "$(dirname "$ASM")"
|
|
180
|
+
if ! mv "$STAGE" "$ASM"; then
|
|
181
|
+
[ -n "$PREV" ] && mv "$PREV" "$ASM"
|
|
182
|
+
die "could not publish the staged assembly into $ASM (previous assembly restored)."
|
|
183
|
+
fi
|
|
184
|
+
trap - EXIT
|
|
185
|
+
[ -n "$PREV" ] && rm -rf "$PREV"
|
|
186
|
+
|
|
187
|
+
echo "[omp-receive-install] assembled $ASM (receiver extension only; birth is install-omp-bridge, the MCP hand is install-omp-mcp)"
|
|
188
|
+
|
|
189
|
+
if [ "$ASSEMBLE_ONLY" -eq 1 ]; then
|
|
190
|
+
echo "[omp-receive-install] --assemble-only: stopping before the omp agent directory is touched"
|
|
191
|
+
exit 0
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
# ── 2. place it where omp discovers it ───────────────────────────────────────
|
|
195
|
+
# Ownership state BEFORE the vendor-directory mutation: once the complete assembly is
|
|
196
|
+
# published, the state exists so a partial failure below still leaves an inverse
|
|
197
|
+
# authority behind. `ownedUnitDir` flips false→true around the placement.
|
|
198
|
+
write_state false
|
|
199
|
+
echo "[omp-receive-install] ownership state written: $STATE_FILE"
|
|
200
|
+
|
|
201
|
+
mkdir -p "$(dirname "$UNIT_DIR")"
|
|
202
|
+
PLACE_STAGE="$UNIT_DIR.staging.$$"
|
|
203
|
+
rm -rf "$PLACE_STAGE"
|
|
204
|
+
cp -r "$ASM/$UNIT" "$PLACE_STAGE" || die "could not stage the unit into $(dirname "$UNIT_DIR")."
|
|
205
|
+
PREV_UNIT=""
|
|
206
|
+
if [ -d "$UNIT_DIR" ]; then
|
|
207
|
+
PREV_UNIT="$UNIT_DIR.previous.$$"
|
|
208
|
+
mv "$UNIT_DIR" "$PREV_UNIT" || { rm -rf "$PLACE_STAGE"; die "could not move the previous unit aside ($UNIT_DIR)."; }
|
|
209
|
+
fi
|
|
210
|
+
if ! mv "$PLACE_STAGE" "$UNIT_DIR"; then
|
|
211
|
+
[ -n "$PREV_UNIT" ] && mv "$PREV_UNIT" "$UNIT_DIR"
|
|
212
|
+
rm -rf "$PLACE_STAGE"
|
|
213
|
+
die "could not publish the unit into $UNIT_DIR (previous unit restored)."
|
|
214
|
+
fi
|
|
215
|
+
[ -n "$PREV_UNIT" ] && rm -rf "$PREV_UNIT"
|
|
216
|
+
write_state true
|
|
217
|
+
|
|
218
|
+
omp_assembly_valid "$(dirname "$UNIT_DIR")" "$UNIT" || die "the placed unit fails the structural oracle (see above)."
|
|
219
|
+
|
|
220
|
+
echo "[omp-receive-install] installed $UNIT_DIR (entry $ENTRY_NAME, version $UNIT_VERSION)"
|
|
221
|
+
echo "[omp-receive-install] DONE. An omp TUI session becomes ADDRESSABLE once this unit"
|
|
222
|
+
echo "arms: it joins the citizen birth minted in the same process, holds a watch on that"
|
|
223
|
+
echo "citizen's mailbox signal, and rings a doorbell the model drains with"
|
|
224
|
+
echo "entwurf_inbox_read. Until it arms, dispatch to that garden id is still the honest"
|
|
225
|
+
echo "mailbox-undeliverable refusal, which is what an unarmed receiver SHOULD look like."
|
|
226
|
+
echo "Requires the birth unit (install-omp-bridge) and the tool hand (install-omp-mcp):"
|
|
227
|
+
echo "without the hand the model has no entwurf_inbox_read to drain with."
|
|
228
|
+
echo "Verify with: ./run.sh doctor-omp-receive · inverse: ./run.sh uninstall-omp-receive"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# omp-receive-uninstall.sh — the honest inverse of install-omp-receive (#87).
|
|
3
|
+
#
|
|
4
|
+
# HONEST means: remove exactly what the ownership state says this repo installed, and
|
|
5
|
+
# nothing else. No `--force`, no "clean up anything that looks like ours", no removal of
|
|
6
|
+
# a unit directory we cannot prove we placed. A no-state host is NOT an empty host: it is
|
|
7
|
+
# a host whose ownership we cannot establish, so the answer is a named refusal.
|
|
8
|
+
#
|
|
9
|
+
# The state is deleted LAST, so an interrupted inverse leaves the authority to retry.
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
UNIT="entwurf-receive-omp"
|
|
14
|
+
ASM="${ENTWURF_OMP_RECEIVE_ASM:-${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/omp-receive/.assembled}"
|
|
15
|
+
STATE_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/omp-receive"
|
|
16
|
+
STATE_FILE="$STATE_DIR/install-state.json"
|
|
17
|
+
|
|
18
|
+
die() { echo "[omp-receive-uninstall] $*" >&2; exit 1; }
|
|
19
|
+
|
|
20
|
+
# shellcheck source=scripts/omp-bridge-oracle.sh
|
|
21
|
+
. "$HERE/omp-bridge-oracle.sh"
|
|
22
|
+
|
|
23
|
+
AGENT_DIR="$(omp_agent_dir)" || die "refusing: the omp agent directory this host reads is ambiguous (see above)."
|
|
24
|
+
UNIT_DIR="$AGENT_DIR/extensions/$UNIT"
|
|
25
|
+
|
|
26
|
+
[ -L "$STATE_FILE" ] && die "ownership state $STATE_FILE is a symlink — refusing to trust it."
|
|
27
|
+
if [ ! -f "$STATE_FILE" ]; then
|
|
28
|
+
die "no ownership state at $STATE_FILE, so nothing here is provably ours. Refusing with zero writes — if $UNIT_DIR is a stale hand-copied unit, remove it by hand."
|
|
29
|
+
fi
|
|
30
|
+
STATE_FACTS="$(omp_state_read "$STATE_FILE" "$UNIT_DIR" "$ASM")" \
|
|
31
|
+
|| die "ownership state $STATE_FILE is corrupt or names a different installation (see above) — refusing with zero writes."
|
|
32
|
+
STATE_ENTRY="${STATE_FACTS#* }"
|
|
33
|
+
|
|
34
|
+
if [ -e "$UNIT_DIR" ]; then
|
|
35
|
+
if [ -L "$UNIT_DIR" ]; then
|
|
36
|
+
die "$UNIT_DIR is a SYMLINK — this installer never creates one, so it is not ours. Refusing; remove it by hand."
|
|
37
|
+
fi
|
|
38
|
+
if [ ! -f "$UNIT_DIR/$STATE_ENTRY" ]; then
|
|
39
|
+
die "$UNIT_DIR exists but does not carry the entry this install recorded ($STATE_ENTRY) — it is not provably the unit we placed. Refusing with zero writes."
|
|
40
|
+
fi
|
|
41
|
+
rm -rf "$UNIT_DIR"
|
|
42
|
+
echo "[omp-receive-uninstall] removed the installed unit: $UNIT_DIR"
|
|
43
|
+
else
|
|
44
|
+
echo "[omp-receive-uninstall] the installed unit was already absent: $UNIT_DIR"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
if [ -d "$ASM" ]; then
|
|
48
|
+
rm -rf "$ASM"
|
|
49
|
+
echo "[omp-receive-uninstall] removed the assembly: $ASM"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
rm -f "$STATE_FILE"
|
|
53
|
+
rmdir "$STATE_DIR" 2>/dev/null || true
|
|
54
|
+
echo "[omp-receive-uninstall] ownership state removed: $STATE_FILE"
|
|
55
|
+
echo "[omp-receive-uninstall] DONE. Records and sender identity are untouched — this"
|
|
56
|
+
echo "inverse removes only the DOORBELL. A live omp session that already armed keeps its"
|
|
57
|
+
echo "receiver marker until its process exits (the start-key guard retires it then), so"
|
|
58
|
+
echo "an omp TUI still open right now may remain addressable until you close it."
|
|
59
|
+
echo "Birth and the MCP hand are separate inverses: ./run.sh uninstall-omp-bridge ·"
|
|
60
|
+
echo "./run.sh uninstall-omp-mcp"
|