@junghanacs/entwurf 0.12.8 → 0.12.9
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 +4 -4
- package/CHANGELOG.md +20 -1
- package/DELIVERY.md +1 -1
- package/README.md +122 -10
- package/demo/README.md +2 -2
- package/docs/setup-clean-host.md +14 -3
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +9 -15
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +12 -12
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +8 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +15 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +483 -34
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +8 -3
- package/mcp/entwurf-bridge/dist/scripts/agy-imprint.js +14 -2
- package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +155 -29
- package/mcp/entwurf-bridge/src/index.ts +10 -12
- package/package.json +7 -7
- package/pi-extensions/entwurf-control.ts +12 -12
- package/pi-extensions/lib/acp/models.ts +12 -12
- package/pi-extensions/lib/entwurf-fact-provider.ts +9 -2
- package/pi-extensions/lib/entwurf-v2-production.ts +8 -2
- package/pi-extensions/lib/entwurf-v2-spawn-production.ts +8 -2
- package/pi-extensions/lib/meta-sender-identity.ts +15 -5
- package/pi-extensions/lib/meta-session.ts +526 -38
- package/pi-extensions/meta-bridge-hook.ts +8 -2
- package/run.sh +30 -26
- package/scripts/agy-imprint.ts +15 -1
- package/scripts/check-acp-session-reuse.ts +1 -1
- package/scripts/check-acp-session-store.ts +3 -3
- package/scripts/check-agy-sender-identity.ts +83 -0
- package/scripts/check-entwurf-control-rpc.ts +2 -2
- package/scripts/check-entwurf-fact-provider.ts +9 -1
- package/scripts/check-entwurf-facts.ts +1 -1
- package/scripts/check-entwurf-mailbox-guard.ts +6 -2
- package/scripts/check-entwurf-resume-args.ts +6 -6
- package/scripts/check-entwurf-session-identity.ts +7 -6
- package/scripts/check-entwurf-v2-production.ts +4 -2
- package/scripts/check-entwurf-v2-spawn-production.ts +2 -2
- package/scripts/check-fresh-cut-gate.sh +305 -4
- package/scripts/check-meta-identity-consumers.ts +501 -1
- package/scripts/check-meta-listing.ts +91 -9
- package/scripts/check-meta-receiver-marker.ts +54 -0
- package/scripts/check-model-lock.ts +1 -1
- package/scripts/meta-bridge-fresh-cut.ts +164 -28
- package/scripts/pi_settings_io.py +65 -0
- package/scripts/register-pi-package.py +183 -37
- package/scripts/register-pi-provider.py +68 -10
- package/scripts/smoke-acp-socket-citizen-live.ts +2 -2
- package/scripts/smoke-meta-install-state.sh +1 -1
- package/scripts/smoke-pi-attach.ts +7 -2
- package/scripts/smoke-user-scope-citizen.sh +177 -0
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
*/
|
|
46
46
|
import * as fs from "node:fs";
|
|
47
47
|
import * as path from "node:path";
|
|
48
|
-
import { defaultMetaMailboxDir, defaultMetaSessionsDir, upsertMetaSession, writeMetaReceiverMarker, writeMetaSenderMarker, } from "./lib/meta-session.js";
|
|
48
|
+
import { defaultMetaMailboxDir, defaultMetaSessionsDir, isPlausibleOwnerPid, upsertMetaSession, writeMetaReceiverMarker, writeMetaSenderMarker, } from "./lib/meta-session.js";
|
|
49
49
|
function logLine(level, message) {
|
|
50
50
|
try {
|
|
51
51
|
// dirname(meta-sessions) == the pi agent dir — no extra resolver export needed.
|
|
@@ -117,7 +117,12 @@ const META_HOOK_LAUNCH_TOKEN = "hook-launch/v1";
|
|
|
117
117
|
* no token means we do not know what our parent is, so we claim nothing.
|
|
118
118
|
* 2. A PLAUSIBLE LIVE PARENT. A reparented orphan (ppid 0/1) is not an owner, and
|
|
119
119
|
* minting a marker for init would be exactly the "blind ancestor" false-positive
|
|
120
|
-
* the old ancestry walk existed to prevent.
|
|
120
|
+
* the old ancestry walk existed to prevent. The rule is `isPlausibleOwnerPid`,
|
|
121
|
+
* shared with the other writer and with every reader: this side already refused
|
|
122
|
+
* what all three readers accepted, and one host's leftover `ownerPid: 1` marker
|
|
123
|
+
* then blocked its fresh-cut until the file was deleted by hand (#53 A). A
|
|
124
|
+
* predicate only one layer knows is how that drift happened, so it is no longer
|
|
125
|
+
* written out by hand here.
|
|
121
126
|
*
|
|
122
127
|
* Failing closed costs only reply-addressability, and the doctor sees the ERROR. The
|
|
123
128
|
* opposite — a marker keyed to a transient wrapper — is a lie a sender acts on.
|
|
@@ -126,7 +131,7 @@ function resolveMetaHookOwnerPid() {
|
|
|
126
131
|
if (process.env[META_HOOK_LAUNCH_ENV] !== META_HOOK_LAUNCH_TOKEN)
|
|
127
132
|
return null;
|
|
128
133
|
const ownerPid = process.ppid;
|
|
129
|
-
if (!
|
|
134
|
+
if (!isPlausibleOwnerPid(ownerPid))
|
|
130
135
|
return null;
|
|
131
136
|
return ownerPid;
|
|
132
137
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import * as fs from "node:fs";
|
|
16
16
|
import * as os from "node:os";
|
|
17
17
|
import * as path from "node:path";
|
|
18
|
-
import { parentPid, processStartKey, upsertMetaSession, writeMetaSenderMarker, } from "../pi-extensions/lib/meta-session.js";
|
|
18
|
+
import { isPlausibleOwnerPid, parentPid, processStartKey, upsertMetaSession, writeMetaSenderMarker, } from "../pi-extensions/lib/meta-session.js";
|
|
19
19
|
const NEUTRAL_RESPONSE = '{"injectSteps":[]}';
|
|
20
20
|
function logPath() {
|
|
21
21
|
const root = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
|
|
@@ -141,8 +141,16 @@ function imprint(raw) {
|
|
|
141
141
|
// Written only after the upsert above: the record store is the identity authority, and a
|
|
142
142
|
// marker pointing at a garden-id with no record would be a window of un-backed identity.
|
|
143
143
|
// A failed marker costs reply-addressability, never the session — log and move on.
|
|
144
|
+
//
|
|
145
|
+
// The owner rule is `isPlausibleOwnerPid`, the SAME predicate the Claude hook
|
|
146
|
+
// and every reader hold. This writer used to ask `> 0` on its own, so THIS hook
|
|
147
|
+
// could mint `meta-senders/antigravity/1.json` through the very reparenting that
|
|
148
|
+
// stranded a host's fresh-cut (#53 A): agy exits, the imprint is reparented to
|
|
149
|
+
// init, and `process.ppid` reads 1. Refusing is fail-closed — it costs reply
|
|
150
|
+
// addressability for that turn and is logged; honoring it would cost the operator
|
|
151
|
+
// their generation cut, with no in-band way to get it back.
|
|
144
152
|
const ownerPid = process.ppid;
|
|
145
|
-
if (
|
|
153
|
+
if (isPlausibleOwnerPid(ownerPid)) {
|
|
146
154
|
try {
|
|
147
155
|
writeMetaSenderMarker({
|
|
148
156
|
backend: "antigravity",
|
|
@@ -157,6 +165,10 @@ function imprint(raw) {
|
|
|
157
165
|
logLine(`sender-marker-failed pid=${ownerPid} gardenId=${result.record.gardenId} ${err instanceof Error ? err.message : String(err)}`);
|
|
158
166
|
}
|
|
159
167
|
}
|
|
168
|
+
else {
|
|
169
|
+
logLine(`sender-marker-refused pid=${ownerPid} gardenId=${result.record.gardenId} implausible owner pid ` +
|
|
170
|
+
"(<= 1: this imprint was reparented, so its parent is not the agy process that owns the turn)");
|
|
171
|
+
}
|
|
160
172
|
}
|
|
161
173
|
catch (err) {
|
|
162
174
|
logLine(`upsert-failed conversationId=${conversationId} ${err instanceof Error ? err.stack || err.message : String(err)}`);
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
* - meta-sessions/ (identity records) → ARCHIVED (`<dir>.archive-<ts>`)
|
|
25
25
|
* - meta-mailbox/ (messages+receipts) → ARCHIVED (`<dir>.archive-<ts>`)
|
|
26
26
|
* - meta-senders/, meta-receivers/ markers → CLEARED (disposable process
|
|
27
|
-
* state; the quiesce gate has already proven every owner pid is gone
|
|
27
|
+
* state; the quiesce gate has already proven every owner pid is gone — or,
|
|
28
|
+
* for a refuted marker, that no such owner could ever have existed)
|
|
28
29
|
* - control sockets (*.sock) → CLEARED if probed dead (the GC
|
|
29
30
|
* rule: only a demonstrably dead socket may be unlinked)
|
|
30
31
|
* - native transcripts, andenken/embedding axes, install ownership state
|
|
@@ -50,6 +51,28 @@
|
|
|
50
51
|
* Only a marker whose named owner demonstrably no longer holds that pid is dead,
|
|
51
52
|
* and only a dead marker/socket is cleared.
|
|
52
53
|
*
|
|
54
|
+
* ONE class is neither live, uncertain nor dead: a marker whose `ownerPid` cannot
|
|
55
|
+
* own anything (`<= 1` — init, or a non-pid number). It is REFUTED BY CONSTRUCTION
|
|
56
|
+
* rather than proven dead: after #53 A no writer in this tree can mint one
|
|
57
|
+
* (`isPlausibleOwnerPid` at both writers and at the write boundary), so on a current
|
|
58
|
+
* install it is LEGACY or CORRUPT residue — a pre-fix writer whose parent had been
|
|
59
|
+
* reparented to init (the retired shell-form Claude hook; the agy imprint, which
|
|
60
|
+
* asked only `> 0` until this cut), or a foreign/damaged marker, which is the only
|
|
61
|
+
* way a non-integer pid can appear at all. The one file actually observed was a
|
|
62
|
+
* shell-form Claude hook reparented to init.
|
|
63
|
+
*
|
|
64
|
+
* Reading it as an owner is not merely wrong, it is a trap: init runs for the whole
|
|
65
|
+
* boot and its start-key does not change while it does, so `classifyMarkerOwner`
|
|
66
|
+
* answers `live` and THE ACTION THIS REFUSAL PRESCRIBES CANNOT CHANGE THAT — the
|
|
67
|
+
* operator quiesces every session, exactly as told, and the cut refuses again.
|
|
68
|
+
* (Deleting the marker removes the claim rather than refuting the verdict, and a
|
|
69
|
+
* reboot recomputes the key with no contract either way — neither is a remedy this
|
|
70
|
+
* cut may lean on.) Meanwhile 0.12.8 names this very cut as the one repair for a
|
|
71
|
+
* pre-v3 store, so the host was stuck until the file was deleted by hand (#53 A,
|
|
72
|
+
* measured 2026-07-25). Such a marker is therefore clearable residue, swept with the
|
|
73
|
+
* dead ones but COUNTED AND REPORTED APART from them, because invalidity and death
|
|
74
|
+
* are different findings.
|
|
75
|
+
*
|
|
53
76
|
* THE MARKER WALK IS NOT THE WHOLE WORLD. A native-push (agy) citizen is registered
|
|
54
77
|
* with NO marker of any kind and is dispatched straight off its record, so marker
|
|
55
78
|
* absence is its NORMAL deliverable state — a socket+marker scan would call such a host
|
|
@@ -69,13 +92,22 @@
|
|
|
69
92
|
* renames reports exactly what already moved (`archived so far:`) and a re-run
|
|
70
93
|
* finishes the cut with its own stamp.
|
|
71
94
|
* Running on a clean/empty host just opens a fresh generation and says so.
|
|
95
|
+
*
|
|
96
|
+
* EXIT CONTRACT (#54) — `FRESH_CUT_EXIT`, defined beside the verb's own name in
|
|
97
|
+
* meta-session.ts because a runbook and a gate must read the same numbers:
|
|
98
|
+
* `0` complete · `1` nothing moved · `2` usage · `3` cut transition incomplete ·
|
|
99
|
+
* `4` cut complete but marker/socket cleanup incomplete. The three failure states
|
|
100
|
+
* were all hidden behind one `1` before, which is why the documented `fresh-cut && setup` chain stopped
|
|
101
|
+
* identically on a refusal that changed nothing and on a cut that had already
|
|
102
|
+
* unblocked the install. Only `0` is success — #54 asked for the states to become
|
|
103
|
+
* DISTINGUISHABLE, not for a failed sweep to become a pass.
|
|
72
104
|
*/
|
|
73
105
|
import * as fs from "node:fs";
|
|
74
106
|
import * as os from "node:os";
|
|
75
107
|
import * as path from "node:path";
|
|
76
108
|
import { CONTROL_SOCKET_SUFFIX, defaultControlSocketDir } from "../pi-extensions/lib/control-socket-path.js";
|
|
77
109
|
import { nativePushSupported } from "../pi-extensions/lib/entwurf-v2-contract.js";
|
|
78
|
-
import { classifyMarkerOwner, defaultMetaMailboxDir, defaultMetaReceiversDir, defaultMetaSendersDir, defaultMetaSessionsDir, parseMetaIdentity, probePidExistence, processStartKey, } from "../pi-extensions/lib/meta-session.js";
|
|
110
|
+
import { classifyMarkerOwner, defaultMetaMailboxDir, defaultMetaReceiversDir, defaultMetaSendersDir, defaultMetaSessionsDir, FRESH_CUT_EXIT, isPlausibleOwnerPid, midCutExit, parseMetaIdentity, probePidExistence, processStartKey, } from "../pi-extensions/lib/meta-session.js";
|
|
79
111
|
import { resolveNativePushAdapter } from "../pi-extensions/lib/native-push/adapter.js";
|
|
80
112
|
import { probeSocketLiveness } from "../pi-extensions/lib/socket-probe.js";
|
|
81
113
|
function usage(code) {
|
|
@@ -91,6 +123,20 @@ function usage(code) {
|
|
|
91
123
|
"store = ENTWURF_META_SESSIONS_DIR || <PI_CODING_AGENT_DIR|~/.pi/agent>/meta-sessions",
|
|
92
124
|
"mailbox = ENTWURF_META_MAILBOX_DIR || <PI_CODING_AGENT_DIR|~/.pi/agent>/meta-mailbox",
|
|
93
125
|
"sockets = ENTWURF_DIR || ~/.pi/entwurf-control",
|
|
126
|
+
"",
|
|
127
|
+
"EXIT CONTRACT (#54) — what already moved, not how bad it was:",
|
|
128
|
+
` ${FRESH_CUT_EXIT.COMPLETE} cut complete: generation archived (or none existed), fresh v3 store open,`,
|
|
129
|
+
" residue cleared. `&& setup` is correct here.",
|
|
130
|
+
` ${FRESH_CUT_EXIT.NO_MOVE} NOTHING MOVED: a live/unprovable surface, an occupied archive destination,`,
|
|
131
|
+
" an unreadable surface, or a first rename that failed. The host is unchanged —",
|
|
132
|
+
" fix the named cause and re-run. Do NOT run setup: the store it refused is still there.",
|
|
133
|
+
` ${FRESH_CUT_EXIT.USAGE} usage.`,
|
|
134
|
+
` ${FRESH_CUT_EXIT.HALF_CUT} CUT TRANSITION INCOMPLETE: at least one archive move happened,`,
|
|
135
|
+
" but the fresh generation is not confirmed open. Inspect, or re-run to finish under a new stamp.",
|
|
136
|
+
` ${FRESH_CUT_EXIT.CLEANUP_INCOMPLETE} CUT COMPLETE, CLEANUP INCOMPLETE: the generation IS archived and the fresh`,
|
|
137
|
+
" generation IS open — install and citizen birth are unblocked, so setup may run —",
|
|
138
|
+
" but marker/socket residue survived. Prefer fixing it and re-running BEFORE setup; after new citizen birth,",
|
|
139
|
+
" remove the named residue manually instead of archiving the new generation with another cut.",
|
|
94
140
|
].join("\n"));
|
|
95
141
|
process.exit(code);
|
|
96
142
|
}
|
|
@@ -163,16 +209,17 @@ function classifySurfaceDir(dir) {
|
|
|
163
209
|
function inspectMarkers(dir, label) {
|
|
164
210
|
const violations = [];
|
|
165
211
|
const deadFiles = [];
|
|
212
|
+
const refutedFiles = [];
|
|
166
213
|
const markerFiles = [];
|
|
167
214
|
const uncertain = (shown, why) => {
|
|
168
215
|
violations.push({ surface: label, detail: `${shown} — ${why}`, kind: "uncertain" });
|
|
169
216
|
};
|
|
170
217
|
const dirState = classifySurfaceDir(dir);
|
|
171
218
|
if (dirState.state === "absent")
|
|
172
|
-
return { violations, deadFiles };
|
|
219
|
+
return { violations, deadFiles, refutedFiles };
|
|
173
220
|
if (dirState.state !== "directory") {
|
|
174
221
|
uncertain(dir, `surface could not be inspected (${dirState.detail}): an unreadable surface is not a quiesced one`);
|
|
175
|
-
return { violations, deadFiles };
|
|
222
|
+
return { violations, deadFiles, refutedFiles };
|
|
176
223
|
}
|
|
177
224
|
// depth 0 = the marker root, depth 1 = a backend subdir. Nothing deeper is part
|
|
178
225
|
// of any marker layout, so it is inspected by hand rather than swept.
|
|
@@ -213,12 +260,28 @@ function inspectMarkers(dir, label) {
|
|
|
213
260
|
uncertain(shown, `unreadable marker (${err instanceof Error ? err.message : String(err)}): owner unprovable`);
|
|
214
261
|
continue;
|
|
215
262
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
263
|
+
// Two different failures, deliberately kept apart. NO numeric `ownerPid` means the
|
|
264
|
+
// marker never NAMED an owner — unprovable, so it refuses the cut like every other
|
|
265
|
+
// unreadable marker. A numeric one that cannot own (`<= 1`, non-integer, unsafe)
|
|
266
|
+
// IS a claim, and it is refuted BY CONSTRUCTION: no writer in this tree can mint it
|
|
267
|
+
// any more, and on the axis this repo certifies "this session is owned by init" is
|
|
268
|
+
// false on its face (a container harness running AS pid 1 is a real but UNSUPPORTED
|
|
269
|
+
// shape — see isPlausibleOwnerPid; it fails closed at the writers). That is a
|
|
270
|
+
// proof of INVALIDITY — strictly stronger than the proof of death this loop already
|
|
271
|
+
// acts on — so it is clearable residue, never `live` and never `uncertain`. Without
|
|
272
|
+
// this row a single reparented-owner marker left quiescence unprovable by the ONE
|
|
273
|
+
// action the refusal prescribes, and the repair 0.12.8 names could not run until
|
|
274
|
+
// the file was deleted by hand (#53 A, measured 2026-07-25).
|
|
275
|
+
if (typeof raw.ownerPid !== "number") {
|
|
276
|
+
uncertain(shown, "no numeric `ownerPid`: owner unprovable");
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (!isPlausibleOwnerPid(raw.ownerPid)) {
|
|
280
|
+
refutedFiles.push(file);
|
|
220
281
|
continue;
|
|
221
282
|
}
|
|
283
|
+
const ownerPid = raw.ownerPid;
|
|
284
|
+
const ownerStartKey = typeof raw.ownerStartKey === "string" ? raw.ownerStartKey : "";
|
|
222
285
|
if (ownerStartKey === "") {
|
|
223
286
|
uncertain(shown, "no `ownerStartKey`: a bare pid cannot distinguish the owner from a reused pid");
|
|
224
287
|
continue;
|
|
@@ -242,7 +305,7 @@ function inspectMarkers(dir, label) {
|
|
|
242
305
|
deadFiles.push(file);
|
|
243
306
|
}
|
|
244
307
|
}
|
|
245
|
-
return { violations, deadFiles };
|
|
308
|
+
return { violations, deadFiles, refutedFiles };
|
|
246
309
|
}
|
|
247
310
|
/**
|
|
248
311
|
* The native-push surface — the ONE live citizen kind that leaves no marker behind.
|
|
@@ -266,7 +329,8 @@ function inspectMarkers(dir, label) {
|
|
|
266
329
|
* record this walk skips, and the claim is deliberately narrow: NOT "that native session
|
|
267
330
|
* has exited", only "these bytes are not an address authority in the live runtime, so
|
|
268
331
|
* they front no current-generation garden surface". Every path that ADDRESSES a citizen
|
|
269
|
-
* goes through the live schema — `
|
|
332
|
+
* goes through the live schema — `readAddressableMetaIdentity` (v2 `resolveTarget` and
|
|
333
|
+
* the pi resume), `readMetaIdentityByGardenId` (the relay reads),
|
|
270
334
|
* the sender-marker trust — and each THROWS on a record this parser refuses, so nothing
|
|
271
335
|
* can dispatch to it. (`entwurf_peers` still LISTS it as a diagnostic; a facts surface
|
|
272
336
|
* reporting what it could not read is not an address.) A record we CAN read is the
|
|
@@ -352,12 +416,41 @@ async function inspectNativePushCitizens(storeDir) {
|
|
|
352
416
|
}
|
|
353
417
|
return violations;
|
|
354
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* Remove the files a completed cut is contracted to clear, and report what it could
|
|
421
|
+
* not. ENOENT is the ONLY tolerated failure — the file raced away, which is the goal
|
|
422
|
+
* state. Every other errno is a real refusal to delete (EACCES, EPERM under a
|
|
423
|
+
* sticky-bit parent, EROFS, an immutable attribute, an LSM denial), and a bare
|
|
424
|
+
* `catch {}` here would launder all of them into "raced away" while the command's own
|
|
425
|
+
* output still said `cleared:`. That is the Crash-Don't-Warn shape this repo removes,
|
|
426
|
+
* not a shortcut it tolerates — the caller decides what to do with the failures,
|
|
427
|
+
* because by this point the archive has already moved and throwing would cost the
|
|
428
|
+
* operator the story of what DID happen.
|
|
429
|
+
*/
|
|
430
|
+
function clearFiles(files) {
|
|
431
|
+
const failures = [];
|
|
432
|
+
let cleared = 0;
|
|
433
|
+
for (const file of files) {
|
|
434
|
+
try {
|
|
435
|
+
fs.unlinkSync(file);
|
|
436
|
+
cleared += 1;
|
|
437
|
+
}
|
|
438
|
+
catch (err) {
|
|
439
|
+
if (err.code === "ENOENT")
|
|
440
|
+
continue; // raced away — already gone is the goal state
|
|
441
|
+
failures.push({ file, reason: err instanceof Error ? err.message : String(err) });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return { cleared, failures };
|
|
445
|
+
}
|
|
446
|
+
/** Surfaces this process has already renamed into an archive — see the rename loop. */
|
|
447
|
+
let archivedSoFar = 0;
|
|
355
448
|
async function main() {
|
|
356
449
|
const args = process.argv.slice(2);
|
|
357
450
|
if (args.includes("-h") || args.includes("--help"))
|
|
358
|
-
usage(
|
|
451
|
+
usage(FRESH_CUT_EXIT.COMPLETE);
|
|
359
452
|
if (args.length > 0)
|
|
360
|
-
usage(
|
|
453
|
+
usage(FRESH_CUT_EXIT.USAGE);
|
|
361
454
|
const storeDir = defaultMetaSessionsDir();
|
|
362
455
|
const mailboxDir = defaultMetaMailboxDir();
|
|
363
456
|
const sendersDir = defaultMetaSendersDir();
|
|
@@ -438,7 +531,7 @@ async function main() {
|
|
|
438
531
|
"address archived out from under it, and an unprovable owner is not a dead one. " +
|
|
439
532
|
"Quiesce those sessions (close them / let them exit), inspect anything listed UNCERTAIN, " +
|
|
440
533
|
"then re-run the same command. Nothing was moved.");
|
|
441
|
-
return
|
|
534
|
+
return FRESH_CUT_EXIT.NO_MOVE;
|
|
442
535
|
}
|
|
443
536
|
// ── the cut: plan the moves, preflight the whole plan, then rename ───────
|
|
444
537
|
const ts = stamp();
|
|
@@ -484,9 +577,15 @@ async function main() {
|
|
|
484
577
|
console.error(`REFUSE: ${collisions.length} archive destination(s) above already exist — this generation's stamp is ` +
|
|
485
578
|
"taken (a cut in the same second, or leftovers from an interrupted one). Nothing was moved: " +
|
|
486
579
|
"wait a second and re-run, or move those directories aside first.");
|
|
487
|
-
return
|
|
580
|
+
return FRESH_CUT_EXIT.NO_MOVE;
|
|
488
581
|
}
|
|
489
582
|
const archived = [];
|
|
583
|
+
// Mirrored to module scope for the ONE reader that cannot see this array: the
|
|
584
|
+
// top-level rejection handler. Everything from here on can still throw — the
|
|
585
|
+
// `mkdirSync` that opens the fresh generation, most obviously — and a handler that
|
|
586
|
+
// answered a flat `1` there would tell a runbook "nothing moved" about a host whose
|
|
587
|
+
// generation is already in an archive. A single-shot CLI may hold one fact in module
|
|
588
|
+
// scope when the alternative is an exit status that lies (#54).
|
|
490
589
|
for (const { src, dest } of plan) {
|
|
491
590
|
try {
|
|
492
591
|
fs.renameSync(src, dest);
|
|
@@ -510,20 +609,20 @@ async function main() {
|
|
|
510
609
|
"not. Fix the cause and re-run the same command — the re-run archives the remainder under its " +
|
|
511
610
|
"own stamp — or inspect by hand.");
|
|
512
611
|
}
|
|
513
|
-
|
|
612
|
+
// The prose above already distinguished these two; the STATUS did not, and a
|
|
613
|
+
// runbook reads the status (#54). `midCutExit` is the SSOT so the words and the
|
|
614
|
+
// number cannot drift apart.
|
|
615
|
+
return midCutExit(archived.length);
|
|
514
616
|
}
|
|
515
617
|
archived.push(dest);
|
|
618
|
+
archivedSoFar = archived.length;
|
|
516
619
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
catch {
|
|
524
|
-
// raced away — already gone is the goal state
|
|
525
|
-
}
|
|
526
|
-
}
|
|
620
|
+
// Both sweeps go through ONE remover so they cannot drift to different meanings of
|
|
621
|
+
// "could not remove". Counted APART, because a dead marker is an owner we PROVED
|
|
622
|
+
// left while a refuted one never named an owner that could exist, and folding the
|
|
623
|
+
// two would dress a proof of invalidity up as a proof of death.
|
|
624
|
+
const dead = clearFiles([...senders.deadFiles, ...receivers.deadFiles, ...deadSockets]);
|
|
625
|
+
const refuted = clearFiles([...senders.refutedFiles, ...receivers.refutedFiles]);
|
|
527
626
|
fs.mkdirSync(storeDir, { recursive: true });
|
|
528
627
|
fs.mkdirSync(mailboxDir, { recursive: true });
|
|
529
628
|
if (archived.length === 0) {
|
|
@@ -533,15 +632,42 @@ async function main() {
|
|
|
533
632
|
for (const dir of archived)
|
|
534
633
|
console.log(`archived: ${dir}`);
|
|
535
634
|
}
|
|
536
|
-
if (cleared > 0)
|
|
537
|
-
console.log(`cleared: ${cleared} dead marker/socket file(s) (disposable process state)`);
|
|
635
|
+
if (dead.cleared > 0)
|
|
636
|
+
console.log(`cleared: ${dead.cleared} dead marker/socket file(s) (disposable process state)`);
|
|
637
|
+
if (refuted.cleared > 0) {
|
|
638
|
+
console.log(`refuted: ${refuted.cleared} legacy/corrupt marker(s) named an owner pid that cannot own a session ` +
|
|
639
|
+
"(<= 1, or not a pid at all); REFUTED BY CONSTRUCTION, not proven dead, and cleared. The observed " +
|
|
640
|
+
"case was a hook reparented to init before it read its own parent.");
|
|
641
|
+
}
|
|
538
642
|
console.log(`fresh generation open: ${storeDir} (empty, v3-only)`);
|
|
539
643
|
console.log("untouched: native transcripts, andenken/embedding memory axes, install state. " +
|
|
540
644
|
"The archive is forensic only — no runtime reads it and there is no restore verb.");
|
|
541
|
-
|
|
645
|
+
// The generation IS cut by now, so a cleanup failure must not throw away that
|
|
646
|
+
// story — but it must not be swallowed either: this command's own report claims
|
|
647
|
+
// the residue was "cleared". Say exactly which files survived and why, keep the
|
|
648
|
+
// success lines above (this is not a half-cut), and exit nonzero so no caller
|
|
649
|
+
// reads a partial sweep as a clean one.
|
|
650
|
+
const cleanupFailures = [...dead.failures, ...refuted.failures];
|
|
651
|
+
if (cleanupFailures.length > 0) {
|
|
652
|
+
for (const { file, reason } of cleanupFailures)
|
|
653
|
+
console.error(`FAIL post-cut cleanup: ${file} — ${reason}`);
|
|
654
|
+
console.error(`FAIL post-cut cleanup: ${cleanupFailures.length} marker/socket file(s) above could NOT be removed. The ` +
|
|
655
|
+
"generation was archived and the fresh generation is open — this is not a half-cut, and install/citizen " +
|
|
656
|
+
"birth are no longer blocked by the store. What survived is disposable process state sitting in the new " +
|
|
657
|
+
"generation's surfaces, and it will refuse the NEXT cut. Fix the cause (permissions, a read-only mount, " +
|
|
658
|
+
`an immutable attribute) and re-run the same command BEFORE \`setup\`, or remove those files by hand. EXIT ` +
|
|
659
|
+
`${FRESH_CUT_EXIT.CLEANUP_INCOMPLETE}: the cut is DONE, so \`setup\` may proceed — but once a new citizen is ` +
|
|
660
|
+
"born, another fresh-cut would archive that new generation too; repair the named residue manually instead.");
|
|
661
|
+
return FRESH_CUT_EXIT.CLEANUP_INCOMPLETE;
|
|
662
|
+
}
|
|
663
|
+
return FRESH_CUT_EXIT.COMPLETE;
|
|
542
664
|
}
|
|
543
665
|
main().then((code) => process.exit(code), (err) => {
|
|
544
666
|
console.error(err instanceof Error ? (err.stack ?? err.message) : String(err));
|
|
545
667
|
console.error("FAIL: fresh-cut did not complete — the message above names the cause; re-run after fixing it.");
|
|
546
|
-
|
|
668
|
+
// Not a flat 1: the ONE thing a caller must know from a crash is whether the host
|
|
669
|
+
// still holds its generation. Every refusal that reaches here from before the first
|
|
670
|
+
// rename really is a no-op (its own message says so); anything after it is a
|
|
671
|
+
// an incomplete cut transition and must not be mistaken for a no-op (#54).
|
|
672
|
+
process.exit(midCutExit(archivedSoFar));
|
|
547
673
|
});
|
|
@@ -40,8 +40,7 @@
|
|
|
40
40
|
* - no user-specific paths baked in; env-configurable with safe defaults
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
|
-
import { existsSync
|
|
44
|
-
import * as fs from "node:fs/promises";
|
|
43
|
+
import { existsSync } from "node:fs";
|
|
45
44
|
import * as os from "node:os";
|
|
46
45
|
import * as path from "node:path";
|
|
47
46
|
import * as process from "node:process";
|
|
@@ -63,6 +62,8 @@ import {
|
|
|
63
62
|
import {
|
|
64
63
|
defaultMetaMailboxDir,
|
|
65
64
|
defaultMetaSessionsDir,
|
|
65
|
+
makeStoreRecordReader,
|
|
66
|
+
readActiveStoreEntries,
|
|
66
67
|
readMetaInbox,
|
|
67
68
|
readMetaReceiverMarker,
|
|
68
69
|
} from "../../../pi-extensions/lib/meta-session.ts";
|
|
@@ -441,18 +442,15 @@ server.tool(
|
|
|
441
442
|
{},
|
|
442
443
|
async () => {
|
|
443
444
|
try {
|
|
444
|
-
// Meta-store axis:
|
|
445
|
-
//
|
|
445
|
+
// Meta-store axis: entries WITH their kind (ENOENT = fresh install = empty; any
|
|
446
|
+
// other readdir failure is a real error, not a silent empty). The name-only
|
|
447
|
+
// readdir that used to live here made rule 1 unenforceable on this surface —
|
|
448
|
+
// `readRecord` would follow a symlinked `.meta.json` to bytes the store does not
|
|
449
|
+
// own, while the doctor refused that same entry. One store, one contract.
|
|
446
450
|
const sessionsDir = defaultMetaSessionsDir();
|
|
447
|
-
let metaEntries: string[] = [];
|
|
448
|
-
try {
|
|
449
|
-
metaEntries = (await fs.readdir(sessionsDir)).filter((n) => n.endsWith(".meta.json"));
|
|
450
|
-
} catch (err) {
|
|
451
|
-
if ((err as NodeJS.ErrnoException)?.code !== "ENOENT") throw err;
|
|
452
|
-
}
|
|
453
451
|
const result = await listEntwurfFacts({
|
|
454
|
-
metaEntries,
|
|
455
|
-
readRecord: (
|
|
452
|
+
metaEntries: readActiveStoreEntries(sessionsDir),
|
|
453
|
+
readRecord: makeStoreRecordReader(sessionsDir),
|
|
456
454
|
// Socket axis: the same dir dispatch uses (grammar SSOT), scan-internal only.
|
|
457
455
|
socket: { dir: ENTWURF_DIR },
|
|
458
456
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junghanacs/entwurf",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.9",
|
|
4
4
|
"description": "Garden-citizen dispatch substrate and meta-bridge for Claude Code, Codex, Antigravity, and pi harnesses.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -78,16 +78,16 @@
|
|
|
78
78
|
"zod": "^3.25.0 || ^4.0.0"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"@earendil-works/pi-ai": ">=0.82.
|
|
82
|
-
"@earendil-works/pi-coding-agent": ">=0.82.
|
|
83
|
-
"@earendil-works/pi-tui": ">=0.82.
|
|
81
|
+
"@earendil-works/pi-ai": ">=0.82.1 <0.83",
|
|
82
|
+
"@earendil-works/pi-coding-agent": ">=0.82.1 <0.83",
|
|
83
|
+
"@earendil-works/pi-tui": ">=0.82.1 <0.83",
|
|
84
84
|
"typebox": "*"
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@biomejs/biome": "^2.4.13",
|
|
88
|
-
"@earendil-works/pi-ai": "0.82.
|
|
89
|
-
"@earendil-works/pi-coding-agent": "0.82.
|
|
90
|
-
"@earendil-works/pi-tui": "0.82.
|
|
88
|
+
"@earendil-works/pi-ai": "0.82.1",
|
|
89
|
+
"@earendil-works/pi-coding-agent": "0.82.1",
|
|
90
|
+
"@earendil-works/pi-tui": "0.82.1",
|
|
91
91
|
"@types/node": "^24.3.0",
|
|
92
92
|
"husky": "^9.1.7",
|
|
93
93
|
"typescript": "^6.0.2"
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
* Send-is-throw cleanup; see note above.)
|
|
62
62
|
*/
|
|
63
63
|
|
|
64
|
-
import { existsSync, promises as fs
|
|
64
|
+
import { existsSync, promises as fs } from "node:fs";
|
|
65
65
|
import * as net from "node:net";
|
|
66
66
|
import * as os from "node:os";
|
|
67
67
|
import * as path from "node:path";
|
|
@@ -525,7 +525,7 @@ const renderSessionMessage: MessageRenderer = (message, { expanded }, theme) =>
|
|
|
525
525
|
// from the native call site until the schema grows the field.
|
|
526
526
|
interface SentBoxData {
|
|
527
527
|
to: string; // target sessionId
|
|
528
|
-
from?: string; // sender agentId, e.g. "entwurf/claude-opus-
|
|
528
|
+
from?: string; // sender agentId, e.g. "entwurf/claude-opus-5"
|
|
529
529
|
cwd?: string; // sender cwd (raw, abbreviateHome applied at render)
|
|
530
530
|
timestamp?: string; // ISO 8601 UTC; rendered in KST
|
|
531
531
|
mode?: string; // "steer" | "follow_up" | string passed through
|
|
@@ -1396,7 +1396,7 @@ The decider — not this surface — chooses the transport.`,
|
|
|
1396
1396
|
|
|
1397
1397
|
interface EntwurfFactProviderModule {
|
|
1398
1398
|
listEntwurfFacts(params: {
|
|
1399
|
-
metaEntries: readonly string[];
|
|
1399
|
+
metaEntries: readonly { filename: string; regularFile: boolean }[];
|
|
1400
1400
|
readRecord: (filename: string) => string;
|
|
1401
1401
|
socket: { dir: string };
|
|
1402
1402
|
}): Promise<unknown>;
|
|
@@ -1408,22 +1408,22 @@ interface EntwurfPeersRenderModule {
|
|
|
1408
1408
|
|
|
1409
1409
|
interface MetaSessionModule {
|
|
1410
1410
|
defaultMetaSessionsDir(): string;
|
|
1411
|
+
readActiveStoreEntries(dir: string): { filename: string; regularFile: boolean }[];
|
|
1412
|
+
makeStoreRecordReader(dir: string): (filename: string) => string;
|
|
1411
1413
|
}
|
|
1412
1414
|
|
|
1413
1415
|
async function renderEntwurfPeersForSurface(): Promise<{ text: string; payload: unknown }> {
|
|
1414
1416
|
const meta = (await import(META_SESSION_MODULE)) as unknown as MetaSessionModule;
|
|
1415
1417
|
const sessionsDir = meta.defaultMetaSessionsDir();
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1418
|
+
// Entries carry their KIND. The name-only readdir this used to do left `readRecord`
|
|
1419
|
+
// free to follow a symlinked `.meta.json` into bytes the store does not own — rule 1
|
|
1420
|
+
// held in the doctor and not on the surface operators actually read (pre-existing;
|
|
1421
|
+
// surfaced by the #52 duplicate pass, which would let such a symlink quarantine the
|
|
1422
|
+
// healthy record it shadowed).
|
|
1423
1423
|
const provider = (await import(ENTWURF_FACT_PROVIDER_MODULE)) as unknown as EntwurfFactProviderModule;
|
|
1424
1424
|
const result = await provider.listEntwurfFacts({
|
|
1425
|
-
metaEntries,
|
|
1426
|
-
readRecord:
|
|
1425
|
+
metaEntries: meta.readActiveStoreEntries(sessionsDir),
|
|
1426
|
+
readRecord: meta.makeStoreRecordReader(sessionsDir),
|
|
1427
1427
|
// Same socket axis as the legacy live-session scan, but merged with the
|
|
1428
1428
|
// meta-record rail by listEntwurfFacts so meta-mailbox citizens are discoverable too.
|
|
1429
1429
|
socket: { dir: ENTWURF_DIR },
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
// Why `/compat` and NOT the 0.80 provider-factory subpath (the pi-ai
|
|
19
19
|
// providers/anthropic subpath): this file is loaded by pi's
|
|
20
20
|
// EXTENSION loader (pi-coding-agent `core/extensions/loader.ts`), whose jiti
|
|
21
|
-
// alias map resolves
|
|
22
|
-
// `/compat`,
|
|
23
|
-
//
|
|
24
|
-
// and appends the remainder, yielding the
|
|
25
|
-
// `…/dist/compat.js/providers/anthropic` (verified live: extension
|
|
26
|
-
// invisible to static typecheck which resolves against node_modules
|
|
27
|
-
// So `/compat`
|
|
28
|
-
// model-catalog API, and the SINGLE allowlisted exception in
|
|
21
|
+
// alias map resolves FOUR pi-ai specifiers for extensions — the bare root,
|
|
22
|
+
// `/compat`, `/oauth`, and (since pi 0.81) `/providers/all`. Other `providers/*`
|
|
23
|
+
// subpaths are NOT in that map: jiti prefix-matches the bare
|
|
24
|
+
// `@earendil-works/pi-ai` alias and appends the remainder, yielding the
|
|
25
|
+
// unresolvable `…/dist/compat.js/providers/anthropic` (verified live: extension
|
|
26
|
+
// load crash, invisible to static typecheck which resolves against node_modules
|
|
27
|
+
// `exports`). So `/compat` remains the SINGLE sanctioned extension entrypoint
|
|
28
|
+
// this repo uses for the old global model-catalog API, and the SINGLE allowlisted exception in
|
|
29
29
|
// `run.sh check-pi-import-surface`. `getModels` here is compat's deprecated
|
|
30
30
|
// re-export of `getBuiltinModels`. When pi removes compat we migrate to whatever
|
|
31
31
|
// the loader then exposes.
|
|
@@ -50,13 +50,13 @@ export const ENTWURF_ACP_NO_AUTH_SENTINEL = "entwurf-no-auth";
|
|
|
50
50
|
// both axes (protocol smoke + agent interview) — do not extend casually.
|
|
51
51
|
// Exported so the claude backend adapter (backend-adapter.ts) can answer
|
|
52
52
|
// `routeModel` without re-deriving the set from curatedClaudeModels().
|
|
53
|
-
export const SUPPORTED_ANTHROPIC_MODEL_IDS = ["claude-sonnet-5", "claude-opus-
|
|
53
|
+
export const SUPPORTED_ANTHROPIC_MODEL_IDS = ["claude-sonnet-5", "claude-opus-5"] as const;
|
|
54
54
|
|
|
55
55
|
/** The anchor model whose absence is a hard registry regression, not a soft skip. */
|
|
56
|
-
export const CURATED_ANCHOR_MODEL_ID = "claude-opus-
|
|
56
|
+
export const CURATED_ANCHOR_MODEL_ID = "claude-opus-5";
|
|
57
57
|
|
|
58
|
-
// Anthropic's registry reports 1M for both Sonnet 5 and Opus
|
|
59
|
-
// entwurf surface
|
|
58
|
+
// Anthropic's registry reports 1M for both Sonnet 5 and Opus 5, and the
|
|
59
|
+
// entwurf surface exposes the full 1M for BOTH. Sonnet 5's 1M window is the
|
|
60
60
|
// whole point of the 0.12.3 bump — it is the compact-free long-context floor the
|
|
61
61
|
// earlier 200K Sonnet cap could not provide. We still clamp to a 1M ceiling so a
|
|
62
62
|
// future registry value can't silently inflate the surface past what we verify.
|
|
@@ -29,7 +29,12 @@
|
|
|
29
29
|
|
|
30
30
|
import { type FactList, isNonPiGardenIdSocketConflict, resolveFactList } from "./entwurf-facts.ts";
|
|
31
31
|
import { isLivenessSupported } from "./entwurf-v2-contract.ts";
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
type ActiveStoreEntry,
|
|
34
|
+
FRESH_CUT_PRESCRIPTION,
|
|
35
|
+
listAllMetaIdentities,
|
|
36
|
+
type MetaCitizenBackend,
|
|
37
|
+
} from "./meta-session.ts";
|
|
33
38
|
import { type SocketScanDeps, scanSocketProbes } from "./socket-discovery.ts";
|
|
34
39
|
import type { SocketLiveness } from "./socket-probe.ts";
|
|
35
40
|
|
|
@@ -82,7 +87,9 @@ export interface EntwurfFactsResult {
|
|
|
82
87
|
|
|
83
88
|
export interface EntwurfFactsDeps {
|
|
84
89
|
/** Meta-store axis: the `.meta.json` entry names + a record reader. */
|
|
85
|
-
|
|
90
|
+
/** Store entries WITH their kind (`readActiveStoreEntries`), never bare names: the
|
|
91
|
+
* listing must be able to refuse a symlinked record without following it. */
|
|
92
|
+
metaEntries: readonly ActiveStoreEntry[];
|
|
86
93
|
readRecord: (filename: string) => string;
|
|
87
94
|
/** Socket axis: injected into scanSocketProbes (controlDir/readdir/probe). */
|
|
88
95
|
socket?: Partial<SocketScanDeps>;
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
*
|
|
22
22
|
* `resolveTarget` (QB1): a MISSING meta-record is a soft `bad-target` (identity:null); a
|
|
23
23
|
* PRESENT-but-corrupt record fails LOUD (the read throws drift) — never matched by string.
|
|
24
|
+
* "Corrupt" includes a record that does not hold its `nativeSessionId` alone (#52): the read
|
|
25
|
+
* here is the ADDRESSABLE one, so the store-wide uniqueness half runs at this boundary and
|
|
26
|
+
* nowhere cheaper. Absent stays soft; ambiguous never does.
|
|
24
27
|
* `preProbeAddressConflict` (QB2): a single record-side `lstat` (NO connect) of the target's
|
|
25
28
|
* canonical socket path; `indeterminate` (EACCES/unknown) fails loud rather than silently
|
|
26
29
|
* claiming "no conflict". The decider's later `inspectSocket` probe is a SEPARATE step.
|
|
@@ -84,7 +87,7 @@ import {
|
|
|
84
87
|
type MetaReceiverMarker,
|
|
85
88
|
metaCapabilityFor,
|
|
86
89
|
metaRecordExistsByGardenId,
|
|
87
|
-
|
|
90
|
+
readAddressableMetaIdentity,
|
|
88
91
|
readMetaReceiverMarker,
|
|
89
92
|
} from "./meta-session.ts";
|
|
90
93
|
import {
|
|
@@ -110,6 +113,9 @@ import { classifyConnectError, probeSocketLiveness, type SocketLiveness } from "
|
|
|
110
113
|
*/
|
|
111
114
|
export interface ProductionEntwurfV2Seams {
|
|
112
115
|
metaRecordExists: (gid: string, sessionsDir: string) => boolean;
|
|
116
|
+
/** The ADDRESSABLE read (#52): the per-entry contract plus store-wide `nativeSessionId`
|
|
117
|
+
* uniqueness. This is a dispatch, so the id it returns is about to become an address —
|
|
118
|
+
* a duplicate here would direct-inject one native session under two garden ids. */
|
|
113
119
|
readIdentity: (gid: string, sessionsDir: string) => MetaIdentity;
|
|
114
120
|
/** Read the target's receiver presence marker (null = absent / dead owner / corrupt). The
|
|
115
121
|
* SE-2 2d-3 active-receiver source; the factory's `mailboxDeliverabilityFor` closure verifies
|
|
@@ -222,7 +228,7 @@ export function makeProductionEntwurfV2Deps(opts: ProductionEntwurfV2Opts): Entw
|
|
|
222
228
|
const s = opts.seams ?? {};
|
|
223
229
|
const io: ProductionEntwurfV2Seams = {
|
|
224
230
|
metaRecordExists: s.metaRecordExists ?? metaRecordExistsByGardenId,
|
|
225
|
-
readIdentity: s.readIdentity ??
|
|
231
|
+
readIdentity: s.readIdentity ?? readAddressableMetaIdentity,
|
|
226
232
|
readReceiverMarker: s.readReceiverMarker ?? ((gid: string) => readMetaReceiverMarker({ gardenId: gid })),
|
|
227
233
|
inspectPath: s.inspectPath ?? inspectControlSocketPath,
|
|
228
234
|
acquireLock: s.acquireLock ?? realAcquireLock,
|