stitchkit 0.74.0 → 0.74.1

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.
@@ -18,6 +18,20 @@ export declare function parsePlatformUuid(dump: string): string | null;
18
18
  * removed — but which executable answers can be, and that makes the branch provable off darwin.
19
19
  */
20
20
  export declare function readDarwinPlatformUuid(command?: string): Promise<string | null>;
21
+ /**
22
+ * The state letter of a pid, or `null` where this platform will not say.
23
+ *
24
+ * `/proc/<pid>/stat` puts the command name in parentheses and the name may itself contain them, so
25
+ * the state follows the LAST `)` — reading the third whitespace-separated field instead is wrong for
26
+ * any process whose name has a space or a bracket in it.
27
+ *
28
+ * @internal `psCommand` is a seam for the tests: `/proc` is Linux-only, so the fallback branch is
29
+ * unreachable here without one, and an untested branch in a liveness probe is how this defect got in.
30
+ */
31
+ export declare function isZombieProcess(pid: number, seam?: {
32
+ procRoot?: string;
33
+ psCommand?: string;
34
+ }): Promise<boolean>;
21
35
  /**
22
36
  * Take the journal's exclusive lock under the configured policy.
23
37
  *
@@ -1 +1 @@
1
- {"version":3,"file":"diagnostic-journal-lock.d.ts","sourceRoot":"","sources":["../../src/application/diagnostic-journal-lock.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAoB,MAAM,kBAAkB,CAAC;AAI1D,OAAO,KAAK,EAEV,2BAA2B,EAC5B,MAAM,+BAA+B,CAAC;AAEvC,KAAK,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC;AAEnD,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AA6DD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7D;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,SAAU,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiBtF;AAiGD;;;;;;;;;;;;GAYG;AACH,wBAAsB,4BAA4B,CAChD,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,2BAA2B,EACnC,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,6BAA6B,CAAC,CA2BxC"}
1
+ {"version":3,"file":"diagnostic-journal-lock.d.ts","sourceRoot":"","sources":["../../src/application/diagnostic-journal-lock.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAoB,MAAM,kBAAkB,CAAC;AAI1D,OAAO,KAAK,EAEV,2BAA2B,EAC5B,MAAM,+BAA+B,CAAC;AAEvC,KAAK,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC;AAEnD,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AA6DD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7D;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,SAAU,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiBtF;AAgED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACnD,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAgDD;;;;;;;;;;;;GAYG;AACH,wBAAsB,4BAA4B,CAChD,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,2BAA2B,EACnC,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,6BAA6B,CAAC,CA2BxC"}
@@ -604,13 +604,30 @@ function attribute(owner, identity) {
604
604
  }
605
605
  return owner.host === hostname() ? "this-machine" : "unattributable";
606
606
  }
607
- function probeLiveness(owner) {
607
+ async function probeLiveness(owner) {
608
608
  try {
609
609
  process.kill(owner.pid, 0);
610
- return "alive";
611
610
  } catch (error) {
612
611
  return isRecord(error) && error.code === "ESRCH" ? "gone" : "alive";
613
612
  }
613
+ return await isZombieProcess(owner.pid) ? "gone" : "alive";
614
+ }
615
+ async function isZombieProcess(pid, seam = {}) {
616
+ const { procRoot = "/proc", psCommand = "ps" } = seam;
617
+ try {
618
+ const stat = await readFile(`${procRoot}/${pid}/stat`, "utf8");
619
+ return stat.slice(stat.lastIndexOf(")") + 1).trimStart().startsWith("Z");
620
+ } catch {}
621
+ try {
622
+ const { stdout } = await run(psCommand, ["-o", "state=", "-p", String(pid)], {
623
+ timeout: 2000,
624
+ maxBuffer: 1 << 16,
625
+ encoding: "utf8"
626
+ });
627
+ return stdout.trim().startsWith("Z");
628
+ } catch {
629
+ return false;
630
+ }
614
631
  }
615
632
  async function diagnose(owner, declaredIdentity) {
616
633
  if (!owner)
@@ -624,7 +641,7 @@ async function diagnose(owner, declaredIdentity) {
624
641
  };
625
642
  if (attribution !== "this-machine")
626
643
  return { attribution, liveness: "not-probed", owner: record };
627
- return { attribution, liveness: probeLiveness(owner), owner: record };
644
+ return { attribution, liveness: await probeLiveness(owner), owner: record };
628
645
  }
629
646
  async function readLockOwner(lockPath) {
630
647
  let handle;
package/llms-full.txt CHANGED
@@ -2137,9 +2137,11 @@ check({ when: new Date() }) // accepted: a live Date
2137
2137
  // server // refused: expected date, received string
2138
2138
  ```
2139
2139
 
2140
- This one fails in the safe direction — nothing valid is blocked — but it means a
2141
- local check passes traffic the server will reject, which is the opposite of what
2142
- it was added for.
2140
+ This one fails in the safe direction — nothing valid is blocked — but "safe" is
2141
+ about traffic, not about diagnostics. The check stays silent exactly where it
2142
+ promised to speak: the refusal then arrives from the server, through a different
2143
+ path and without the field names the local gate would have given. A gate that
2144
+ lets through what it was added to catch is not a cheap gate, it is a quiet one.
2143
2145
 
2144
2146
  One schema, one call, opposite answers — inside a single release, with no version
2145
2147
  skew involved. Argument validation therefore stays on the server, which is the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.74.0",
3
+ "version": "0.74.1",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",