@sentropic/h2a-runtime 0.98.6 → 0.98.7-rc.10

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 (236) hide show
  1. package/dist/agent-launch-args.d.ts.map +1 -1
  2. package/dist/agent-launch-args.js +5 -5
  3. package/dist/agent-launch-args.js.map +1 -1
  4. package/dist/agy-claude.d.ts +42 -0
  5. package/dist/agy-claude.d.ts.map +1 -0
  6. package/dist/agy-claude.js +148 -0
  7. package/dist/agy-claude.js.map +1 -0
  8. package/dist/agy-headless.d.ts +11 -0
  9. package/dist/agy-headless.d.ts.map +1 -0
  10. package/dist/agy-headless.js +104 -0
  11. package/dist/agy-headless.js.map +1 -0
  12. package/dist/attach.d.ts +5 -0
  13. package/dist/attach.d.ts.map +1 -1
  14. package/dist/attach.js +75 -6
  15. package/dist/attach.js.map +1 -1
  16. package/dist/auth-login.d.ts +24 -1
  17. package/dist/auth-login.d.ts.map +1 -1
  18. package/dist/auth-login.js +132 -4
  19. package/dist/auth-login.js.map +1 -1
  20. package/dist/central-mcp.d.ts +34 -0
  21. package/dist/central-mcp.d.ts.map +1 -1
  22. package/dist/central-mcp.js +275 -71
  23. package/dist/central-mcp.js.map +1 -1
  24. package/dist/claude-code-cli-isolation.d.ts +53 -0
  25. package/dist/claude-code-cli-isolation.d.ts.map +1 -0
  26. package/dist/claude-code-cli-isolation.js +277 -0
  27. package/dist/claude-code-cli-isolation.js.map +1 -0
  28. package/dist/claude-code-cli-lifecycle.d.ts +80 -0
  29. package/dist/claude-code-cli-lifecycle.d.ts.map +1 -0
  30. package/dist/claude-code-cli-lifecycle.js +402 -0
  31. package/dist/claude-code-cli-lifecycle.js.map +1 -0
  32. package/dist/claude-code-cli-runner.d.ts +89 -0
  33. package/dist/claude-code-cli-runner.d.ts.map +1 -0
  34. package/dist/claude-code-cli-runner.js +659 -0
  35. package/dist/claude-code-cli-runner.js.map +1 -0
  36. package/dist/claude-seat-input.d.ts +47 -0
  37. package/dist/claude-seat-input.d.ts.map +1 -0
  38. package/dist/claude-seat-input.js +272 -0
  39. package/dist/claude-seat-input.js.map +1 -0
  40. package/dist/claude-seat-mesh-contract.d.ts +88 -0
  41. package/dist/claude-seat-mesh-contract.d.ts.map +1 -0
  42. package/dist/claude-seat-mesh-contract.js +13 -0
  43. package/dist/claude-seat-mesh-contract.js.map +1 -0
  44. package/dist/cli-help-groups.js +1 -1
  45. package/dist/cli-help-groups.js.map +1 -1
  46. package/dist/cli-seat-codecs.d.ts +86 -0
  47. package/dist/cli-seat-codecs.d.ts.map +1 -0
  48. package/dist/cli-seat-codecs.js +255 -0
  49. package/dist/cli-seat-codecs.js.map +1 -0
  50. package/dist/cli-seat-fixtures.d.ts +86 -0
  51. package/dist/cli-seat-fixtures.d.ts.map +1 -0
  52. package/dist/cli-seat-fixtures.js +114 -0
  53. package/dist/cli-seat-fixtures.js.map +1 -0
  54. package/dist/cli-seat-materialization.d.ts +245 -0
  55. package/dist/cli-seat-materialization.d.ts.map +1 -0
  56. package/dist/cli-seat-materialization.js +646 -0
  57. package/dist/cli-seat-materialization.js.map +1 -0
  58. package/dist/conv-guard.d.ts.map +1 -1
  59. package/dist/conv-guard.js +6 -2
  60. package/dist/conv-guard.js.map +1 -1
  61. package/dist/crash-log.d.ts +112 -0
  62. package/dist/crash-log.d.ts.map +1 -0
  63. package/dist/crash-log.js +560 -0
  64. package/dist/crash-log.js.map +1 -0
  65. package/dist/delegate.d.ts +10 -0
  66. package/dist/delegate.d.ts.map +1 -1
  67. package/dist/delegate.js +29 -0
  68. package/dist/delegate.js.map +1 -1
  69. package/dist/file-lock.d.ts +4 -2
  70. package/dist/file-lock.d.ts.map +1 -1
  71. package/dist/file-lock.js +24 -10
  72. package/dist/file-lock.js.map +1 -1
  73. package/dist/gateway-host/claude-seat-client.d.ts +32 -0
  74. package/dist/gateway-host/claude-seat-client.d.ts.map +1 -0
  75. package/dist/gateway-host/claude-seat-client.js +38 -0
  76. package/dist/gateway-host/claude-seat-client.js.map +1 -0
  77. package/dist/gateway-host/config-file.d.ts +2 -0
  78. package/dist/gateway-host/config-file.d.ts.map +1 -1
  79. package/dist/gateway-host/config-file.js +5 -1
  80. package/dist/gateway-host/config-file.js.map +1 -1
  81. package/dist/gateway-host/daemon.d.ts.map +1 -1
  82. package/dist/gateway-host/daemon.js +1 -2
  83. package/dist/gateway-host/daemon.js.map +1 -1
  84. package/dist/gateway-host/host.d.ts +6 -0
  85. package/dist/gateway-host/host.d.ts.map +1 -1
  86. package/dist/gateway-host/host.js +24 -3
  87. package/dist/gateway-host/host.js.map +1 -1
  88. package/dist/gateway-host/live-auth.d.ts +62 -0
  89. package/dist/gateway-host/live-auth.d.ts.map +1 -0
  90. package/dist/gateway-host/live-auth.js +87 -0
  91. package/dist/gateway-host/live-auth.js.map +1 -0
  92. package/dist/gateway-host/live-declarations.d.ts +58 -0
  93. package/dist/gateway-host/live-declarations.d.ts.map +1 -0
  94. package/dist/gateway-host/live-declarations.js +429 -0
  95. package/dist/gateway-host/live-declarations.js.map +1 -0
  96. package/dist/gateway-host/live-disclosure.d.ts +124 -0
  97. package/dist/gateway-host/live-disclosure.d.ts.map +1 -0
  98. package/dist/gateway-host/live-disclosure.js +347 -0
  99. package/dist/gateway-host/live-disclosure.js.map +1 -0
  100. package/dist/gateway-host/live-github.d.ts +11 -0
  101. package/dist/gateway-host/live-github.d.ts.map +1 -0
  102. package/dist/gateway-host/live-github.js +374 -0
  103. package/dist/gateway-host/live-github.js.map +1 -0
  104. package/dist/gateway-host/live-host.d.ts +20 -0
  105. package/dist/gateway-host/live-host.d.ts.map +1 -0
  106. package/dist/gateway-host/live-host.js +14 -0
  107. package/dist/gateway-host/live-host.js.map +1 -0
  108. package/dist/gateway-host/live-object-store.d.ts +35 -0
  109. package/dist/gateway-host/live-object-store.d.ts.map +1 -0
  110. package/dist/gateway-host/live-object-store.js +447 -0
  111. package/dist/gateway-host/live-object-store.js.map +1 -0
  112. package/dist/gateway-host/live-registration.d.ts +162 -0
  113. package/dist/gateway-host/live-registration.d.ts.map +1 -0
  114. package/dist/gateway-host/live-registration.js +455 -0
  115. package/dist/gateway-host/live-registration.js.map +1 -0
  116. package/dist/gateway-model-catalog/model-catalog.d.ts +38 -0
  117. package/dist/gateway-model-catalog/model-catalog.d.ts.map +1 -0
  118. package/dist/gateway-model-catalog/model-catalog.js +167 -0
  119. package/dist/gateway-model-catalog/model-catalog.js.map +1 -0
  120. package/dist/identity-cull/cull.js +1 -1
  121. package/dist/identity-cull/cull.js.map +1 -1
  122. package/dist/identity-cull/identity-fence.d.ts +48 -0
  123. package/dist/identity-cull/identity-fence.d.ts.map +1 -0
  124. package/dist/identity-cull/identity-fence.js +221 -0
  125. package/dist/identity-cull/identity-fence.js.map +1 -0
  126. package/dist/identity-operator-break.d.ts +43 -0
  127. package/dist/identity-operator-break.d.ts.map +1 -0
  128. package/dist/identity-operator-break.js +85 -0
  129. package/dist/identity-operator-break.js.map +1 -0
  130. package/dist/index.d.ts +8 -1
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +525 -132
  133. package/dist/index.js.map +1 -1
  134. package/dist/launch-layout.d.ts.map +1 -1
  135. package/dist/launch-layout.js +10 -2
  136. package/dist/launch-layout.js.map +1 -1
  137. package/dist/llm-mesh-accounts.d.ts +128 -6
  138. package/dist/llm-mesh-accounts.d.ts.map +1 -1
  139. package/dist/llm-mesh-accounts.js +370 -14
  140. package/dist/llm-mesh-accounts.js.map +1 -1
  141. package/dist/native-host.d.ts +25 -1
  142. package/dist/native-host.d.ts.map +1 -1
  143. package/dist/native-host.js +211 -28
  144. package/dist/native-host.js.map +1 -1
  145. package/dist/native-restart.d.ts +2 -0
  146. package/dist/native-restart.d.ts.map +1 -1
  147. package/dist/native-restart.js +7 -0
  148. package/dist/native-restart.js.map +1 -1
  149. package/dist/native-terminal/fleet.d.ts +11 -0
  150. package/dist/native-terminal/fleet.d.ts.map +1 -1
  151. package/dist/native-terminal/fleet.js +58 -1
  152. package/dist/native-terminal/fleet.js.map +1 -1
  153. package/dist/native-terminal/host-scope.d.ts +18 -0
  154. package/dist/native-terminal/host-scope.d.ts.map +1 -0
  155. package/dist/native-terminal/host-scope.js +65 -0
  156. package/dist/native-terminal/host-scope.js.map +1 -0
  157. package/dist/native-terminal/host.d.ts +7 -1
  158. package/dist/native-terminal/host.d.ts.map +1 -1
  159. package/dist/native-terminal/host.js +10 -26
  160. package/dist/native-terminal/host.js.map +1 -1
  161. package/dist/native-terminal/journal.d.ts +5 -0
  162. package/dist/native-terminal/journal.d.ts.map +1 -1
  163. package/dist/native-terminal/journal.js +28 -16
  164. package/dist/native-terminal/journal.js.map +1 -1
  165. package/dist/native-terminal/op.d.ts +49 -1
  166. package/dist/native-terminal/op.d.ts.map +1 -1
  167. package/dist/native-terminal/op.js +254 -38
  168. package/dist/native-terminal/op.js.map +1 -1
  169. package/dist/native-terminal/process.d.ts.map +1 -1
  170. package/dist/native-terminal/process.js +10 -1
  171. package/dist/native-terminal/process.js.map +1 -1
  172. package/dist/native-terminal/screen.d.ts.map +1 -1
  173. package/dist/native-terminal/screen.js +1 -1
  174. package/dist/native-terminal/screen.js.map +1 -1
  175. package/dist/native-terminal/server.d.ts +12 -2
  176. package/dist/native-terminal/server.d.ts.map +1 -1
  177. package/dist/native-terminal/server.js +143 -42
  178. package/dist/native-terminal/server.js.map +1 -1
  179. package/dist/native-terminal/session-scope.d.ts +2 -0
  180. package/dist/native-terminal/session-scope.d.ts.map +1 -0
  181. package/dist/native-terminal/session-scope.js +32 -0
  182. package/dist/native-terminal/session-scope.js.map +1 -0
  183. package/dist/proc-identity.d.ts +31 -0
  184. package/dist/proc-identity.d.ts.map +1 -1
  185. package/dist/proc-identity.js +54 -0
  186. package/dist/proc-identity.js.map +1 -1
  187. package/dist/prompt-delivery.d.ts +3 -0
  188. package/dist/prompt-delivery.d.ts.map +1 -1
  189. package/dist/prompt-delivery.js +8 -3
  190. package/dist/prompt-delivery.js.map +1 -1
  191. package/dist/protocol-local.d.ts +16 -0
  192. package/dist/protocol-local.d.ts.map +1 -1
  193. package/dist/protocol-local.js +14 -0
  194. package/dist/protocol-local.js.map +1 -1
  195. package/dist/registry.d.ts +16 -12
  196. package/dist/registry.d.ts.map +1 -1
  197. package/dist/registry.js +39 -5
  198. package/dist/registry.js.map +1 -1
  199. package/dist/relaunch.d.ts +16 -0
  200. package/dist/relaunch.d.ts.map +1 -1
  201. package/dist/relaunch.js +42 -1
  202. package/dist/relaunch.js.map +1 -1
  203. package/dist/restore.d.ts +4 -0
  204. package/dist/restore.d.ts.map +1 -1
  205. package/dist/restore.js +9 -37
  206. package/dist/restore.js.map +1 -1
  207. package/dist/resume-launch-options.d.ts +3 -3
  208. package/dist/resume-launch-options.d.ts.map +1 -1
  209. package/dist/resume-launch-options.js +7 -4
  210. package/dist/resume-launch-options.js.map +1 -1
  211. package/dist/run.d.ts +2 -0
  212. package/dist/run.d.ts.map +1 -1
  213. package/dist/run.js +88 -20
  214. package/dist/run.js.map +1 -1
  215. package/dist/soft-refresh.d.ts.map +1 -1
  216. package/dist/soft-refresh.js +6 -0
  217. package/dist/soft-refresh.js.map +1 -1
  218. package/dist/terminal-reset.d.ts +104 -0
  219. package/dist/terminal-reset.d.ts.map +1 -0
  220. package/dist/terminal-reset.js +229 -0
  221. package/dist/terminal-reset.js.map +1 -0
  222. package/dist/tmux.d.ts +54 -8
  223. package/dist/tmux.d.ts.map +1 -1
  224. package/dist/tmux.js +143 -36
  225. package/dist/tmux.js.map +1 -1
  226. package/dist/vendor/engram-live/contracts.d.ts +825 -0
  227. package/dist/vendor/engram-live/contracts.js +1734 -0
  228. package/dist/vendor/engram-live/integration.d.ts +113 -0
  229. package/dist/vendor/engram-live/integration.js +254 -0
  230. package/dist/vendor/engram-live/pr-sync.d.ts +144 -0
  231. package/dist/vendor/engram-live/pr-sync.js +411 -0
  232. package/dist/vendor/engram-live/registration.d.ts +138 -0
  233. package/dist/vendor/engram-live/registration.js +358 -0
  234. package/dist/vendor/engram-live/retention.d.ts +56 -0
  235. package/dist/vendor/engram-live/retention.js +179 -0
  236. package/package.json +6 -6
@@ -0,0 +1,62 @@
1
+ /** Live disclosure uses registered host keys, never loopback/session bearers.
2
+ * All ports are host-owned. This module neither discovers identity nor reads
3
+ * owner state. Missing trust/declaration/revocation information fails closed.
4
+ */
5
+ import { type KeyObject } from "node:crypto";
6
+ export declare const LIVE_DISCLOSURE_AUDIENCE = "h2a:live-disclosure:v1";
7
+ export type DisclosureOperation = "grant" | "revoke" | "emit";
8
+ export type DisclosureRefusal = "authority-unavailable" | "unverified-principal" | "wrong-audience" | "capability-expired" | "capability-revoked" | "operation-denied" | "scope-mismatch" | "policy-changed" | "invalid-request" | "read-denied" | "grant-required" | "grant-expired" | "grant-revoked" | "provider-mismatch" | "out-of-scope" | "secret-excluded" | "stale-evidence" | "evidence-unavailable" | "policy-unavailable" | "audit-unavailable" | "transport-failed";
9
+ export type Refused = Readonly<{
10
+ ok: false;
11
+ reason: DisclosureRefusal;
12
+ }>;
13
+ export declare const refuse: (reason: DisclosureRefusal) => Refused;
14
+ export interface LiveDisclosureState {
15
+ readonly principal: string;
16
+ readonly hostId: string;
17
+ readonly tenant: string;
18
+ /** Host-derived opaque clone identity, never a URL or workspace fingerprint. */
19
+ readonly repositoryId: string;
20
+ readonly declarationId: string;
21
+ readonly declarationRevision: number;
22
+ readonly policyVersion: string;
23
+ readonly authorizedRoots: readonly string[];
24
+ /** Latest local state; cached derivatives must still match it at emission. */
25
+ readonly stateRevision: string;
26
+ }
27
+ export interface LiveDisclosureClaims extends Omit<LiveDisclosureState, "stateRevision"> {
28
+ readonly id: string;
29
+ readonly audience: string;
30
+ readonly keyId: string;
31
+ readonly issuedAt: number;
32
+ readonly expiresAt: number;
33
+ readonly operations: readonly DisclosureOperation[];
34
+ }
35
+ export interface LiveDisclosureCapability {
36
+ readonly claims: LiveDisclosureClaims;
37
+ readonly signature: string;
38
+ }
39
+ /** Fixed domain and field order: signatures cannot be reused as session proofs. */
40
+ export declare function liveDisclosureSigningBytes(c: LiveDisclosureClaims): Buffer;
41
+ export interface LiveDisclosureAuthOptions {
42
+ readonly now?: () => number;
43
+ readonly trust?: (keyId: string) => Readonly<{
44
+ principal: string;
45
+ hostId: string;
46
+ tenant: string;
47
+ publicKey: KeyObject;
48
+ }> | undefined;
49
+ readonly readState?: (repositoryId: string) => LiveDisclosureState | undefined;
50
+ /** Undefined means revocation status is unavailable, not active. */
51
+ readonly isRevoked?: (capabilityId: string) => boolean | undefined;
52
+ }
53
+ export type LiveDisclosureAuthorization = Refused | Readonly<{
54
+ ok: true;
55
+ claims: LiveDisclosureClaims;
56
+ state: LiveDisclosureState;
57
+ }>;
58
+ export interface LiveDisclosureAuth {
59
+ authorize(capability: unknown, operation: DisclosureOperation, repositoryId?: string): LiveDisclosureAuthorization;
60
+ }
61
+ export declare function createLiveDisclosureAuth(options?: LiveDisclosureAuthOptions): LiveDisclosureAuth;
62
+ //# sourceMappingURL=live-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-auth.d.ts","sourceRoot":"","sources":["../../src/gateway-host/live-auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ /** Live disclosure uses registered host keys, never loopback/session bearers.
2
+ * All ports are host-owned. This module neither discovers identity nor reads
3
+ * owner state. Missing trust/declaration/revocation information fails closed.
4
+ */
5
+ import { verify } from "node:crypto";
6
+ export const LIVE_DISCLOSURE_AUDIENCE = "h2a:live-disclosure:v1";
7
+ export const refuse = (reason) => Object.freeze({ ok: false, reason });
8
+ /** Fixed domain and field order: signatures cannot be reused as session proofs. */
9
+ export function liveDisclosureSigningBytes(c) {
10
+ return Buffer.from(JSON.stringify([LIVE_DISCLOSURE_AUDIENCE, c.id, c.audience,
11
+ c.keyId, c.principal, c.hostId, c.tenant, c.repositoryId, c.declarationId,
12
+ c.declarationRevision, c.policyVersion, c.authorizedRoots, c.issuedAt,
13
+ c.expiresAt, c.operations]));
14
+ }
15
+ const nonempty = (v) => typeof v === "string" && v.length > 0 && !v.includes("\0");
16
+ function validClaims(c) {
17
+ return [c.id, c.keyId, c.audience, c.principal, c.hostId, c.tenant,
18
+ c.repositoryId, c.declarationId, c.policyVersion].every(nonempty)
19
+ && c.tenant.startsWith("local:") && Number.isSafeInteger(c.declarationRevision) && c.declarationRevision > 0
20
+ && Number.isSafeInteger(c.issuedAt) && Number.isSafeInteger(c.expiresAt)
21
+ && Array.isArray(c.authorizedRoots) && c.authorizedRoots.length > 0 && c.authorizedRoots.every(nonempty)
22
+ && Array.isArray(c.operations) && c.operations.length > 0
23
+ && c.operations.every((op) => ["grant", "revoke", "emit"].includes(op));
24
+ }
25
+ export function createLiveDisclosureAuth(options = {}) {
26
+ const now = options.now ?? Date.now;
27
+ return { authorize(input, operation, repositoryId) {
28
+ if (!options.trust || !options.readState || !options.isRevoked)
29
+ return refuse("authority-unavailable");
30
+ let proof;
31
+ try {
32
+ // Freeze caller-controlled bytes before consulting any host port.
33
+ proof = JSON.parse(JSON.stringify(input));
34
+ if (!proof || !proof.claims || !validClaims(proof.claims) || typeof proof.signature !== "string")
35
+ return refuse("unverified-principal");
36
+ }
37
+ catch {
38
+ return refuse("unverified-principal");
39
+ }
40
+ try {
41
+ const c = proof.claims;
42
+ const trust = options.trust(c.keyId);
43
+ if (!trust || trust.principal !== c.principal || trust.hostId !== c.hostId || trust.tenant !== c.tenant
44
+ || trust.publicKey.asymmetricKeyType !== "ed25519"
45
+ || !verify(null, liveDisclosureSigningBytes(c), trust.publicKey, Buffer.from(proof.signature, "base64url"))) {
46
+ return refuse("unverified-principal");
47
+ }
48
+ if (c.audience !== LIVE_DISCLOSURE_AUDIENCE)
49
+ return refuse("wrong-audience");
50
+ const time = now();
51
+ if (!Number.isSafeInteger(time))
52
+ return refuse("authority-unavailable");
53
+ if (c.issuedAt > time || c.expiresAt <= time || c.expiresAt <= c.issuedAt)
54
+ return refuse("capability-expired");
55
+ const revoked = options.isRevoked(c.id);
56
+ if (revoked === true)
57
+ return refuse("capability-revoked");
58
+ if (revoked !== false)
59
+ return refuse("authority-unavailable");
60
+ if (!c.operations.includes(operation))
61
+ return refuse("operation-denied");
62
+ // Revocation authenticates the signed owner/repository, without requiring
63
+ // the declaration, roots or content policy that it is trying to revoke.
64
+ const target = repositoryId ?? (operation === "revoke" ? c.repositoryId : undefined);
65
+ if (c.repositoryId !== target)
66
+ return refuse("scope-mismatch");
67
+ const state = options.readState(target);
68
+ if (!state || state.repositoryId !== target
69
+ || ["principal", "hostId", "tenant"].some((key) => c[key] !== state[key]))
70
+ return refuse("scope-mismatch");
71
+ if (operation !== "revoke" && (["declarationId", "declarationRevision"].some((key) => c[key] !== state[key])
72
+ || JSON.stringify(c.authorizedRoots) !== JSON.stringify(state.authorizedRoots)
73
+ || !nonempty(state.stateRevision)))
74
+ return refuse("scope-mismatch");
75
+ if (operation !== "revoke" && state.policyVersion !== c.policyVersion)
76
+ return refuse("policy-changed");
77
+ Object.freeze(c.authorizedRoots);
78
+ Object.freeze(c.operations);
79
+ return Object.freeze({ ok: true, claims: Object.freeze(c),
80
+ state: Object.freeze({ ...state, authorizedRoots: Object.freeze([...state.authorizedRoots]) }) });
81
+ }
82
+ catch {
83
+ return refuse("authority-unavailable");
84
+ }
85
+ } };
86
+ }
87
+ //# sourceMappingURL=live-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-auth.js","sourceRoot":"","sources":["../../src/gateway-host/live-auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import type { CloneIdentityDto, WorkspaceDeclarationDto, WorktreeIdentityDto } from "../vendor/engram-live/contracts.js";
2
+ export declare const LIVE_DECLARATION_KIND = "h2a-live-declaration";
3
+ export declare const LIVE_DECLARATION_SCHEMA_VERSION = 1;
4
+ /**
5
+ * Typed revision conflict: the durable declaration moved (or vanished)
6
+ * under a writer holding a stale base revision. The message keeps the
7
+ * `revision_conflict` marker; the name and the revision fields let callers
8
+ * branch on it without parsing text.
9
+ */
10
+ export declare class LiveRevisionConflict extends Error {
11
+ readonly declarationId: string;
12
+ readonly expectedDeclarationRevision: number;
13
+ /** Current durable revision; undefined when the declaration is gone. */
14
+ readonly actualDeclarationRevision: number | undefined;
15
+ constructor(declarationId: string, expectedDeclarationRevision: number, actualDeclarationRevision: number | undefined);
16
+ }
17
+ export interface LiveDeclarationRecord {
18
+ readonly kind: typeof LIVE_DECLARATION_KIND;
19
+ readonly schemaVersion: typeof LIVE_DECLARATION_SCHEMA_VERSION;
20
+ /** The exact neutral DTO; validated with validateWorkspaceDeclaration on every write. */
21
+ readonly declaration: WorkspaceDeclarationDto;
22
+ /** Full local clone identity (commonDirRealpath stays host-local; only cloneId is shared). */
23
+ readonly clone: CloneIdentityDto;
24
+ /** Full local worktree identity. */
25
+ readonly worktree: WorktreeIdentityDto;
26
+ readonly createdAt: string;
27
+ /** Activation timestamp; replay after restart never renews it. */
28
+ readonly activatedAt: string;
29
+ readonly updatedAt: string;
30
+ }
31
+ /** Durable declarations live under the injected h2a root, never a worktree. */
32
+ export declare function liveDeclarationsPath(root: string): string;
33
+ export interface LiveDeclarationStore {
34
+ /** All durable declarations, deterministically ordered. */
35
+ list(): readonly LiveDeclarationRecord[];
36
+ get(declarationId: string): LiveDeclarationRecord | undefined;
37
+ /** First declaration whose authorized root covers `path`. */
38
+ findByPath(path: string): LiveDeclarationRecord | undefined;
39
+ insert(record: LiveDeclarationRecord): void;
40
+ /**
41
+ * Revision-guarded mutation: re-reads the durable store, refuses a stale
42
+ * `expectedDeclarationRevision` with `revision_conflict`, verifies the
43
+ * composed next record (declarationRevision exactly + 1, immutable
44
+ * identity triangle, binding renewal-or-preserved with first binding
45
+ * exactly INITIAL_BINDING_REVISION then strictly greater, valid DTOs)
46
+ * and persists atomically. The callback returns the fully composed next
47
+ * record; the store owns the guard, never silent arithmetic.
48
+ */
49
+ update(declarationId: string, expectedDeclarationRevision: number, mutate: (record: LiveDeclarationRecord) => LiveDeclarationRecord): LiveDeclarationRecord;
50
+ remove(declarationId: string): boolean;
51
+ }
52
+ /**
53
+ * A store instance is a thin read-through over the durable file: every
54
+ * operation re-reads, so two instances (or two processes) on the same root
55
+ * observe each other's committed writes.
56
+ */
57
+ export declare function createLiveDeclarationStore(root: string): LiveDeclarationStore;
58
+ //# sourceMappingURL=live-declarations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-declarations.d.ts","sourceRoot":"","sources":["../../src/gateway-host/live-declarations.ts"],"names":[],"mappings":"AAwDA,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EACvB,mBAAmB,EACpB,MAAM,oCAAoC,CAAC;AAE5C,eAAO,MAAM,qBAAqB,yBAAyB,CAAC;AAC5D,eAAO,MAAM,+BAA+B,IAAI,CAAC;AAEjD;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,2BAA2B,EAAE,MAAM,CAAC;IAC7C,wEAAwE;IACxE,QAAQ,CAAC,yBAAyB,EAAE,MAAM,GAAG,SAAS,CAAC;gBAGrD,aAAa,EAAE,MAAM,EACrB,2BAA2B,EAAE,MAAM,EACnC,yBAAyB,EAAE,MAAM,GAAG,SAAS;CAahD;AAqHD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,OAAO,qBAAqB,CAAC;IAC5C,QAAQ,CAAC,aAAa,EAAE,OAAO,+BAA+B,CAAC;IAC/D,yFAAyF;IACzF,QAAQ,CAAC,WAAW,EAAE,uBAAuB,CAAC;IAC9C,8FAA8F;IAC9F,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAkED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AA4CD,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,IAAI,IAAI,SAAS,qBAAqB,EAAE,CAAC;IACzC,GAAG,CAAC,aAAa,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAAC;IAC9D,6DAA6D;IAC7D,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAAC;IAC5D,MAAM,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC5C;;;;;;;;OAQG;IACH,MAAM,CACJ,aAAa,EAAE,MAAM,EACrB,2BAA2B,EAAE,MAAM,EACnC,MAAM,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,qBAAqB,GAC/D,qBAAqB,CAAC;IACzB,MAAM,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;CACxC;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,CAmN7E"}
@@ -0,0 +1,429 @@
1
+ /**
2
+ * L6a (H) — durable local store for explicit live workspace declarations.
3
+ *
4
+ * DESIGN (LIVE §2, §12):
5
+ * - The declaration store is LOCAL h2a state under the injected h2a root
6
+ * (<root>/live/declarations.json), outside every observed worktree and
7
+ * its git-dir/common-dir. It is authoritative independently of the live
8
+ * projection: losing the projection never loses registrations, and the
9
+ * store is never a projection-owned surface.
10
+ * - Each record wraps the EXACT neutral WorkspaceDeclarationDto (flat
11
+ * identity triangle declarationId/cloneId/worktreeId, tenant,
12
+ * liveCapability, declarationRevision, flat product binding) plus the
13
+ * full local CloneIdentityDto/WorktreeIdentityDto (host-local realpath,
14
+ * git-dir and paths needed to operate; only the opaque ids cross to
15
+ * sentropic). Every write is validated with the engram validators — the
16
+ * runtime keeps no mirror of the contract, it imports the vendored
17
+ * neutral contracts entry (../vendor/engram-live, see the
18
+ * core live/contracts.ts header).
19
+ * - Writes are atomic (0600, tmp+rename) through a token-owned staging
20
+ * file; a corrupt store fails EXPLICITLY, it never silently
21
+ * restarts empty (that would erase desired live registrations). A
22
+ * pre-realignment (nested, `live-git-1`) store file is rejected
23
+ * EXPLICITLY as unsupported — never silently migrated, never silently
24
+ * read: L6a is not consumed yet, so no migration replay exists and the
25
+ * operator removes the file to start fresh.
26
+ * - Update is revision-guarded ATOMICALLY: the caller composes the next
27
+ * record through the contract rules (declarationRevision + 1, immutable
28
+ * identity triangle, binding renewal-or-preserved); the store verifies
29
+ * the guard, the triangle, the binding monotonicity (first binding
30
+ * exactly INITIAL_BINDING_REVISION, then strictly greater) and the
31
+ * final DTOs, then re-verifies the durable revision and persists under
32
+ * ONE exclusive lock. A stale writer is refused with the typed
33
+ * LiveRevisionConflict, never clobbering; a declaration removed after
34
+ * the read is never resurrected. Insert and remove hold the same lock,
35
+ * so every read-check-write (including removals) is serialized.
36
+ */
37
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
38
+ import { randomUUID } from "node:crypto";
39
+ import { join } from "node:path";
40
+ import { INITIAL_BINDING_REVISION, validateCloneIdentity, validateWorkspaceDeclaration, validateWorktreeIdentity, } from "../vendor/engram-live/contracts.js";
41
+ export const LIVE_DECLARATION_KIND = "h2a-live-declaration";
42
+ export const LIVE_DECLARATION_SCHEMA_VERSION = 1;
43
+ /**
44
+ * Typed revision conflict: the durable declaration moved (or vanished)
45
+ * under a writer holding a stale base revision. The message keeps the
46
+ * `revision_conflict` marker; the name and the revision fields let callers
47
+ * branch on it without parsing text.
48
+ */
49
+ export class LiveRevisionConflict extends Error {
50
+ declarationId;
51
+ expectedDeclarationRevision;
52
+ /** Current durable revision; undefined when the declaration is gone. */
53
+ actualDeclarationRevision;
54
+ constructor(declarationId, expectedDeclarationRevision, actualDeclarationRevision) {
55
+ super(actualDeclarationRevision === undefined
56
+ ? `revision_conflict: live declaration ${declarationId} was removed; stale writer held declaration revision ${expectedDeclarationRevision}`
57
+ : `revision_conflict: live declaration ${declarationId} is at declaration revision ` +
58
+ `${actualDeclarationRevision}, stale writer held ${expectedDeclarationRevision}`);
59
+ this.name = "LiveRevisionConflict";
60
+ this.declarationId = declarationId;
61
+ this.expectedDeclarationRevision = expectedDeclarationRevision;
62
+ this.actualDeclarationRevision = actualDeclarationRevision;
63
+ }
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+ // Exclusive store lock (synchronous, no dependency).
67
+ // ---------------------------------------------------------------------------
68
+ //
69
+ // Every mutation (insert/update/remove) holds this lock from its read to
70
+ // its persisted rename, so no stale revision can slip between the guard
71
+ // check and the write — across processes as well as across store
72
+ // instances. The lock is an atomically published nonempty directory; acquisition
73
+ // retries briefly, recovers abandoned locks by age, then fails explicitly
74
+ // (never silently proceeding unlocked). Re-entry by the SAME process only
75
+ // bumps a counter: the pre-persist re-verification inside update() still
76
+ // rejects a same-process interleaving, so re-entrancy never hides a
77
+ // conflict. Each acquisition has a unique token directory: takeover removes
78
+ // that generation, including its staged writes. Even a holder suspended
79
+ // after its final fence check cannot rename a removed source into the store.
80
+ // Reads stay lock-free: the tmp+rename persistence is atomic,
81
+ // so readers always observe one whole committed file.
82
+ const STORE_LOCK_DIRNAME = ".declarations.lock";
83
+ const STORE_LOCK_TIMEOUT_MS = 5_000;
84
+ const STORE_LOCK_STALE_MS = 10_000;
85
+ const STORE_LOCK_POLL_MS = 5;
86
+ /** Re-entry shares the SAME generation, never a successor's token. */
87
+ const storeLocksHeld = new Map();
88
+ const lockDirFor = (root) => join(root, "live", STORE_LOCK_DIRNAME);
89
+ const sleepSync = (ms) => {
90
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
91
+ };
92
+ /** Remove only the observed generation; never recursively delete a successor. */
93
+ function removeStoreLockGeneration(dir, token) {
94
+ rmSync(join(dir, token), { recursive: true, force: true });
95
+ try {
96
+ rmdirSync(dir);
97
+ }
98
+ catch (error) {
99
+ const code = error.code;
100
+ if (code !== "ENOENT" && code !== "ENOTEMPTY" && code !== "EEXIST")
101
+ throw error;
102
+ }
103
+ }
104
+ function acquireStoreLock(root) {
105
+ const dir = lockDirFor(root);
106
+ mkdirSync(join(root, "live"), { recursive: true });
107
+ const held = storeLocksHeld.get(dir);
108
+ if (held !== undefined) {
109
+ held.depth += 1;
110
+ return held;
111
+ }
112
+ const deadline = Date.now() + STORE_LOCK_TIMEOUT_MS;
113
+ for (;;) {
114
+ const lock = { token: randomUUID(), depth: 1 };
115
+ const candidate = `${dir}.${lock.token}.candidate`;
116
+ try {
117
+ mkdirSync(candidate, { mode: 0o700 });
118
+ mkdirSync(join(candidate, lock.token), { mode: 0o700 });
119
+ // One atomic publication, with the generation already inside. An
120
+ // existing nonempty lock cannot be replaced by rename. A suspended
121
+ // publisher can never resume by adding its token to a successor.
122
+ renameSync(candidate, dir);
123
+ storeLocksHeld.set(dir, lock);
124
+ return lock;
125
+ }
126
+ catch (error) {
127
+ const code = error.code;
128
+ if (code !== "EEXIST" && code !== "ENOENT" && code !== "ENOTEMPTY")
129
+ throw error;
130
+ }
131
+ finally {
132
+ rmSync(candidate, { recursive: true, force: true });
133
+ }
134
+ // Age permits takeover, but does NOT prove the holder stopped. Invalidate
135
+ // only the generation observed before the age check. A competing cleanup
136
+ // cannot erase a newly acquired token or its staged write.
137
+ try {
138
+ const tokens = readdirSync(dir);
139
+ if (Date.now() - statSync(dir).mtimeMs > STORE_LOCK_STALE_MS) {
140
+ for (const token of tokens)
141
+ removeStoreLockGeneration(dir, token);
142
+ if (tokens.length === 0)
143
+ rmdirSync(dir);
144
+ }
145
+ }
146
+ catch {
147
+ // The directory vanished under us; the next iteration retries.
148
+ }
149
+ if (Date.now() >= deadline) {
150
+ throw new Error(`live declarations store busy: cannot acquire lock at ${dir}`);
151
+ }
152
+ sleepSync(STORE_LOCK_POLL_MS);
153
+ }
154
+ }
155
+ function releaseStoreLock(root) {
156
+ const dir = lockDirFor(root);
157
+ const held = storeLocksHeld.get(dir);
158
+ if (held === undefined)
159
+ return;
160
+ if (held.depth > 1) {
161
+ held.depth -= 1;
162
+ return;
163
+ }
164
+ storeLocksHeld.delete(dir);
165
+ removeStoreLockGeneration(dir, held.token);
166
+ }
167
+ function withStoreLock(root, fn) {
168
+ const lock = acquireStoreLock(root);
169
+ try {
170
+ return fn(lock);
171
+ }
172
+ finally {
173
+ releaseStoreLock(root);
174
+ }
175
+ }
176
+ /** Structural validation of a record: envelope, triangle consistency, then the engram validators. */
177
+ const validateRecord = (record) => {
178
+ if (record.kind !== LIVE_DECLARATION_KIND)
179
+ return "unexpected declaration kind";
180
+ if (record.schemaVersion !== LIVE_DECLARATION_SCHEMA_VERSION) {
181
+ return "unsupported declaration schema version";
182
+ }
183
+ const declaration = validateWorkspaceDeclaration(record.declaration);
184
+ if (!declaration.ok)
185
+ return `invalid declaration: ${declaration.errors.join("; ")}`;
186
+ if (record.clone.cloneId !== record.declaration.cloneId) {
187
+ return "clone/worktree attachment mismatch: clone.cloneId differs from declaration.cloneId";
188
+ }
189
+ if (record.worktree.cloneId !== record.declaration.cloneId) {
190
+ return "clone/worktree attachment mismatch: worktree.cloneId differs from declaration.cloneId";
191
+ }
192
+ if (record.worktree.worktreeId !== record.declaration.worktreeId) {
193
+ return "clone/worktree attachment mismatch: worktree.worktreeId differs from declaration.worktreeId";
194
+ }
195
+ const clone = validateCloneIdentity(record.clone);
196
+ if (!clone.ok)
197
+ return `invalid clone identity: ${clone.errors.join("; ")}`;
198
+ const worktree = validateWorktreeIdentity(record.worktree);
199
+ if (!worktree.ok)
200
+ return `invalid worktree identity: ${worktree.errors.join("; ")}`;
201
+ for (const field of ["createdAt", "activatedAt", "updatedAt"]) {
202
+ if (typeof record[field] !== "string" || record[field].length === 0) {
203
+ return `missing ${field}`;
204
+ }
205
+ }
206
+ return undefined;
207
+ };
208
+ const isRecord = (value) => {
209
+ if (typeof value !== "object" || value === null)
210
+ return false;
211
+ const record = value;
212
+ if (record.kind !== LIVE_DECLARATION_KIND ||
213
+ record.schemaVersion !== LIVE_DECLARATION_SCHEMA_VERSION ||
214
+ typeof record.declaration !== "object" ||
215
+ record.declaration === null ||
216
+ typeof record.clone !== "object" ||
217
+ record.clone === null ||
218
+ typeof record.worktree !== "object" ||
219
+ record.worktree === null ||
220
+ typeof record.declaration.declarationId !== "string") {
221
+ return false;
222
+ }
223
+ return validateRecord(record) === undefined;
224
+ };
225
+ /** Pre-realignment entries (nested shape, top-level hostScope/revision, no neutral DTO) are never read as records. */
226
+ const isLegacyRecord = (value) => {
227
+ if (typeof value !== "object" || value === null)
228
+ return false;
229
+ const record = value;
230
+ return ("hostScope" in record ||
231
+ ("declarationId" in record && !("declaration" in record)));
232
+ };
233
+ /** Durable declarations live under the injected h2a root, never a worktree. */
234
+ export function liveDeclarationsPath(root) {
235
+ return join(root, "live", "declarations.json");
236
+ }
237
+ const readStoreFile = (root) => {
238
+ const path = liveDeclarationsPath(root);
239
+ if (!existsSync(path)) {
240
+ return { kind: "h2a-live-declarations", version: 1, declarations: [] };
241
+ }
242
+ let parsed;
243
+ try {
244
+ parsed = JSON.parse(readFileSync(path, "utf8"));
245
+ }
246
+ catch (error) {
247
+ // Explicit failure: a corrupt declaration store must never be treated
248
+ // as an empty registry (LIVE §12: database loss must not erase live
249
+ // registrations; silent loss is worse).
250
+ throw new Error(`live declarations store unreadable at ${path}: ${error.message}`);
251
+ }
252
+ if (typeof parsed !== "object" ||
253
+ parsed === null ||
254
+ parsed.kind !== "h2a-live-declarations" ||
255
+ parsed.version !== 1 ||
256
+ !Array.isArray(parsed.declarations)) {
257
+ throw new Error(`live declarations store invalid at ${path}`);
258
+ }
259
+ const file = parsed;
260
+ for (const entry of file.declarations) {
261
+ if (isLegacyRecord(entry)) {
262
+ throw new Error(`live declarations store uses the pre-realignment L6a format at ${path}: ` +
263
+ `remove the file to start fresh (L6a is not consumed yet; no migration replay exists)`);
264
+ }
265
+ if (!isRecord(entry)) {
266
+ throw new Error(`live declarations store contains a malformed declaration at ${path}`);
267
+ }
268
+ }
269
+ return file;
270
+ };
271
+ /**
272
+ * A store instance is a thin read-through over the durable file: every
273
+ * operation re-reads, so two instances (or two processes) on the same root
274
+ * observe each other's committed writes.
275
+ */
276
+ export function createLiveDeclarationStore(root) {
277
+ const load = () => readStoreFile(root).declarations;
278
+ const persist = (lock, expected, declarations, declarationId, expectedRevision) => {
279
+ const generation = join(lockDirFor(root), lock.token);
280
+ const tmp = join(generation, `${randomUUID()}.tmp`);
281
+ const conflict = () => new LiveRevisionConflict(declarationId, expectedRevision, load().find((record) => record.declaration.declarationId === declarationId)
282
+ ?.declaration.declarationRevision);
283
+ const file = {
284
+ kind: "h2a-live-declarations",
285
+ version: 1,
286
+ declarations: [...declarations],
287
+ };
288
+ try {
289
+ if (!existsSync(generation))
290
+ throw conflict();
291
+ // The source belongs to this unique token and cannot survive takeover.
292
+ // Never mkdir here: a dispossessed holder must not recreate its source.
293
+ writeFileSync(tmp, JSON.stringify(file, null, 2) + "\n", { mode: 0o600, flag: "wx" });
294
+ const fresh = load();
295
+ if (!existsSync(generation) || JSON.stringify(fresh) !== JSON.stringify(expected)) {
296
+ throw conflict();
297
+ }
298
+ // Atomic rename is the commit point. A suspension AFTER the checks
299
+ // above is fenced by takeover removing this generation's source file.
300
+ renameSync(tmp, liveDeclarationsPath(root));
301
+ }
302
+ catch (error) {
303
+ // Cleanup can unlink the source before removing its directory.
304
+ if (error.code === "ENOENT" || !existsSync(generation)) {
305
+ throw conflict();
306
+ }
307
+ throw error;
308
+ }
309
+ finally {
310
+ rmSync(tmp, { force: true });
311
+ }
312
+ };
313
+ return {
314
+ list() {
315
+ return [...load()].sort((a, b) => a.declaration.declarationId < b.declaration.declarationId
316
+ ? -1
317
+ : a.declaration.declarationId > b.declaration.declarationId
318
+ ? 1
319
+ : 0);
320
+ },
321
+ get(declarationId) {
322
+ return load().find((record) => record.declaration.declarationId === declarationId);
323
+ },
324
+ findByPath(path) {
325
+ return load().find((record) => record.declaration.authorizedRoots.includes(path));
326
+ },
327
+ insert(record) {
328
+ const problem = validateRecord(record);
329
+ if (problem !== undefined) {
330
+ throw new Error(`invalid live declaration record: ${problem}`);
331
+ }
332
+ withStoreLock(root, (lock) => {
333
+ const declarations = load();
334
+ if (declarations.some((existing) => existing.declaration.declarationId === record.declaration.declarationId)) {
335
+ throw new Error(`live declaration already exists: ${record.declaration.declarationId}`);
336
+ }
337
+ persist(lock, declarations, [...declarations, record], record.declaration.declarationId, record.declaration.declarationRevision);
338
+ });
339
+ },
340
+ update(declarationId, expectedDeclarationRevision, mutate) {
341
+ return withStoreLock(root, (lock) => {
342
+ const declarations = load();
343
+ const index = declarations.findIndex((record) => record.declaration.declarationId === declarationId);
344
+ if (index === -1) {
345
+ throw new Error(`unknown live declaration: ${declarationId}`);
346
+ }
347
+ const current = declarations[index];
348
+ if (current.declaration.declarationRevision !== expectedDeclarationRevision) {
349
+ throw new LiveRevisionConflict(declarationId, expectedDeclarationRevision, current.declaration.declarationRevision);
350
+ }
351
+ const next = mutate(current);
352
+ if (next.declaration.declarationId !== declarationId) {
353
+ throw new Error(`update must not change declarationId: ${declarationId}`);
354
+ }
355
+ if (next.declaration.declarationRevision !== expectedDeclarationRevision + 1) {
356
+ throw new Error(`update must advance declarationRevision by exactly one: held ${expectedDeclarationRevision}`);
357
+ }
358
+ for (const field of [
359
+ "tenant",
360
+ "principal",
361
+ "hostFingerprint",
362
+ "cloneId",
363
+ "worktreeId",
364
+ "liveCapability",
365
+ ]) {
366
+ if (next.declaration[field] !== current.declaration[field]) {
367
+ throw new Error(`update must not change declaration identity field "${field}": ${declarationId}`);
368
+ }
369
+ }
370
+ // Binding renewal-or-preserved: a workspace/issuer/revision change
371
+ // renews the binding (complete triplet, first binding exactly
372
+ // INITIAL_BINDING_REVISION, then strictly greater); any other
373
+ // mutation preserves it.
374
+ const previous = current.declaration;
375
+ const candidate = next.declaration;
376
+ const workspaceChanged = candidate.serverWorkspaceId !== previous.serverWorkspaceId;
377
+ const issuerChanged = candidate.serverIssuer !== previous.serverIssuer;
378
+ const revisionTouched = candidate.bindingRevision !== previous.bindingRevision;
379
+ if (workspaceChanged || issuerChanged || revisionTouched) {
380
+ if (candidate.serverWorkspaceId === undefined ||
381
+ candidate.serverIssuer === undefined ||
382
+ candidate.bindingRevision === undefined) {
383
+ throw new Error(`update must carry the complete serverWorkspaceId + serverIssuer + bindingRevision triplet when the binding changes: ${declarationId}`);
384
+ }
385
+ if (previous.bindingRevision === undefined) {
386
+ if (candidate.bindingRevision !== INITIAL_BINDING_REVISION) {
387
+ throw new Error(`update must carry bindingRevision ${INITIAL_BINDING_REVISION} for the first product binding: ${declarationId}`);
388
+ }
389
+ }
390
+ else if (candidate.bindingRevision <= previous.bindingRevision) {
391
+ throw new Error(`update must carry a strictly increasing bindingRevision: ${declarationId}`);
392
+ }
393
+ }
394
+ const problem = validateRecord(next);
395
+ if (problem !== undefined) {
396
+ throw new Error(`invalid updated live declaration record: ${problem}`);
397
+ }
398
+ // Atomic compare-and-write under the same lock: re-read the durable
399
+ // file and refuse when the declaration moved (a concurrent writer
400
+ // committed inside mutate()) or vanished (an interleaved removal).
401
+ // The merge applies onto the FRESH list, so unrelated concurrent
402
+ // records are never dropped; a stale write never clobbers and a
403
+ // removed declaration never resurrects.
404
+ const fresh = load();
405
+ const refetch = fresh.find((record) => record.declaration.declarationId === declarationId);
406
+ if (refetch === undefined) {
407
+ throw new LiveRevisionConflict(declarationId, expectedDeclarationRevision, undefined);
408
+ }
409
+ if (refetch.declaration.declarationRevision !== expectedDeclarationRevision) {
410
+ throw new LiveRevisionConflict(declarationId, expectedDeclarationRevision, refetch.declaration.declarationRevision);
411
+ }
412
+ persist(lock, fresh, fresh.map((record) => record.declaration.declarationId === declarationId ? next : record), declarationId, expectedDeclarationRevision);
413
+ return next;
414
+ });
415
+ },
416
+ remove(declarationId) {
417
+ return withStoreLock(root, (lock) => {
418
+ const declarations = load();
419
+ const remaining = declarations.filter((record) => record.declaration.declarationId !== declarationId);
420
+ if (remaining.length === declarations.length)
421
+ return false;
422
+ persist(lock, declarations, remaining, declarationId, declarations.find((record) => record.declaration.declarationId === declarationId)
423
+ .declaration.declarationRevision);
424
+ return true;
425
+ });
426
+ },
427
+ };
428
+ }
429
+ //# sourceMappingURL=live-declarations.js.map