@sentropic/h2a-runtime 0.98.6 → 0.98.7-rc.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.
Files changed (58) hide show
  1. package/dist/attach.d.ts +2 -0
  2. package/dist/attach.d.ts.map +1 -1
  3. package/dist/attach.js +73 -6
  4. package/dist/attach.js.map +1 -1
  5. package/dist/auth-login.d.ts +24 -1
  6. package/dist/auth-login.d.ts.map +1 -1
  7. package/dist/auth-login.js +132 -4
  8. package/dist/auth-login.js.map +1 -1
  9. package/dist/central-mcp.d.ts +2 -0
  10. package/dist/central-mcp.d.ts.map +1 -1
  11. package/dist/central-mcp.js +90 -3
  12. package/dist/central-mcp.js.map +1 -1
  13. package/dist/cli-help-groups.js +1 -1
  14. package/dist/cli-help-groups.js.map +1 -1
  15. package/dist/crash-log.d.ts +112 -0
  16. package/dist/crash-log.d.ts.map +1 -0
  17. package/dist/crash-log.js +560 -0
  18. package/dist/crash-log.js.map +1 -0
  19. package/dist/gateway-host/live-declarations.d.ts +58 -0
  20. package/dist/gateway-host/live-declarations.d.ts.map +1 -0
  21. package/dist/gateway-host/live-declarations.js +423 -0
  22. package/dist/gateway-host/live-declarations.js.map +1 -0
  23. package/dist/gateway-host/live-registration.d.ts +162 -0
  24. package/dist/gateway-host/live-registration.d.ts.map +1 -0
  25. package/dist/gateway-host/live-registration.js +455 -0
  26. package/dist/gateway-host/live-registration.js.map +1 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +79 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/native-host.d.ts.map +1 -1
  31. package/dist/native-host.js +27 -5
  32. package/dist/native-host.js.map +1 -1
  33. package/dist/native-terminal/journal.d.ts +5 -0
  34. package/dist/native-terminal/journal.d.ts.map +1 -1
  35. package/dist/native-terminal/journal.js +28 -16
  36. package/dist/native-terminal/journal.js.map +1 -1
  37. package/dist/native-terminal/op.d.ts +13 -0
  38. package/dist/native-terminal/op.d.ts.map +1 -1
  39. package/dist/native-terminal/op.js +64 -5
  40. package/dist/native-terminal/op.js.map +1 -1
  41. package/dist/run.d.ts.map +1 -1
  42. package/dist/run.js +79 -16
  43. package/dist/run.js.map +1 -1
  44. package/dist/terminal-reset.d.ts +104 -0
  45. package/dist/terminal-reset.d.ts.map +1 -0
  46. package/dist/terminal-reset.js +229 -0
  47. package/dist/terminal-reset.js.map +1 -0
  48. package/dist/tmux.d.ts +23 -3
  49. package/dist/tmux.d.ts.map +1 -1
  50. package/dist/tmux.js +47 -16
  51. package/dist/tmux.js.map +1 -1
  52. package/dist/vendor/engram-live/contracts.d.ts +208 -0
  53. package/dist/vendor/engram-live/contracts.js +558 -0
  54. package/dist/vendor/engram-live/integration.d.ts +73 -0
  55. package/dist/vendor/engram-live/integration.js +194 -0
  56. package/dist/vendor/engram-live/registration.d.ts +138 -0
  57. package/dist/vendor/engram-live/registration.js +360 -0
  58. package/package.json +2 -2
@@ -0,0 +1,360 @@
1
+ // Vendored from @sentropic/engram-live 0.1.0 dist/registration.js — same
2
+ // provenance as the sibling contracts.js header (graphify@70ebdfee, pinned
3
+ // tarball sha512 in the GUARD test). Portable: node:crypto plus the sibling
4
+ // contracts.js only. Do not hand-edit.
5
+ // @sentropic/engram-live/registration — pure neutral registration helpers
6
+ // (spec/SPEC_LIVE_GIT_INDEXING.md §§2–3, 10, 12; plan/09 L6a; owner Q4 A).
7
+ //
8
+ // Pure functions over ./contracts.js DTOs: declaration creation, revision,
9
+ // revocation, the ONE canonical clone/worktree/declaration identity
10
+ // derivation, clone equality and lifecycle classification. No host state, no
11
+ // storage, no network, no projection provider: every function is
12
+ // deterministic over its arguments, never mutates its inputs, and keeps no
13
+ // module-level mutable state. Durable declarations, replay, cancellation
14
+ // and purge execution belong to the h2a (H) and cluster-mesh/sentropic (S)
15
+ // hosts consuming the contract documented in ./contracts.js.
16
+ //
17
+ // Both seats mint their opaque ids through these functions (canonicalIdKey
18
+ // in ./contracts.js + the sha256 minting below): identical identity
19
+ // components give byte-identical cloneId/worktreeId/declarationId on every
20
+ // side, so identity equality is comparison by canonical identifier. h2a
21
+ // identifiers persisted before this freeze were derived from
22
+ // newline-joined components; re-derive and rewrite them locally
23
+ // (see the MIGRATION note in ./contracts.js).
24
+ import { createHash } from "node:crypto";
25
+ import { ID_PREFIXES, IDENTITY_UNCERTAINTY_STATES, canonicalIdKey, isIdentityUncertaintyState, isNonActivatingLifecycleKind, isRevocationReason, normalizeCommonDirRealpath, validateCloneIdentity, validateDeclarationRevocation, validateWorkspaceDeclaration, validateWorktreeIdentity, INITIAL_BINDING_REVISION, INITIAL_DECLARATION_REVISION, } from "./contracts.js";
26
+ const LIVE_DECLARATION_PATCH_KEYS = [
27
+ "authorizedRoots",
28
+ "policyVersion",
29
+ "serverWorkspaceId",
30
+ "serverIssuer",
31
+ "bindingRevision",
32
+ ];
33
+ function isRecord(value) {
34
+ return typeof value === "object" && value !== null && !Array.isArray(value);
35
+ }
36
+ function isNonEmptyString(value) {
37
+ return typeof value === "string" && value.trim().length > 0;
38
+ }
39
+ function isCloneIdReference(value) {
40
+ return isNonEmptyString(value) && value.startsWith(ID_PREFIXES.clone);
41
+ }
42
+ function isWorktreeIdReference(value) {
43
+ return isNonEmptyString(value) && value.startsWith(ID_PREFIXES.worktree);
44
+ }
45
+ function sha256Hex(canonical) {
46
+ return createHash("sha256").update(canonical, "utf8").digest("hex");
47
+ }
48
+ function checkUncertainty(uncertainty) {
49
+ const states = [...(uncertainty ?? [])];
50
+ if (states.some((state) => !isIdentityUncertaintyState(state))) {
51
+ throw new Error(`identity uncertainty states must be one of ${IDENTITY_UNCERTAINTY_STATES.join(", ")}`);
52
+ }
53
+ return states;
54
+ }
55
+ /**
56
+ * Create the initial live declaration for one workspace. Requires the
57
+ * explicit "live" capability and the full identity triangle
58
+ * (declarationId/cloneId/worktreeId, minted by the canonical derivation
59
+ * below): any other opt-in value (bind, upload, session kinds) is refused,
60
+ * so no lifecycle event can implicitly activate live. The declaration
61
+ * revision starts at the frozen INITIAL_DECLARATION_REVISION.
62
+ */
63
+ export function createLiveDeclaration(input) {
64
+ if (!isRecord(input)) {
65
+ throw new Error("createLiveDeclaration requires a declaration input object");
66
+ }
67
+ const declaration = {
68
+ tenant: input.tenant,
69
+ principal: input.principal,
70
+ ...(input.hostFingerprint !== undefined ? { hostFingerprint: input.hostFingerprint } : {}),
71
+ identityUncertainty: [...input.identityUncertainty],
72
+ declarationId: input.declarationId,
73
+ cloneId: input.cloneId,
74
+ worktreeId: input.worktreeId,
75
+ authorizedRoots: [...input.authorizedRoots],
76
+ liveCapability: input.liveCapability,
77
+ policyVersion: input.policyVersion,
78
+ declarationRevision: INITIAL_DECLARATION_REVISION,
79
+ ...(input.serverWorkspaceId !== undefined ? { serverWorkspaceId: input.serverWorkspaceId } : {}),
80
+ ...(input.serverIssuer !== undefined ? { serverIssuer: input.serverIssuer } : {}),
81
+ ...(input.bindingRevision !== undefined ? { bindingRevision: input.bindingRevision } : {}),
82
+ };
83
+ if (input.bindingRevision !== undefined && input.bindingRevision !== INITIAL_BINDING_REVISION) {
84
+ throw new Error(`createLiveDeclaration requires the first product binding to carry bindingRevision ${INITIAL_BINDING_REVISION} (FROZEN L6a convention; rebinds and revalidations grow strictly from there)`);
85
+ }
86
+ const checked = validateWorkspaceDeclaration(declaration);
87
+ if (!checked.ok) {
88
+ throw new Error(`invalid live workspace declaration: ${checked.errors.join("; ")}`);
89
+ }
90
+ return declaration;
91
+ }
92
+ /**
93
+ * Revise a declaration as a pure revision increment. The revision grows by
94
+ * exactly one; tenant, principal, hostFingerprint, the identity triangle
95
+ * and the live capability are immutable. The product binding is the
96
+ * complete (serverWorkspaceId, serverIssuer, bindingRevision) triplet.
97
+ * A revision outside the binding (roots, policy version) PRESERVES the
98
+ * binding: no renewal is imposed. Only a change of serverWorkspaceId or
99
+ * serverIssuer, or an explicit bindingRevision revalidation, renews the
100
+ * binding — and then strictly increasingly: the first binding carries
101
+ * exactly INITIAL_BINDING_REVISION, and every rebind/revalidation carries
102
+ * a strictly greater bindingRevision; binding revisions never go
103
+ * backwards. The host must additionally refuse to redirect in-flight
104
+ * collection/publication when a rebind lands.
105
+ */
106
+ export function reviseLiveDeclaration(previous, patch) {
107
+ const previousCheck = validateWorkspaceDeclaration(previous);
108
+ if (!previousCheck.ok) {
109
+ throw new Error(`invalid previous declaration: ${previousCheck.errors.join("; ")}`);
110
+ }
111
+ if (!isRecord(patch)) {
112
+ throw new Error("reviseLiveDeclaration requires a patch object");
113
+ }
114
+ const patchRecord = patch;
115
+ for (const key of Object.keys(patchRecord)) {
116
+ if (!LIVE_DECLARATION_PATCH_KEYS.includes(key)) {
117
+ throw new Error(`reviseLiveDeclaration refuses patch key "${key}": identity fields are immutable across revisions`);
118
+ }
119
+ }
120
+ const workspaceChanged = patch.serverWorkspaceId !== undefined &&
121
+ patch.serverWorkspaceId !== previous.serverWorkspaceId;
122
+ const issuerChanged = patch.serverIssuer !== undefined &&
123
+ patch.serverIssuer !== previous.serverIssuer;
124
+ const bindingTouched = patch.bindingRevision !== undefined || workspaceChanged || issuerChanged;
125
+ // A revision outside the product binding preserves it; only a workspace or
126
+ // issuer change, or an explicit revalidation, renews it — strictly
127
+ // increasingly, starting the first binding at INITIAL_BINDING_REVISION.
128
+ let nextBindingRevision = previous.bindingRevision;
129
+ if (bindingTouched) {
130
+ if (patch.bindingRevision === undefined) {
131
+ throw new Error("reviseLiveDeclaration requires a fresh strictly increasing bindingRevision when the server workspace or issuer changes (revalidation)");
132
+ }
133
+ if (previous.bindingRevision === undefined) {
134
+ if (patch.bindingRevision !== INITIAL_BINDING_REVISION) {
135
+ throw new Error(`reviseLiveDeclaration requires the first product binding to carry bindingRevision ${INITIAL_BINDING_REVISION} (FROZEN L6a convention)`);
136
+ }
137
+ }
138
+ else if (patch.bindingRevision <= previous.bindingRevision) {
139
+ throw new Error("reviseLiveDeclaration requires a strictly increasing bindingRevision (binding revisions are monotonic)");
140
+ }
141
+ nextBindingRevision = patch.bindingRevision;
142
+ }
143
+ const next = {
144
+ ...previous,
145
+ authorizedRoots: patch.authorizedRoots !== undefined ? [...patch.authorizedRoots] : [...previous.authorizedRoots],
146
+ policyVersion: patch.policyVersion !== undefined ? patch.policyVersion : previous.policyVersion,
147
+ serverWorkspaceId: patch.serverWorkspaceId !== undefined
148
+ ? patch.serverWorkspaceId
149
+ : previous.serverWorkspaceId,
150
+ serverIssuer: patch.serverIssuer !== undefined ? patch.serverIssuer : previous.serverIssuer,
151
+ bindingRevision: nextBindingRevision,
152
+ declarationRevision: previous.declarationRevision + 1,
153
+ };
154
+ const checked = validateWorkspaceDeclaration(next);
155
+ if (!checked.ok) {
156
+ throw new Error(`invalid revised declaration: ${checked.errors.join("; ")}`);
157
+ }
158
+ return next;
159
+ }
160
+ /**
161
+ * Revoke exactly one declaration as a pure private-purge request. The
162
+ * returned DTO names the single target by its opaque declarationId and
163
+ * carries the declaration's cloneId/worktreeId attachment and revision, so
164
+ * the recipient can never confuse two declarations of one tenant that
165
+ * happen to share public fingerprints or revisions. The host stops work,
166
+ * rejects delayed jobs and purges private snapshots, derived indexes and
167
+ * decryptability, while preserving authorized committed history.
168
+ * Revocation deletes no committed facts.
169
+ */
170
+ export function revokeLiveDeclaration(declaration, reason) {
171
+ const checked = validateWorkspaceDeclaration(declaration);
172
+ if (!checked.ok) {
173
+ throw new Error(`invalid declaration: ${checked.errors.join("; ")}`);
174
+ }
175
+ if (!isRevocationReason(reason)) {
176
+ throw new Error(`invalid revocation reason: ${String(reason)}`);
177
+ }
178
+ const revocation = {
179
+ declarationId: declaration.declarationId,
180
+ tenant: declaration.tenant,
181
+ cloneId: declaration.cloneId,
182
+ worktreeId: declaration.worktreeId,
183
+ declarationRevision: declaration.declarationRevision,
184
+ reason,
185
+ requestPrivatePurge: {
186
+ purgePrivateSnapshots: true,
187
+ purgeDerivedIndexes: true,
188
+ revokeDecryptability: true,
189
+ preserveCommittedHistory: true,
190
+ },
191
+ };
192
+ const revocationCheck = validateDeclarationRevocation(revocation);
193
+ if (!revocationCheck.ok) {
194
+ throw new Error(`invalid revocation: ${revocationCheck.errors.join("; ")}`);
195
+ }
196
+ return revocation;
197
+ }
198
+ /**
199
+ * Derive the clone identity for one common-dir — the ONE canonical clone
200
+ * derivation. The key components (hostScope, normalized common-dir
201
+ * realpath, common-dir project fingerprint) serialize through
202
+ * canonicalIdKey (unambiguous, NUL-refusing) and hash to the opaque
203
+ * cloneId: equal fingerprints under different realpaths derive distinct
204
+ * clones, while linked worktrees sharing one common-dir derive the same
205
+ * clone. The fingerprint may be absent only under explicit
206
+ * "missing_history" uncertainty; the hostScope + realpath components keep
207
+ * clones distinct even then (LIVE §12). The realpath stays host-local;
208
+ * only cloneId is shared. Deterministic and free of I/O.
209
+ */
210
+ export function deriveCloneIdentity(input) {
211
+ if (!isRecord(input)) {
212
+ throw new Error("deriveCloneIdentity requires a clone input object");
213
+ }
214
+ if (!isNonEmptyString(input.hostScope)) {
215
+ throw new Error("deriveCloneIdentity requires a non-empty hostScope");
216
+ }
217
+ if (!isNonEmptyString(input.commonDirRealpath)) {
218
+ throw new Error("deriveCloneIdentity requires a non-empty commonDirRealpath");
219
+ }
220
+ const uncertainty = checkUncertainty(input.identityUncertainty);
221
+ if (input.commonDirProjectFingerprint !== undefined &&
222
+ !isNonEmptyString(input.commonDirProjectFingerprint)) {
223
+ throw new Error("deriveCloneIdentity requires a non-empty commonDirProjectFingerprint when present");
224
+ }
225
+ if (input.commonDirProjectFingerprint === undefined &&
226
+ !uncertainty.includes("missing_history")) {
227
+ throw new Error('deriveCloneIdentity omits commonDirProjectFingerprint only under explicit "missing_history" uncertainty');
228
+ }
229
+ if (input.oidAlgorithm !== "sha1" && input.oidAlgorithm !== "sha256") {
230
+ throw new Error('deriveCloneIdentity requires oidAlgorithm "sha1" or "sha256"');
231
+ }
232
+ const normalizedRealpath = normalizeCommonDirRealpath(input.commonDirRealpath);
233
+ // canonicalIdKey refuses NUL inside any component.
234
+ const canonical = canonicalIdKey("clone", [
235
+ input.hostScope,
236
+ normalizedRealpath,
237
+ input.commonDirProjectFingerprint ?? "",
238
+ ]);
239
+ const identity = {
240
+ hostScope: input.hostScope,
241
+ commonDirRealpath: normalizedRealpath,
242
+ ...(input.commonDirProjectFingerprint !== undefined
243
+ ? { commonDirProjectFingerprint: input.commonDirProjectFingerprint }
244
+ : {}),
245
+ cloneId: `${ID_PREFIXES.clone}${sha256Hex(canonical)}`,
246
+ oidAlgorithm: input.oidAlgorithm,
247
+ identityUncertainty: [...uncertainty],
248
+ };
249
+ const checked = validateCloneIdentity(identity);
250
+ if (!checked.ok) {
251
+ throw new Error(`invalid derived clone identity: ${checked.errors.join("; ")}`);
252
+ }
253
+ return identity;
254
+ }
255
+ /**
256
+ * Derive the identity of one worktree under an already derived clone —
257
+ * the ONE canonical worktree derivation. Worktrees of one clone share
258
+ * cloneId and keep an isolated host-scoped worktreeId plus per-worktree
259
+ * git-dir, fingerprint and path. The worktree fingerprint may be absent
260
+ * only under explicit "missing_history" uncertainty (LIVE §12).
261
+ */
262
+ export function deriveWorktreeIdentity(input) {
263
+ if (!isRecord(input)) {
264
+ throw new Error("deriveWorktreeIdentity requires a worktree input object");
265
+ }
266
+ if (!isRecord(input.clone) || !isCloneIdReference(input.clone.cloneId)) {
267
+ throw new Error("deriveWorktreeIdentity requires a derived clone carrying a canonical cloneId");
268
+ }
269
+ if (!isNonEmptyString(input.hostScope)) {
270
+ throw new Error("deriveWorktreeIdentity requires a non-empty hostScope");
271
+ }
272
+ if (!isNonEmptyString(input.worktreePath)) {
273
+ throw new Error("deriveWorktreeIdentity requires a non-empty worktreePath");
274
+ }
275
+ if (!isNonEmptyString(input.gitDir)) {
276
+ throw new Error("deriveWorktreeIdentity requires a non-empty gitDir");
277
+ }
278
+ const uncertainty = checkUncertainty(input.identityUncertainty);
279
+ if (input.worktreeFingerprint !== undefined &&
280
+ !isNonEmptyString(input.worktreeFingerprint)) {
281
+ throw new Error("deriveWorktreeIdentity requires a non-empty worktreeFingerprint when present");
282
+ }
283
+ if (input.worktreeFingerprint === undefined && !uncertainty.includes("missing_history")) {
284
+ throw new Error('deriveWorktreeIdentity omits worktreeFingerprint only under explicit "missing_history" uncertainty');
285
+ }
286
+ const canonical = canonicalIdKey("worktree", [input.hostScope, input.gitDir]);
287
+ const identity = {
288
+ cloneId: input.clone.cloneId,
289
+ worktreeId: `${ID_PREFIXES.worktree}${sha256Hex(canonical)}`,
290
+ worktreePath: input.worktreePath,
291
+ gitDir: input.gitDir,
292
+ ...(input.worktreeFingerprint !== undefined
293
+ ? { worktreeFingerprint: input.worktreeFingerprint }
294
+ : {}),
295
+ identityUncertainty: [...uncertainty],
296
+ };
297
+ const checked = validateWorktreeIdentity(identity);
298
+ if (!checked.ok) {
299
+ throw new Error(`invalid derived worktree identity: ${checked.errors.join("; ")}`);
300
+ }
301
+ return identity;
302
+ }
303
+ /**
304
+ * Derive the identity of one declaration — the ONE canonical declaration
305
+ * derivation. One declaration per activated worktree: the declarationId
306
+ * binds the host scope, the clone and the worktree, so two worktrees of
307
+ * one clone (and two clones of one project) derive distinct declarationIds
308
+ * even without fingerprints. Stable across every revision of the same
309
+ * declaration. Deterministic and free of I/O.
310
+ */
311
+ export function deriveDeclarationIdentity(input) {
312
+ if (!isRecord(input)) {
313
+ throw new Error("deriveDeclarationIdentity requires a declaration identity input object");
314
+ }
315
+ if (!isRecord(input.clone) || !isCloneIdReference(input.clone.cloneId)) {
316
+ throw new Error("deriveDeclarationIdentity requires a derived clone carrying a canonical cloneId");
317
+ }
318
+ if (!isRecord(input.worktree) || !isWorktreeIdReference(input.worktree.worktreeId)) {
319
+ throw new Error("deriveDeclarationIdentity requires a derived worktree carrying a canonical worktreeId");
320
+ }
321
+ if (!isNonEmptyString(input.hostScope)) {
322
+ throw new Error("deriveDeclarationIdentity requires a non-empty hostScope");
323
+ }
324
+ const canonical = canonicalIdKey("declaration", [
325
+ input.hostScope,
326
+ input.clone.cloneId,
327
+ input.worktree.worktreeId,
328
+ ]);
329
+ const identity = {
330
+ declarationId: `${ID_PREFIXES.declaration}${sha256Hex(canonical)}`,
331
+ cloneId: input.clone.cloneId,
332
+ worktreeId: input.worktree.worktreeId,
333
+ };
334
+ return identity;
335
+ }
336
+ /**
337
+ * Decide whether two clone references denote the same clone, by canonical
338
+ * identifier only: the cloneId minted by the shared canonical derivation is
339
+ * the clone identity, so the comparison is an equivalence (transitive and
340
+ * consistent between full DTOs and opaque references). Throws when either
341
+ * reference lacks a cloneId — equality is never guessed from components.
342
+ */
343
+ export function isSameClone(a, b) {
344
+ if (!isNonEmptyString(a.cloneId) || !isNonEmptyString(b.cloneId)) {
345
+ throw new Error("isSameClone compares canonical clone ids: both references must carry a non-empty cloneId");
346
+ }
347
+ return a.cloneId === b.cloneId;
348
+ }
349
+ /**
350
+ * Classify a host lifecycle event against live declarations. Bind, upload,
351
+ * code-workspace creation and session open/end never activate or remove a
352
+ * declaration: the only outcome is "no-activation-change". Unknown kinds
353
+ * are refused.
354
+ */
355
+ export function classifyLifecycleEvent(kind) {
356
+ if (!isNonActivatingLifecycleKind(kind)) {
357
+ throw new Error(`unknown lifecycle kind: ${String(kind)}`);
358
+ }
359
+ return "no-activation-change";
360
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentropic/h2a-runtime",
3
- "version": "0.98.6",
3
+ "version": "0.98.7-rc.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -54,6 +54,6 @@
54
54
  "main": "./dist/index.js",
55
55
  "types": "./dist/index.d.ts",
56
56
  "scripts": {
57
- "build": "node ../../scripts/clean-workspace-dist.mjs && tsc -b --force"
57
+ "build": "node ../../scripts/clean-workspace-dist.mjs && tsc -b --force && node ../../scripts/copy-vendored-engram-live.mjs"
58
58
  }
59
59
  }