pi-daddy 0.43.0 → 0.44.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.
Files changed (222) hide show
  1. package/CHANGELOG.md +104 -33
  2. package/README.md +27 -5
  3. package/THIRD_PARTY_LICENSES/musl-COPYRIGHT.txt +193 -0
  4. package/THIRD_PARTY_LICENSES/zig-MIT.txt +21 -0
  5. package/THIRD_PARTY_NOTICES.md +9 -0
  6. package/contracts/ledger-record/v1/README.md +40 -25
  7. package/dist/cli.d.ts +2 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +10 -5
  10. package/dist/cli.js.map +1 -1
  11. package/dist/executors/activity-session.js +116 -65
  12. package/dist/executors/activity-session.js.map +1 -1
  13. package/dist/executors/captured-execution.d.ts +13 -0
  14. package/dist/executors/captured-execution.d.ts.map +1 -0
  15. package/dist/executors/captured-execution.js +53 -0
  16. package/dist/executors/captured-execution.js.map +1 -0
  17. package/dist/executors/child-final.d.ts +31 -0
  18. package/dist/executors/child-final.d.ts.map +1 -0
  19. package/dist/executors/child-final.js +197 -0
  20. package/dist/executors/child-final.js.map +1 -0
  21. package/dist/executors/executor.d.ts +2 -1
  22. package/dist/executors/executor.d.ts.map +1 -1
  23. package/dist/executors/executor.js +12 -0
  24. package/dist/executors/executor.js.map +1 -1
  25. package/dist/executors/owned-worker.d.ts +18 -0
  26. package/dist/executors/owned-worker.d.ts.map +1 -0
  27. package/dist/executors/owned-worker.js +295 -0
  28. package/dist/executors/owned-worker.js.map +1 -0
  29. package/dist/executors/worker-artifact.d.ts +3 -0
  30. package/dist/executors/worker-artifact.d.ts.map +1 -0
  31. package/dist/executors/worker-artifact.js +3 -0
  32. package/dist/executors/worker-artifact.js.map +1 -0
  33. package/dist/governance/approval-prompt.d.ts +4 -3
  34. package/dist/governance/approval-prompt.d.ts.map +1 -1
  35. package/dist/governance/approval-prompt.js +13 -6
  36. package/dist/governance/approval-prompt.js.map +1 -1
  37. package/dist/governance/captured-worker-record.d.ts +9 -0
  38. package/dist/governance/captured-worker-record.d.ts.map +1 -0
  39. package/dist/governance/captured-worker-record.js +123 -0
  40. package/dist/governance/captured-worker-record.js.map +1 -0
  41. package/dist/governance/lease-record.d.ts +4 -0
  42. package/dist/governance/lease-record.d.ts.map +1 -1
  43. package/dist/governance/lease-record.js +19 -2
  44. package/dist/governance/lease-record.js.map +1 -1
  45. package/dist/governance/ledger-events.d.ts +5 -5
  46. package/dist/governance/ledger-events.d.ts.map +1 -1
  47. package/dist/governance/ledger-v3-validation.js +1 -1
  48. package/dist/governance/ledger-v3-validation.js.map +1 -1
  49. package/dist/governance/workspace-acceptance.d.ts +3 -1
  50. package/dist/governance/workspace-acceptance.d.ts.map +1 -1
  51. package/dist/governance/workspace-acceptance.js +21 -5
  52. package/dist/governance/workspace-acceptance.js.map +1 -1
  53. package/dist/governance/workspace-lease.d.ts.map +1 -1
  54. package/dist/governance/workspace-lease.js +44 -1
  55. package/dist/governance/workspace-lease.js.map +1 -1
  56. package/dist/kernel/authority-text.d.ts +3 -0
  57. package/dist/kernel/authority-text.d.ts.map +1 -0
  58. package/dist/kernel/authority-text.js +22 -0
  59. package/dist/kernel/authority-text.js.map +1 -0
  60. package/dist/kernel/bounded-read-failures.d.ts +9 -0
  61. package/dist/kernel/bounded-read-failures.d.ts.map +1 -0
  62. package/dist/kernel/bounded-read-failures.js +33 -0
  63. package/dist/kernel/bounded-read-failures.js.map +1 -0
  64. package/dist/kernel/bounded-read-sync.d.ts +24 -0
  65. package/dist/kernel/bounded-read-sync.d.ts.map +1 -0
  66. package/dist/kernel/bounded-read-sync.js +75 -0
  67. package/dist/kernel/bounded-read-sync.js.map +1 -0
  68. package/dist/kernel/bounded-read.d.ts +29 -2
  69. package/dist/kernel/bounded-read.d.ts.map +1 -1
  70. package/dist/kernel/bounded-read.js +98 -9
  71. package/dist/kernel/bounded-read.js.map +1 -1
  72. package/dist/kernel/capacity.d.ts +36 -0
  73. package/dist/kernel/capacity.d.ts.map +1 -0
  74. package/dist/kernel/capacity.js +94 -0
  75. package/dist/kernel/capacity.js.map +1 -0
  76. package/dist/kernel/captured-worker-contract.d.ts +38 -0
  77. package/dist/kernel/captured-worker-contract.d.ts.map +1 -0
  78. package/dist/kernel/captured-worker-contract.js +2 -0
  79. package/dist/kernel/captured-worker-contract.js.map +1 -0
  80. package/dist/kernel/catalog.d.ts +12 -3
  81. package/dist/kernel/catalog.d.ts.map +1 -1
  82. package/dist/kernel/catalog.js +28 -8
  83. package/dist/kernel/catalog.js.map +1 -1
  84. package/dist/kernel/chain.d.ts +1 -0
  85. package/dist/kernel/chain.d.ts.map +1 -1
  86. package/dist/kernel/chain.js.map +1 -1
  87. package/dist/kernel/definitions.d.ts +11 -1
  88. package/dist/kernel/definitions.d.ts.map +1 -1
  89. package/dist/kernel/definitions.js +6 -3
  90. package/dist/kernel/definitions.js.map +1 -1
  91. package/dist/kernel/fanout.d.ts +5 -25
  92. package/dist/kernel/fanout.d.ts.map +1 -1
  93. package/dist/kernel/fanout.js +15 -30
  94. package/dist/kernel/fanout.js.map +1 -1
  95. package/dist/kernel/grant-env.d.ts +0 -2
  96. package/dist/kernel/grant-env.d.ts.map +1 -1
  97. package/dist/kernel/grant-env.js +0 -1
  98. package/dist/kernel/grant-env.js.map +1 -1
  99. package/dist/kernel/model-preflight.d.ts +8 -0
  100. package/dist/kernel/model-preflight.d.ts.map +1 -1
  101. package/dist/kernel/model-preflight.js +23 -0
  102. package/dist/kernel/model-preflight.js.map +1 -1
  103. package/dist/kernel/selected-definitions.d.ts +15 -0
  104. package/dist/kernel/selected-definitions.d.ts.map +1 -0
  105. package/dist/kernel/selected-definitions.js +146 -0
  106. package/dist/kernel/selected-definitions.js.map +1 -0
  107. package/dist/kernel/skill-packages.d.ts +4 -2
  108. package/dist/kernel/skill-packages.d.ts.map +1 -1
  109. package/dist/kernel/skill-packages.js +34 -41
  110. package/dist/kernel/skill-packages.js.map +1 -1
  111. package/dist/kernel/skill-resources.d.ts.map +1 -1
  112. package/dist/kernel/skill-resources.js +8 -11
  113. package/dist/kernel/skill-resources.js.map +1 -1
  114. package/dist/kernel/workspace.d.ts +6 -3
  115. package/dist/kernel/workspace.d.ts.map +1 -1
  116. package/dist/kernel/workspace.js +10 -6
  117. package/dist/kernel/workspace.js.map +1 -1
  118. package/dist/products/dashboard-cli.js +1 -1
  119. package/dist/products/dashboard-cli.js.map +1 -1
  120. package/dist/products/dashboard-render.d.ts +1 -2
  121. package/dist/products/dashboard-render.d.ts.map +1 -1
  122. package/dist/products/dashboard-render.js +3 -1
  123. package/dist/products/dashboard-render.js.map +1 -1
  124. package/dist/products/dashboard-session-client.d.ts +1 -2
  125. package/dist/products/dashboard-session-client.d.ts.map +1 -1
  126. package/dist/products/dashboard-session-client.js.map +1 -1
  127. package/dist/products/episode-report.d.ts +12 -8
  128. package/dist/products/episode-report.d.ts.map +1 -1
  129. package/dist/products/episode-report.js +61 -12
  130. package/dist/products/episode-report.js.map +1 -1
  131. package/extensions/approvals.ts +1 -1
  132. package/extensions/chain-plan.ts +9 -3
  133. package/extensions/context-staging.ts +30 -35
  134. package/extensions/dashboard-session-server.ts +1 -2
  135. package/extensions/definition-describe.ts +44 -0
  136. package/extensions/definition-runtime.ts +89 -26
  137. package/extensions/delegate-chain.ts +273 -256
  138. package/extensions/delegation-activation.ts +1 -1
  139. package/extensions/delegation.ts +41 -53
  140. package/extensions/execute-child.ts +167 -134
  141. package/extensions/fanout-outcome.ts +1 -9
  142. package/extensions/grants-command.ts +2 -9
  143. package/extensions/grants.ts +106 -38
  144. package/extensions/init-command.ts +52 -12
  145. package/extensions/reload-environment.ts +21 -2
  146. package/extensions/retired-inputs.ts +7 -0
  147. package/extensions/run-delegation.ts +212 -265
  148. package/extensions/session-capacity.ts +53 -0
  149. package/extensions/session-environment.ts +5 -2
  150. package/extensions/session-model-prompt.ts +33 -32
  151. package/extensions/session-report.ts +28 -11
  152. package/extensions/session.ts +200 -157
  153. package/extensions/workspace-runtime.ts +2 -1
  154. package/native/README.md +25 -0
  155. package/native/linux-x64/worker +0 -0
  156. package/native/linux-x64/worker.sha256 +1 -0
  157. package/native/provenance.json +42 -0
  158. package/native/toolchain.json +18 -0
  159. package/native/worker.c +209 -0
  160. package/package.json +15 -7
  161. package/src/cli.ts +16 -6
  162. package/src/executors/activity-session.ts +120 -66
  163. package/src/executors/captured-execution.ts +61 -0
  164. package/src/executors/child-final.ts +190 -0
  165. package/src/executors/executor.ts +14 -1
  166. package/src/executors/owned-worker.ts +322 -0
  167. package/src/executors/worker-artifact.ts +2 -0
  168. package/src/governance/approval-prompt.ts +18 -8
  169. package/src/governance/captured-worker-record.ts +138 -0
  170. package/src/governance/lease-record.ts +21 -2
  171. package/src/governance/ledger-events.ts +5 -5
  172. package/src/governance/ledger-v3-validation.ts +1 -1
  173. package/src/governance/workspace-acceptance.ts +20 -4
  174. package/src/governance/workspace-lease.ts +59 -1
  175. package/src/kernel/authority-text.ts +17 -0
  176. package/src/kernel/bounded-read-failures.ts +42 -0
  177. package/src/kernel/bounded-read-sync.ts +87 -0
  178. package/src/kernel/bounded-read.ts +128 -11
  179. package/src/kernel/capacity.ts +123 -0
  180. package/src/kernel/captured-worker-contract.ts +29 -0
  181. package/src/kernel/catalog.ts +45 -16
  182. package/src/kernel/chain.ts +1 -0
  183. package/src/kernel/definitions.ts +11 -2
  184. package/src/kernel/fanout.ts +17 -32
  185. package/src/kernel/grant-env.ts +0 -3
  186. package/src/kernel/model-preflight.ts +29 -0
  187. package/src/kernel/selected-definitions.ts +156 -0
  188. package/src/kernel/skill-packages.ts +40 -38
  189. package/src/kernel/skill-resources.ts +8 -9
  190. package/src/kernel/workspace.ts +12 -5
  191. package/src/products/dashboard-cli.ts +1 -1
  192. package/src/products/dashboard-render.ts +6 -2
  193. package/src/products/dashboard-session-client.ts +1 -2
  194. package/src/products/episode-report.ts +80 -17
  195. package/dist/advisors/advisor.d.ts +0 -90
  196. package/dist/advisors/advisor.d.ts.map +0 -1
  197. package/dist/advisors/advisor.js +0 -154
  198. package/dist/advisors/advisor.js.map +0 -1
  199. package/dist/advisors/decider.d.ts +0 -88
  200. package/dist/advisors/decider.d.ts.map +0 -1
  201. package/dist/advisors/decider.js +0 -29
  202. package/dist/advisors/decider.js.map +0 -1
  203. package/dist/advisors/jev.d.ts +0 -41
  204. package/dist/advisors/jev.d.ts.map +0 -1
  205. package/dist/advisors/jev.js +0 -139
  206. package/dist/advisors/jev.js.map +0 -1
  207. package/dist/advisors/settings.d.ts +0 -49
  208. package/dist/advisors/settings.d.ts.map +0 -1
  209. package/dist/advisors/settings.js +0 -99
  210. package/dist/advisors/settings.js.map +0 -1
  211. package/dist/governance/episode-cost-gate.d.ts +0 -31
  212. package/dist/governance/episode-cost-gate.d.ts.map +0 -1
  213. package/dist/governance/episode-cost-gate.js +0 -112
  214. package/dist/governance/episode-cost-gate.js.map +0 -1
  215. package/extensions/advisor-session.ts +0 -67
  216. package/extensions/effort-advice.ts +0 -112
  217. package/extensions/pruning-advice.ts +0 -133
  218. package/src/advisors/advisor.ts +0 -246
  219. package/src/advisors/decider.ts +0 -74
  220. package/src/advisors/jev.ts +0 -163
  221. package/src/advisors/settings.ts +0 -115
  222. package/src/governance/episode-cost-gate.ts +0 -128
@@ -0,0 +1,42 @@
1
+ {
2
+ "revision": 1,
3
+ "toolchain": {
4
+ "version": "0.14.1",
5
+ "url": "https://ziglang.org/download/0.14.1/zig-x86_64-linux-0.14.1.tar.xz",
6
+ "sha256": "24aeeec8af16c381934a6cd7d95c807a8cb2cf7df9fa40d359aa884195c4716c",
7
+ "directory": "zig-x86_64-linux-0.14.1",
8
+ "target": "x86_64-linux-musl",
9
+ "flags": [
10
+ "-std=c11",
11
+ "-Wall",
12
+ "-Wextra",
13
+ "-Werror",
14
+ "-O2",
15
+ "-static",
16
+ "-s",
17
+ "-fno-ident",
18
+ "-Wl,--build-id=none"
19
+ ]
20
+ },
21
+ "source": {
22
+ "path": "native/worker.c",
23
+ "sha256": "205bacc28f9011c0603fdb95e930e901ba5dbff6f6170cd2adfd39d8ec5c2d6d"
24
+ },
25
+ "artifact": {
26
+ "path": "native/linux-x64/worker",
27
+ "sha256": "981a45591b1de790501ef531f6b0ccc8299b1e9a2d08a7970ccaeeecb9868798",
28
+ "bytes": 56360
29
+ },
30
+ "licenses": [
31
+ {
32
+ "path": "THIRD_PARTY_LICENSES/musl-COPYRIGHT.txt",
33
+ "sha256": "f9bc4423732350eb0b3f7ed7e91d530298476f8fec0c6c427a1c04ade22655af",
34
+ "source": "lib/libc/musl/COPYRIGHT"
35
+ },
36
+ {
37
+ "path": "THIRD_PARTY_LICENSES/zig-MIT.txt",
38
+ "sha256": "5c537d6853e005298a285d508cff9ac7192cea23576c840d485b2b586a7ff177",
39
+ "source": "LICENSE"
40
+ }
41
+ ]
42
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "version": "0.14.1",
3
+ "url": "https://ziglang.org/download/0.14.1/zig-x86_64-linux-0.14.1.tar.xz",
4
+ "sha256": "24aeeec8af16c381934a6cd7d95c807a8cb2cf7df9fa40d359aa884195c4716c",
5
+ "directory": "zig-x86_64-linux-0.14.1",
6
+ "target": "x86_64-linux-musl",
7
+ "flags": [
8
+ "-std=c11",
9
+ "-Wall",
10
+ "-Wextra",
11
+ "-Werror",
12
+ "-O2",
13
+ "-static",
14
+ "-s",
15
+ "-fno-ident",
16
+ "-Wl,--build-id=none"
17
+ ]
18
+ }
@@ -0,0 +1,209 @@
1
+ /* Trusted-development Linux worker owner. Built and packaged; never compiled at runtime.
2
+ * Direct children stay waitable until this single-threaded helper reaps them, preventing PID reuse
3
+ * between children-list observation and pidfd_open. Detached descendants are adopted by subreaper.
4
+ * SIGKILL of this helper is explicitly NOT a successful settlement: no receipt can be written.
5
+ */
6
+ #define _GNU_SOURCE
7
+ #include <errno.h>
8
+ #include <fcntl.h>
9
+ #include <limits.h>
10
+ #include <poll.h>
11
+ #include <signal.h>
12
+ #include <stdint.h>
13
+ #include <stdio.h>
14
+ #include <stdlib.h>
15
+ #include <string.h>
16
+ #include <sys/prctl.h>
17
+ #include <sys/stat.h>
18
+ #include <sys/syscall.h>
19
+ #include <sys/types.h>
20
+ #include <sys/wait.h>
21
+ #include <time.h>
22
+ #include <unistd.h>
23
+
24
+ static volatile sig_atomic_t requested_signal;
25
+ static void on_signal(int signo) { requested_signal = signo; }
26
+ static long long monotonic_ms(void) {
27
+ struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now);
28
+ return (long long)now.tv_sec * 1000 + now.tv_nsec / 1000000;
29
+ }
30
+ static void quoted(FILE *out, const char *s) {
31
+ fputc('"', out);
32
+ for (; *s; s++) {
33
+ unsigned char c = (unsigned char)*s;
34
+ if (c == '"' || c == '\\') { fputc('\\', out); fputc(c, out); }
35
+ else if (c < 32) fprintf(out, "\\u%04x", c);
36
+ else fputc(c, out);
37
+ }
38
+ fputc('"', out);
39
+ }
40
+ static int read_text(const char *path, char *out, size_t size) {
41
+ int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) return -1;
42
+ ssize_t n = read(fd, out, size - 1); close(fd); if (n <= 0) return -1;
43
+ out[n] = 0; out[strcspn(out, "\r\n")] = 0; return 0;
44
+ }
45
+ static int self_start(char *out, size_t size) {
46
+ char data[4096]; if (read_text("/proc/self/stat", data, sizeof(data)) < 0) return -1;
47
+ char *end = strrchr(data, ')'); if (!end) return -1;
48
+ char *save = NULL, *part = strtok_r(end + 2, " ", &save);
49
+ for (int field = 3; part; field++, part = strtok_r(NULL, " ", &save)) {
50
+ if (field == 22) { snprintf(out, size, "%s", part); return 0; }
51
+ }
52
+ return -1;
53
+ }
54
+ struct identity {
55
+ const char *execution, *nonce, *root, *hash, *directory;
56
+ char boot[80], ns[80], start[40], ownership[PATH_MAX], receipt[PATH_MAX];
57
+ struct stat workspace;
58
+ pid_t worker;
59
+ };
60
+ static void identity_json(FILE *out, const struct identity *id) {
61
+ fprintf(out, "{\"revision\":1,\"executionId\":"); quoted(out, id->execution);
62
+ fprintf(out, ",\"nonce\":"); quoted(out, id->nonce);
63
+ fprintf(out, ",\"root\":"); quoted(out, id->root);
64
+ fprintf(out, ",\"rootDevice\":\"%llu\",\"rootInode\":\"%llu\"", (unsigned long long)id->workspace.st_dev, (unsigned long long)id->workspace.st_ino);
65
+ fprintf(out, ",\"bootId\":"); quoted(out, id->boot);
66
+ fprintf(out, ",\"pidNamespace\":"); quoted(out, id->ns);
67
+ fprintf(out, ",\"helperPid\":%d,\"helperStartTicks\":", getpid()); quoted(out, id->start);
68
+ fprintf(out, ",\"helperSha256\":"); quoted(out, id->hash);
69
+ fprintf(out, ",\"workerPid\":%d,\"ownershipPath\":", id->worker); quoted(out, id->ownership);
70
+ fprintf(out, ",\"receiptPath\":"); quoted(out, id->receipt); fprintf(out, "}");
71
+ }
72
+ static int durable(const struct identity *id, int settled, int worker_status, const char *reason) {
73
+ const char *path = settled ? id->receipt : id->ownership;
74
+ char temp[PATH_MAX]; if (snprintf(temp, sizeof(temp), "%s.tmp", path) >= (int)sizeof(temp)) return -1;
75
+ int fd = open(temp, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600); if (fd < 0) return -1;
76
+ FILE *out = fdopen(fd, "w"); if (!out) { close(fd); return -1; }
77
+ fprintf(out, "{\"state\":\"%s\",\"identity\":", settled ? "settled" : "ready"); identity_json(out, id);
78
+ if (settled) {
79
+ fprintf(out, ",\"workerCode\":"); if (WIFEXITED(worker_status)) fprintf(out, "%d", WEXITSTATUS(worker_status)); else fprintf(out, "null");
80
+ fprintf(out, ",\"workerSignal\":%d,\"reason\":", WIFSIGNALED(worker_status) ? WTERMSIG(worker_status) : 0); quoted(out, reason);
81
+ fprintf(out, ",\"reapedAll\":true");
82
+ }
83
+ fprintf(out, "}\n");
84
+ int ok = fflush(out) == 0 && fsync(fd) == 0;
85
+ if (fclose(out) != 0) ok = 0;
86
+ if (!ok || link(temp, path) < 0 || unlink(temp) < 0) return -1;
87
+ int dir = open(id->directory, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
88
+ if (dir < 0) return -1;
89
+ ok = fsync(dir) == 0; close(dir); return ok ? 0 : -1;
90
+ }
91
+ static int milliseconds(const char *text, long maximum, long *value) {
92
+ if (!*text) return -1;
93
+ for (const char *p = text; *p; p++) if (*p < '0' || *p > '9') return -1;
94
+ char *end; errno = 0; long parsed = strtol(text, &end, 10);
95
+ if (errno || *end || parsed < 0 || parsed > maximum) return -1;
96
+ *value = parsed; return 0;
97
+ }
98
+ static int validate_channel(int fd, int writing) {
99
+ struct stat info; int flags = fcntl(fd, F_GETFL);
100
+ if (flags < 0 || fstat(fd, &info) < 0 || (!S_ISFIFO(info.st_mode) && !S_ISSOCK(info.st_mode))) return -1;
101
+ if ((writing && (flags & O_ACCMODE) == O_RDONLY) || (!writing && (flags & O_ACCMODE) == O_WRONLY)) return -1;
102
+ return writing ? 0 : fcntl(fd, F_SETFL, flags | O_NONBLOCK);
103
+ }
104
+ /* A direct child cannot reuse its PID before our waitpid. Forget its signal record only after reaping. */
105
+ struct signalled_child { pid_t pid; int signo; };
106
+ static struct signalled_child *signalled;
107
+ static size_t signalled_count;
108
+ static void forget_child(pid_t pid) {
109
+ for (size_t i = 0; i < signalled_count; i++) if (signalled[i].pid == pid) {
110
+ signalled[i] = signalled[--signalled_count]; return;
111
+ }
112
+ }
113
+ /* Signal only this helper's direct children. No process-name, cwd, PID-group or global enumeration. */
114
+ static int signal_children(int signo) {
115
+ char path[100]; snprintf(path, sizeof(path), "/proc/self/task/%d/children", getpid());
116
+ FILE *children = fopen(path, "r"); if (!children) return -1;
117
+ int pid, ok = 0, scanned;
118
+ while ((scanned = fscanf(children, "%d", &pid)) == 1) {
119
+ if (pid <= 0) { ok = -1; break; }
120
+ size_t index = 0;
121
+ while (index < signalled_count && signalled[index].pid != pid) index++;
122
+ if (signo && index < signalled_count && signalled[index].signo == signo) continue;
123
+ int pfd = (int)syscall(SYS_pidfd_open, pid, 0);
124
+ if (pfd < 0) { if (errno != ESRCH) ok = -1; continue; }
125
+ if (syscall(SYS_pidfd_send_signal, pfd, signo, NULL, 0) < 0 && errno != ESRCH) ok = -1;
126
+ close(pfd);
127
+ if (signo && ok == 0) {
128
+ if (index == signalled_count) {
129
+ void *next = realloc(signalled, (signalled_count + 1) * sizeof(*signalled));
130
+ if (!next) { ok = -1; break; }
131
+ signalled = next; signalled_count++; signalled[index].pid = pid;
132
+ }
133
+ signalled[index].signo = signo;
134
+ }
135
+ }
136
+ if (scanned != EOF || ferror(children)) ok = -1;
137
+ fclose(children); return ok;
138
+ }
139
+ int main(int argc, char **argv) {
140
+ /* execution nonce canonical-root helper-sha ownership-dir grace-ms cleanup-ms -- command args */
141
+ if (argc < 10 || strcmp(argv[8], "--")) return 64;
142
+ struct identity id = { .execution=argv[1], .nonce=argv[2], .root=argv[3], .hash=argv[4], .directory=argv[5] };
143
+ long grace, ceiling;
144
+ if (milliseconds(argv[6], 60000, &grace) < 0 || milliseconds(argv[7], 120000, &ceiling) < 0 || ceiling < grace) return 64;
145
+ if (validate_channel(3, 0) < 0 || validate_channel(4, 1) < 0) return 65;
146
+ if (stat(id.root, &id.workspace) < 0 || !S_ISDIR(id.workspace.st_mode)) return 65;
147
+ if (read_text("/proc/sys/kernel/random/boot_id", id.boot, sizeof(id.boot)) < 0 || self_start(id.start, sizeof(id.start)) < 0) return 65;
148
+ ssize_t n = readlink("/proc/self/ns/pid", id.ns, sizeof(id.ns)-1); if (n < 0) return 65; id.ns[n] = 0;
149
+ if (snprintf(id.ownership, sizeof(id.ownership), "%s/ownership.json", id.directory) >= (int)sizeof(id.ownership) ||
150
+ snprintf(id.receipt, sizeof(id.receipt), "%s/receipt.json", id.directory) >= (int)sizeof(id.receipt)) return 65;
151
+ if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) return 65;
152
+ int probe = (int)syscall(SYS_pidfd_open, getpid(), 0); if (probe < 0) return 65; close(probe);
153
+ if (signal_children(0) < 0) return 65; /* Probe /proc children support before admitting work. */
154
+ signal(SIGPIPE, SIG_IGN); signal(SIGTERM, on_signal); signal(SIGINT, on_signal); signal(SIGHUP, on_signal);
155
+ int gate[2]; if (pipe2(gate, O_CLOEXEC) < 0) return 65;
156
+ id.worker = fork(); if (id.worker < 0) return 65;
157
+ if (id.worker == 0) {
158
+ close(gate[1]); close(3); close(4); close(5);
159
+ signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGHUP, SIG_DFL); signal(SIGPIPE, SIG_DFL);
160
+ char release; if (read(gate[0], &release, 1) != 1 || release != 'S') _exit(125);
161
+ close(gate[0]); if (chdir(id.root) < 0) _exit(126);
162
+ execvp(argv[9], &argv[9]); _exit(errno == ENOENT ? 127 : 126);
163
+ }
164
+ close(gate[0]);
165
+ int ready = durable(&id, 0, 0, "ready") == 0;
166
+ if (ready) {
167
+ FILE *status = fdopen(dup(4), "w");
168
+ if (status) { identity_json(status, &id); fputc('\n', status); if (ferror(status)) ready = 0; if (fclose(status) != 0) ready = 0; }
169
+ else ready = 0;
170
+ }
171
+ long long stopping = ready ? 0 : monotonic_ms();
172
+ const char *reason = ready ? "worker-exit" : "ownership-write-failed";
173
+ int started = 0, worker_status = 0, worker_reaped = 0, control_open = 1;
174
+ for (;;) {
175
+ int status; pid_t reaped;
176
+ while ((reaped = waitpid(-1, &status, WNOHANG)) > 0) {
177
+ forget_child(reaped);
178
+ if (reaped == id.worker) { worker_status=status; worker_reaped=1; if (!stopping) stopping=monotonic_ms(); }
179
+ }
180
+ if (reaped < 0 && errno == ECHILD) {
181
+ if (!worker_reaped) return 74;
182
+ if (durable(&id, 1, worker_status, reason) < 0) return 74;
183
+ close(gate[1]); return 0;
184
+ }
185
+ if (requested_signal && !stopping) { stopping=monotonic_ms(); reason="helper-signal"; }
186
+ char control[32]; ssize_t received = control_open ? read(3, control, sizeof(control)) : -1;
187
+ if (control_open && (received == 0 || (received < 0 && errno != EAGAIN && errno != EWOULDBLOCK))) { control_open=0; if (!stopping) { stopping=monotonic_ms(); reason="owner-loss"; } }
188
+ if (received > 0) for (ssize_t i=0;i<received;i++) {
189
+ if (control[i] == 'K') {
190
+ if (!stopping) { stopping=monotonic_ms(); reason="cancelled"; }
191
+ long long immediate = monotonic_ms()-grace;
192
+ if (stopping > immediate) stopping=immediate; /* Escalate without rewriting the original cause. */
193
+ }
194
+ if (control[i] == 'C' && !stopping) { stopping=monotonic_ms(); reason="cancelled"; }
195
+ if (control[i] == 'S' && ready && !started && !stopping) { started=1; if (write(gate[1], "S", 1) != 1) { stopping=monotonic_ms(); reason="start-failed"; } }
196
+ }
197
+ if (stopping) {
198
+ long long elapsed = monotonic_ms()-stopping;
199
+ if (signal_children(elapsed >= grace ? SIGKILL : SIGTERM) < 0) return 74;
200
+ if (elapsed >= ceiling) return 74; /* Missing receipt preserves uncertainty. */
201
+ }
202
+ struct pollfd pfd = { .fd=control_open ? 3 : -1, .events=POLLIN | POLLHUP };
203
+ int polled = poll(&pfd, 1, 10);
204
+ if ((polled < 0 && errno != EINTR) || (polled > 0 && (pfd.revents & POLLNVAL))) {
205
+ control_open=0;
206
+ if (!stopping) { stopping=monotonic_ms(); reason="owner-loss"; }
207
+ }
208
+ }
209
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-daddy",
3
- "version": "0.43.0",
3
+ "version": "0.44.0",
4
4
  "description": "Capability governance for pi sub-agents: spawn Agent Skills (SKILL.md) definitions whose allowed-tools becomes a grant that can only narrow going down a delegation tree, enforced by pi's own --tools allowlist, with an append-only ledger.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -45,7 +45,8 @@
45
45
  "THIRD_PARTY_NOTICES.md",
46
46
  "README.md",
47
47
  "CHANGELOG.md",
48
- "LICENSE"
48
+ "LICENSE",
49
+ "native"
49
50
  ],
50
51
  "scripts": {
51
52
  "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
@@ -55,7 +56,8 @@
55
56
  "test:integration:ci": "node --test test-integration/approval.it.ts test-integration/governance.it.ts test-integration/activity.it.ts",
56
57
  "contracts:generate": "node scripts/generate-ledger-record-contract.ts",
57
58
  "prepack": "npm run build",
58
- "test:smoke": "node scripts/smoke-installed.mjs"
59
+ "test:smoke": "node scripts/smoke-installed.mjs",
60
+ "build:worker": "node scripts/build-worker.ts"
59
61
  },
60
62
  "main": "./dist/index.js",
61
63
  "types": "./dist/index.d.ts",
@@ -65,16 +67,22 @@
65
67
  ]
66
68
  },
67
69
  "peerDependencies": {
68
- "@earendil-works/pi-coding-agent": ">=0.83.0",
69
70
  "@types/node": ">=22",
70
- "typebox": ">=1.0.0"
71
+ "typebox": ">=1.0.0",
72
+ "@earendil-works/pi-coding-agent": "1.0.4"
71
73
  },
72
74
  "dependencies": {
73
- "@earendil-works/pi-coding-agent": ">=0.83.0",
75
+ "@earendil-works/pi-coding-agent": "1.0.4",
74
76
  "typebox": ">=1.0.0"
75
77
  },
76
78
  "devDependencies": {
77
79
  "@types/node": "^22.0.0",
78
80
  "typescript": "^5.7.0"
79
- }
81
+ },
82
+ "os": [
83
+ "linux"
84
+ ],
85
+ "cpu": [
86
+ "x64"
87
+ ]
80
88
  }
package/src/cli.ts CHANGED
@@ -17,6 +17,7 @@ import { realpathSync } from "node:fs";
17
17
  import { readFile } from "node:fs/promises";
18
18
  import { relative, resolve as resolvePath } from "node:path";
19
19
  import { pathToFileURL } from "node:url";
20
+ import { BoundedReadCleanupError } from "./kernel/bounded-read.ts";
20
21
  import { UnsafeGrantError } from "./kernel/grant-env.ts";
21
22
  import {
22
23
  applyInit,
@@ -26,7 +27,7 @@ import {
26
27
  GITIGNORE_REINCLUDE_LINES,
27
28
  settingsIgnoredByGit,
28
29
  } from "./governance/init.ts";
29
- import { registeredWorkspaceIds } from "./kernel/workspace.ts";
30
+ import { registeredWorkspaceIds, loadWorkspaceRegistry } from "./kernel/workspace.ts";
30
31
  import { explainDoubledNamespace } from "./kernel/catalog.ts";
31
32
  import type { Capability } from "./kernel/resolve.ts";
32
33
  import {
@@ -178,7 +179,7 @@ export function parseArgs(argv: string[]): ParsedArgs {
178
179
  }
179
180
 
180
181
  /** Report a plan and what came of applying it. Returns the process exit code. */
181
- async function init(cwd: string, force: boolean): Promise<number> {
182
+ async function init(cwd: string, force: boolean, loadRegistry: typeof loadWorkspaceRegistry): Promise<number> {
182
183
  const packages = await discoverSkillPackages(cwd);
183
184
  if (packages.length === 0) {
184
185
  // Names BOTH roots it looked in, and offers pi's own install command first. The previous message named
@@ -204,13 +205,19 @@ async function init(cwd: string, force: boolean): Promise<number> {
204
205
  plan = planInit(
205
206
  packages,
206
207
  cwd,
207
- await registeredWorkspaceIds(undefined, (reason) =>
208
- console.error(`pi-daddy init: workspace registry unreadable, scaffolding none — ${reason}`),
208
+ await registeredWorkspaceIds(
209
+ undefined,
210
+ (reason) => console.error(`pi-daddy init: workspace registry unreadable, scaffolding none — ${reason}`),
211
+ loadRegistry,
209
212
  ),
210
213
  );
211
214
  } catch (error) {
212
215
  // R-78's backstop reaching the surface. Nothing is written: a grant that could mean something to a
213
216
  // shell is not a grant, and half-scaffolding a project would be worse than scaffolding none of it.
217
+ if (error instanceof BoundedReadCleanupError) {
218
+ console.error(`pi-daddy init: cleanup failed — ${error.message}; no scaffolding applied.`);
219
+ throw error;
220
+ }
214
221
  if (error instanceof UnsafeGrantError) {
215
222
  console.error(`pi-daddy init: ${error.message}`);
216
223
  return 1;
@@ -352,7 +359,10 @@ async function report(plan: InitPlan): Promise<void> {
352
359
  );
353
360
  }
354
361
 
355
- export async function main(argv: string[]): Promise<number> {
362
+ export async function main(
363
+ argv: string[],
364
+ loadRegistry: typeof loadWorkspaceRegistry = loadWorkspaceRegistry,
365
+ ): Promise<number> {
356
366
  const adoptedLegacy = adoptLegacyEnvironment(process.env);
357
367
  if (adoptedLegacy.length > 0) console.error(legacyEnvironmentWarning(adoptedLegacy));
358
368
  const parsed = parseArgs(argv);
@@ -475,7 +485,7 @@ export async function main(argv: string[]): Promise<number> {
475
485
  // `--dir` made that comparison false for every entry: every declared skill of every package was reported
476
486
  // "declared but unreadable", nothing was copied, a degenerate grants.env was written anyway, and the exit
477
487
  // code was 0 — while the message blamed the package for a defect in this line.
478
- return init(resolvePath(parsed.dir ?? process.cwd()), parsed.force);
488
+ return init(resolvePath(parsed.dir ?? process.cwd()), parsed.force, loadRegistry);
479
489
  }
480
490
 
481
491
  /**
@@ -8,9 +8,9 @@
8
8
  * already carries `--session` (native-session retention), that file is the probe; otherwise a private temporary
9
9
  * file is allocated for the run and removed afterwards.
10
10
  */
11
- import { createReadStream } from "node:fs";
12
11
  import { mkdtemp, readdir, rm, stat } from "node:fs/promises";
13
- import { createInterface } from "node:readline";
12
+ import { SessionManager, type FileEntry } from "@earendil-works/pi-coding-agent";
13
+ import { BoundedReadCleanupError, readBoundedBytes } from "../kernel/bounded-read.ts";
14
14
  import type { ChildUsageTotals } from "../governance/ledger-events.ts";
15
15
  import { tmpdir } from "node:os";
16
16
  import { join } from "node:path";
@@ -74,7 +74,7 @@ export async function activitySessionFor(planArgs: string[], executionId: string
74
74
  args: planArgs,
75
75
  path: dir,
76
76
  probe: probeDirectory(dir),
77
- usage: () => readChildUsage(newestSessionFile(dir)),
77
+ usage: () => readChildUsage(exactSessionFile(dir, planArgs[planArgs.indexOf("--session-id") + 1])),
78
78
  dispose: async () => undefined,
79
79
  };
80
80
  }
@@ -114,14 +114,11 @@ export async function activitySessionFor(planArgs: string[], executionId: string
114
114
  };
115
115
  }
116
116
 
117
- async function newestSessionFile(directory: string): Promise<string | undefined> {
117
+ async function exactSessionFile(directory: string, id: string | undefined): Promise<string | undefined> {
118
+ if (!id || !/^[a-zA-Z0-9-]+$/.test(id)) return undefined;
118
119
  try {
119
- const entries = await Promise.all(
120
- (await readdir(directory))
121
- .filter((name) => name.endsWith(".jsonl"))
122
- .map(async (name) => ({ path: join(directory, name), modified: (await stat(join(directory, name))).mtimeMs })),
123
- );
124
- return entries.sort((a, b) => b.modified - a.modified)[0]?.path;
120
+ const names = (await readdir(directory)).filter((name) => name.endsWith(`_${id}.jsonl`));
121
+ return names.length === 1 ? join(directory, names[0]) : undefined;
125
122
  } catch {
126
123
  return undefined;
127
124
  }
@@ -130,82 +127,138 @@ async function newestSessionFile(directory: string): Promise<string | undefined>
130
127
  const TOKEN_FIELDS = ["input", "output", "cacheRead", "cacheWrite", "totalTokens"] as const;
131
128
  const COST_FIELDS = ["input", "output", "cacheRead", "cacheWrite", "total"] as const;
132
129
 
130
+ // Match final capture's bound; oversized or damaged accounting stays unknown, never a partial total.
131
+ const MAX_USAGE_SESSION_BYTES = 64 * 1024 * 1024;
132
+ const ENTRY_TYPES = new Set([
133
+ "message",
134
+ "model_change",
135
+ "thinking_level_change",
136
+ "usage",
137
+ "compaction",
138
+ "branch_summary",
139
+ "custom",
140
+ "label",
141
+ "session_info",
142
+ "custom_message",
143
+ "context_edit",
144
+ ]);
145
+ function activeUsageBranch(source: string) {
146
+ if (!source.endsWith("\n")) throw new Error("incomplete session record");
147
+ const entries = source
148
+ .slice(0, -1)
149
+ .split("\n")
150
+ .map((line) => JSON.parse(line));
151
+ const header = object(entries[0]);
152
+ if (
153
+ !header ||
154
+ header.type !== "session" ||
155
+ header.version !== 3 ||
156
+ typeof header.id !== "string" ||
157
+ !header.id ||
158
+ typeof header.cwd !== "string" ||
159
+ !header.cwd
160
+ )
161
+ throw new Error("invalid canonical session header");
162
+ const seen = new Set<string>();
163
+ for (const value of entries.slice(1)) {
164
+ const entry = object(value);
165
+ if (
166
+ !entry ||
167
+ !ENTRY_TYPES.has(String(entry.type)) ||
168
+ typeof entry.id !== "string" ||
169
+ !entry.id ||
170
+ seen.has(entry.id) ||
171
+ typeof entry.timestamp !== "string" ||
172
+ (entry.parentId !== null && (typeof entry.parentId !== "string" || !seen.has(entry.parentId)))
173
+ )
174
+ throw new Error("ambiguous canonical session tree");
175
+ if (entry.type === "message" && !object(entry.message)) throw new Error("invalid session message");
176
+ seen.add(entry.id);
177
+ }
178
+ // This public SDK seam reads already bounded in-memory bytes; it never reopens the path or scans other branches.
179
+ return SessionManager.inMemory(header.cwd, undefined, entries as FileEntry[]).getBranch();
180
+ }
181
+
133
182
  async function readChildUsage(path: string | Promise<string | undefined>): Promise<ChildUsageObservation> {
134
183
  const resolved = await path;
135
184
  if (!resolved) return { unavailable: "session-missing" };
136
185
  const totals = emptyUsage();
137
- let found = false;
138
- let reasoning = 0;
139
- let sawReasoning = false;
186
+ let found = false,
187
+ incomplete = false,
188
+ reasoning = 0,
189
+ sawReasoning = false;
140
190
  let resolvedModel: ChildUsageObservation["resolvedModel"];
141
191
  let effectiveThinkingLevel: string | undefined;
142
192
  let compactionCount = 0;
143
193
  try {
144
- const lines = createInterface({ input: createReadStream(resolved, { encoding: "utf8" }), crlfDelay: Infinity });
145
- for await (const line of lines) {
146
- let entry: unknown;
147
- try {
148
- entry = JSON.parse(line);
149
- } catch {
150
- return { unavailable: "session-invalid" };
151
- }
152
- const parsed = object(entry);
153
- if (
154
- parsed?.type === "model_change" &&
155
- typeof parsed.provider === "string" &&
156
- typeof parsed.modelId === "string"
157
- ) {
158
- resolvedModel = { provider: parsed.provider, modelId: parsed.modelId };
194
+ const read = await readBoundedBytes(resolved, { maxBytes: MAX_USAGE_SESSION_BYTES, timeoutMs: 3000 });
195
+ if (!read.ok)
196
+ return { unavailable: "code" in read && read.code === "ENOENT" ? "session-missing" : "session-invalid" };
197
+ const source = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(read.bytes);
198
+ const branch = activeUsageBranch(source);
199
+ const userIndex = branch.findLastIndex((entry) => entry.type === "message" && entry.message.role === "user");
200
+ for (const [index, entry] of branch.entries()) {
201
+ if (entry.type === "model_change") {
202
+ if (typeof entry.provider !== "string" || typeof entry.modelId !== "string")
203
+ return { unavailable: "session-invalid" };
204
+ resolvedModel = { provider: entry.provider, modelId: entry.modelId };
159
205
  continue;
160
206
  }
161
- if (parsed?.type === "thinking_level_change" && typeof parsed.thinkingLevel === "string") {
162
- effectiveThinkingLevel = parsed.thinkingLevel;
207
+ if (entry.type === "thinking_level_change") {
208
+ if (typeof entry.thinkingLevel !== "string") return { unavailable: "session-invalid" };
209
+ effectiveThinkingLevel = entry.thinkingLevel;
163
210
  continue;
164
211
  }
165
- if (parsed?.type === "compaction") {
166
- compactionCount += 1;
167
- continue;
168
- }
169
- const message = object(parsed?.message);
170
- if (!message) continue;
171
- if (message.role === "user") {
172
- Object.assign(totals, emptyUsage());
173
- found = false;
174
- reasoning = 0;
175
- sawReasoning = false;
176
- compactionCount = 0;
177
- continue;
178
- }
179
- if (message.role !== "assistant" && message.role !== "toolResult") continue;
180
- if (message.role === "assistant" && typeof message.provider === "string" && typeof message.model === "string") {
181
- resolvedModel = { provider: message.provider, modelId: message.model };
182
- }
183
- const usage = object(message.usage);
184
- if (message.role === "toolResult" && !usage) continue;
212
+ // Current-turn accounting excludes both inherited prior turns and abandoned branch work.
213
+ if (userIndex < 0 || index <= userIndex) continue;
214
+ if (entry.type === "compaction") compactionCount += 1;
215
+ let usage: Record<string, unknown> | undefined;
216
+ if (entry.type === "message") {
217
+ const message = object(entry.message)!;
218
+ if (message.role !== "assistant" && message.role !== "toolResult") continue;
219
+ if (message.role === "assistant" && typeof message.provider === "string" && typeof message.model === "string")
220
+ resolvedModel = { provider: message.provider, modelId: message.model };
221
+ usage = object(message.usage);
222
+ if (message.role === "toolResult" && !usage) continue;
223
+ } else if (["usage", "compaction", "branch_summary"].includes(entry.type)) {
224
+ usage = object((entry as unknown as Record<string, unknown>).usage);
225
+ if (!usage) {
226
+ incomplete = true;
227
+ continue;
228
+ }
229
+ } else continue;
185
230
  const cost = object(usage?.cost);
186
- if (!usage || !cost) return { unavailable: "session-invalid" };
187
- if (!TOKEN_FIELDS.every((field) => nonNegative(usage[field]))) return { unavailable: "session-invalid" };
188
- if (!COST_FIELDS.every((field) => nonNegative(cost[field]))) return { unavailable: "session-invalid" };
189
- if (usage.reasoning !== undefined && !nonNegative(usage.reasoning)) return { unavailable: "session-invalid" };
231
+ if (
232
+ !usage ||
233
+ !cost ||
234
+ !TOKEN_FIELDS.every((field) => nonNegative(usage[field])) ||
235
+ !COST_FIELDS.every((field) => nonNegative(cost[field])) ||
236
+ (usage.reasoning !== undefined && !nonNegative(usage.reasoning))
237
+ )
238
+ return { unavailable: "session-invalid" };
190
239
  for (const field of TOKEN_FIELDS) totals[field] += usage[field] as number;
191
240
  for (const field of COST_FIELDS) totals.cost[field] += cost[field] as number;
192
241
  if (usage.reasoning !== undefined) {
193
242
  reasoning += usage.reasoning as number;
194
243
  sawReasoning = true;
195
244
  }
245
+ if (
246
+ !TOKEN_FIELDS.every((field) => nonNegative(totals[field])) ||
247
+ !COST_FIELDS.every((field) => nonNegative(totals.cost[field])) ||
248
+ !nonNegative(reasoning)
249
+ )
250
+ return { unavailable: "session-invalid" };
196
251
  found = true;
197
252
  }
198
- if (!found)
199
- return {
200
- unavailable: "usage-missing",
201
- ...(resolvedModel ? { resolvedModel } : {}),
202
- ...(effectiveThinkingLevel ? { effectiveThinkingLevel } : {}),
203
- compactionCount,
204
- };
205
- return {
206
- usage: { ...totals, ...(sawReasoning ? { reasoning } : {}) },
253
+ const metadata = {
207
254
  ...(resolvedModel ? { resolvedModel } : {}),
208
255
  ...(effectiveThinkingLevel ? { effectiveThinkingLevel } : {}),
256
+ compactionCount,
257
+ };
258
+ if (!found || incomplete) return { unavailable: "usage-missing", ...metadata };
259
+ return {
260
+ usage: { ...totals, ...(sawReasoning ? { reasoning } : {}) },
261
+ ...metadata,
209
262
  tokenDetail: {
210
263
  inputTokens: positiveOrNull(totals.input),
211
264
  outputTokens: positiveOrNull(totals.output),
@@ -213,10 +266,11 @@ async function readChildUsage(path: string | Promise<string | undefined>): Promi
213
266
  cacheWriteTokens: positiveOrNull(totals.cacheWrite),
214
267
  reasoningTokens: sawReasoning ? positiveOrNull(reasoning) : null,
215
268
  },
216
- compactionCount,
217
269
  };
218
- } catch {
219
- return { unavailable: "session-missing" };
270
+ } catch (error) {
271
+ // Preserve the exact retained descriptor capability for the initiating execution/session owner.
272
+ if (error instanceof BoundedReadCleanupError) throw error;
273
+ return { unavailable: "session-invalid" };
220
274
  }
221
275
  }
222
276