@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.
- package/dist/attach.d.ts +2 -0
- package/dist/attach.d.ts.map +1 -1
- package/dist/attach.js +73 -6
- package/dist/attach.js.map +1 -1
- package/dist/auth-login.d.ts +24 -1
- package/dist/auth-login.d.ts.map +1 -1
- package/dist/auth-login.js +132 -4
- package/dist/auth-login.js.map +1 -1
- package/dist/central-mcp.d.ts +2 -0
- package/dist/central-mcp.d.ts.map +1 -1
- package/dist/central-mcp.js +90 -3
- package/dist/central-mcp.js.map +1 -1
- package/dist/cli-help-groups.js +1 -1
- package/dist/cli-help-groups.js.map +1 -1
- package/dist/crash-log.d.ts +112 -0
- package/dist/crash-log.d.ts.map +1 -0
- package/dist/crash-log.js +560 -0
- package/dist/crash-log.js.map +1 -0
- package/dist/gateway-host/live-declarations.d.ts +58 -0
- package/dist/gateway-host/live-declarations.d.ts.map +1 -0
- package/dist/gateway-host/live-declarations.js +423 -0
- package/dist/gateway-host/live-declarations.js.map +1 -0
- package/dist/gateway-host/live-registration.d.ts +162 -0
- package/dist/gateway-host/live-registration.d.ts.map +1 -0
- package/dist/gateway-host/live-registration.js +455 -0
- package/dist/gateway-host/live-registration.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +79 -1
- package/dist/index.js.map +1 -1
- package/dist/native-host.d.ts.map +1 -1
- package/dist/native-host.js +27 -5
- package/dist/native-host.js.map +1 -1
- package/dist/native-terminal/journal.d.ts +5 -0
- package/dist/native-terminal/journal.d.ts.map +1 -1
- package/dist/native-terminal/journal.js +28 -16
- package/dist/native-terminal/journal.js.map +1 -1
- package/dist/native-terminal/op.d.ts +13 -0
- package/dist/native-terminal/op.d.ts.map +1 -1
- package/dist/native-terminal/op.js +64 -5
- package/dist/native-terminal/op.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +79 -16
- package/dist/run.js.map +1 -1
- package/dist/terminal-reset.d.ts +104 -0
- package/dist/terminal-reset.d.ts.map +1 -0
- package/dist/terminal-reset.js +229 -0
- package/dist/terminal-reset.js.map +1 -0
- package/dist/tmux.d.ts +23 -3
- package/dist/tmux.d.ts.map +1 -1
- package/dist/tmux.js +47 -16
- package/dist/tmux.js.map +1 -1
- package/dist/vendor/engram-live/contracts.d.ts +208 -0
- package/dist/vendor/engram-live/contracts.js +558 -0
- package/dist/vendor/engram-live/integration.d.ts +73 -0
- package/dist/vendor/engram-live/integration.js +194 -0
- package/dist/vendor/engram-live/registration.d.ts +138 -0
- package/dist/vendor/engram-live/registration.js +360 -0
- 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.
|
|
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
|
}
|