@sentropic/h2a-runtime 0.98.6 → 0.98.7-rc.2
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/agent-launch-args.d.ts.map +1 -1
- package/dist/agent-launch-args.js +5 -5
- package/dist/agent-launch-args.js.map +1 -1
- package/dist/agy-claude.d.ts +2 -0
- package/dist/agy-claude.d.ts.map +1 -0
- package/dist/agy-claude.js +3 -0
- package/dist/agy-claude.js.map +1 -0
- package/dist/agy-headless.d.ts +11 -0
- package/dist/agy-headless.d.ts.map +1 -0
- package/dist/agy-headless.js +104 -0
- package/dist/agy-headless.js.map +1 -0
- 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/claude-code-cli-isolation.d.ts +53 -0
- package/dist/claude-code-cli-isolation.d.ts.map +1 -0
- package/dist/claude-code-cli-isolation.js +277 -0
- package/dist/claude-code-cli-isolation.js.map +1 -0
- package/dist/claude-code-cli-lifecycle.d.ts +80 -0
- package/dist/claude-code-cli-lifecycle.d.ts.map +1 -0
- package/dist/claude-code-cli-lifecycle.js +402 -0
- package/dist/claude-code-cli-lifecycle.js.map +1 -0
- package/dist/claude-code-cli-runner.d.ts +89 -0
- package/dist/claude-code-cli-runner.d.ts.map +1 -0
- package/dist/claude-code-cli-runner.js +659 -0
- package/dist/claude-code-cli-runner.js.map +1 -0
- package/dist/claude-seat-input.d.ts +47 -0
- package/dist/claude-seat-input.d.ts.map +1 -0
- package/dist/claude-seat-input.js +272 -0
- package/dist/claude-seat-input.js.map +1 -0
- package/dist/claude-seat-mesh-contract.d.ts +88 -0
- package/dist/claude-seat-mesh-contract.d.ts.map +1 -0
- package/dist/claude-seat-mesh-contract.js +13 -0
- package/dist/claude-seat-mesh-contract.js.map +1 -0
- 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/delegate.d.ts +10 -0
- package/dist/delegate.d.ts.map +1 -1
- package/dist/delegate.js +29 -0
- package/dist/delegate.js.map +1 -1
- package/dist/gateway-host/claude-seat-client.d.ts +32 -0
- package/dist/gateway-host/claude-seat-client.d.ts.map +1 -0
- package/dist/gateway-host/claude-seat-client.js +38 -0
- package/dist/gateway-host/claude-seat-client.js.map +1 -0
- package/dist/gateway-host/host.d.ts +6 -0
- package/dist/gateway-host/host.d.ts.map +1 -1
- package/dist/gateway-host/host.js +24 -3
- package/dist/gateway-host/host.js.map +1 -1
- 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 +429 -0
- package/dist/gateway-host/live-declarations.js.map +1 -0
- package/dist/gateway-host/live-github.d.ts +11 -0
- package/dist/gateway-host/live-github.d.ts.map +1 -0
- package/dist/gateway-host/live-github.js +374 -0
- package/dist/gateway-host/live-github.js.map +1 -0
- package/dist/gateway-host/live-object-store.d.ts +35 -0
- package/dist/gateway-host/live-object-store.d.ts.map +1 -0
- package/dist/gateway-host/live-object-store.js +447 -0
- package/dist/gateway-host/live-object-store.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 +290 -23
- package/dist/index.js.map +1 -1
- package/dist/llm-mesh-accounts.d.ts +70 -4
- package/dist/llm-mesh-accounts.d.ts.map +1 -1
- package/dist/llm-mesh-accounts.js +296 -9
- package/dist/llm-mesh-accounts.js.map +1 -1
- package/dist/native-host.d.ts +1 -1
- package/dist/native-host.d.ts.map +1 -1
- package/dist/native-host.js +30 -17
- package/dist/native-host.js.map +1 -1
- package/dist/native-terminal/host.d.ts +6 -1
- package/dist/native-terminal/host.d.ts.map +1 -1
- package/dist/native-terminal/host.js +6 -25
- package/dist/native-terminal/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/proc-identity.d.ts +31 -0
- package/dist/proc-identity.d.ts.map +1 -1
- package/dist/proc-identity.js +54 -0
- package/dist/proc-identity.js.map +1 -1
- package/dist/prompt-delivery.d.ts +3 -0
- package/dist/prompt-delivery.d.ts.map +1 -1
- package/dist/prompt-delivery.js +8 -3
- package/dist/prompt-delivery.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 +26 -4
- package/dist/tmux.d.ts.map +1 -1
- package/dist/tmux.js +60 -25
- package/dist/tmux.js.map +1 -1
- package/dist/vendor/engram-live/contracts.d.ts +825 -0
- package/dist/vendor/engram-live/contracts.js +1734 -0
- package/dist/vendor/engram-live/integration.d.ts +113 -0
- package/dist/vendor/engram-live/integration.js +254 -0
- package/dist/vendor/engram-live/pr-sync.d.ts +144 -0
- package/dist/vendor/engram-live/pr-sync.js +411 -0
- package/dist/vendor/engram-live/registration.d.ts +138 -0
- package/dist/vendor/engram-live/registration.js +358 -0
- package/dist/vendor/engram-live/retention.d.ts +56 -0
- package/dist/vendor/engram-live/retention.js +179 -0
- package/package.json +2 -2
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
// Vendored from rhanka/engram@545a57b8911ab108e84fa1778e0f268fe11bf1de: engram-live/registration.ts.
|
|
2
|
+
// Compiled with TypeScript 5.9.3 (ES2022/NodeNext, declarations); do not hand-edit.
|
|
3
|
+
// @sentropic/engram-live/registration — pure neutral registration helpers
|
|
4
|
+
// (spec/SPEC_LIVE_GIT_INDEXING.md §§2–3, 10, 12; plan/09 L6a; owner Q4 A).
|
|
5
|
+
//
|
|
6
|
+
// Pure functions over ./contracts.js DTOs: declaration creation, revision,
|
|
7
|
+
// revocation, the ONE canonical clone/worktree/declaration identity
|
|
8
|
+
// derivation, clone equality and lifecycle classification. No host state, no
|
|
9
|
+
// storage, no network, no projection provider: every function is
|
|
10
|
+
// deterministic over its arguments, never mutates its inputs, and keeps no
|
|
11
|
+
// module-level mutable state. Durable declarations, replay, cancellation
|
|
12
|
+
// and purge execution belong to the h2a (H) and cluster-mesh/sentropic (S)
|
|
13
|
+
// hosts consuming the contract documented in ./contracts.js.
|
|
14
|
+
//
|
|
15
|
+
// Both seats mint their opaque ids through these functions (canonicalIdKey
|
|
16
|
+
// in ./contracts.js + the sha256 minting below): identical identity
|
|
17
|
+
// components give byte-identical cloneId/worktreeId/declarationId on every
|
|
18
|
+
// side, so identity equality is comparison by canonical identifier. h2a
|
|
19
|
+
// identifiers persisted before this freeze were derived from
|
|
20
|
+
// newline-joined components; re-derive and rewrite them locally
|
|
21
|
+
// (see the MIGRATION note in ./contracts.js).
|
|
22
|
+
import { createHash } from "node:crypto";
|
|
23
|
+
import { ID_PREFIXES, IDENTITY_UNCERTAINTY_STATES, canonicalIdKey, isIdentityUncertaintyState, isNonActivatingLifecycleKind, isRevocationReason, normalizeCommonDirRealpath, validateCloneIdentity, validateDeclarationRevocation, validateWorkspaceDeclaration, validateWorktreeIdentity, INITIAL_BINDING_REVISION, INITIAL_DECLARATION_REVISION, } from "./contracts.js";
|
|
24
|
+
const LIVE_DECLARATION_PATCH_KEYS = [
|
|
25
|
+
"authorizedRoots",
|
|
26
|
+
"policyVersion",
|
|
27
|
+
"serverWorkspaceId",
|
|
28
|
+
"serverIssuer",
|
|
29
|
+
"bindingRevision",
|
|
30
|
+
];
|
|
31
|
+
function isRecord(value) {
|
|
32
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33
|
+
}
|
|
34
|
+
function isNonEmptyString(value) {
|
|
35
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
36
|
+
}
|
|
37
|
+
function isCloneIdReference(value) {
|
|
38
|
+
return isNonEmptyString(value) && value.startsWith(ID_PREFIXES.clone);
|
|
39
|
+
}
|
|
40
|
+
function isWorktreeIdReference(value) {
|
|
41
|
+
return isNonEmptyString(value) && value.startsWith(ID_PREFIXES.worktree);
|
|
42
|
+
}
|
|
43
|
+
function sha256Hex(canonical) {
|
|
44
|
+
return createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
45
|
+
}
|
|
46
|
+
function checkUncertainty(uncertainty) {
|
|
47
|
+
const states = [...(uncertainty ?? [])];
|
|
48
|
+
if (states.some((state) => !isIdentityUncertaintyState(state))) {
|
|
49
|
+
throw new Error(`identity uncertainty states must be one of ${IDENTITY_UNCERTAINTY_STATES.join(", ")}`);
|
|
50
|
+
}
|
|
51
|
+
return states;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create the initial live declaration for one workspace. Requires the
|
|
55
|
+
* explicit "live" capability and the full identity triangle
|
|
56
|
+
* (declarationId/cloneId/worktreeId, minted by the canonical derivation
|
|
57
|
+
* below): any other opt-in value (bind, upload, session kinds) is refused,
|
|
58
|
+
* so no lifecycle event can implicitly activate live. The declaration
|
|
59
|
+
* revision starts at the frozen INITIAL_DECLARATION_REVISION.
|
|
60
|
+
*/
|
|
61
|
+
export function createLiveDeclaration(input) {
|
|
62
|
+
if (!isRecord(input)) {
|
|
63
|
+
throw new Error("createLiveDeclaration requires a declaration input object");
|
|
64
|
+
}
|
|
65
|
+
const declaration = {
|
|
66
|
+
tenant: input.tenant,
|
|
67
|
+
principal: input.principal,
|
|
68
|
+
...(input.hostFingerprint !== undefined ? { hostFingerprint: input.hostFingerprint } : {}),
|
|
69
|
+
identityUncertainty: [...input.identityUncertainty],
|
|
70
|
+
declarationId: input.declarationId,
|
|
71
|
+
cloneId: input.cloneId,
|
|
72
|
+
worktreeId: input.worktreeId,
|
|
73
|
+
authorizedRoots: [...input.authorizedRoots],
|
|
74
|
+
liveCapability: input.liveCapability,
|
|
75
|
+
policyVersion: input.policyVersion,
|
|
76
|
+
declarationRevision: INITIAL_DECLARATION_REVISION,
|
|
77
|
+
...(input.serverWorkspaceId !== undefined ? { serverWorkspaceId: input.serverWorkspaceId } : {}),
|
|
78
|
+
...(input.serverIssuer !== undefined ? { serverIssuer: input.serverIssuer } : {}),
|
|
79
|
+
...(input.bindingRevision !== undefined ? { bindingRevision: input.bindingRevision } : {}),
|
|
80
|
+
};
|
|
81
|
+
if (input.bindingRevision !== undefined && input.bindingRevision !== INITIAL_BINDING_REVISION) {
|
|
82
|
+
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)`);
|
|
83
|
+
}
|
|
84
|
+
const checked = validateWorkspaceDeclaration(declaration);
|
|
85
|
+
if (!checked.ok) {
|
|
86
|
+
throw new Error(`invalid live workspace declaration: ${checked.errors.join("; ")}`);
|
|
87
|
+
}
|
|
88
|
+
return declaration;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Revise a declaration as a pure revision increment. The revision grows by
|
|
92
|
+
* exactly one; tenant, principal, hostFingerprint, the identity triangle
|
|
93
|
+
* and the live capability are immutable. The product binding is the
|
|
94
|
+
* complete (serverWorkspaceId, serverIssuer, bindingRevision) triplet.
|
|
95
|
+
* A revision outside the binding (roots, policy version) PRESERVES the
|
|
96
|
+
* binding: no renewal is imposed. Only a change of serverWorkspaceId or
|
|
97
|
+
* serverIssuer, or an explicit bindingRevision revalidation, renews the
|
|
98
|
+
* binding — and then strictly increasingly: the first binding carries
|
|
99
|
+
* exactly INITIAL_BINDING_REVISION, and every rebind/revalidation carries
|
|
100
|
+
* a strictly greater bindingRevision; binding revisions never go
|
|
101
|
+
* backwards. The host must additionally refuse to redirect in-flight
|
|
102
|
+
* collection/publication when a rebind lands.
|
|
103
|
+
*/
|
|
104
|
+
export function reviseLiveDeclaration(previous, patch) {
|
|
105
|
+
const previousCheck = validateWorkspaceDeclaration(previous);
|
|
106
|
+
if (!previousCheck.ok) {
|
|
107
|
+
throw new Error(`invalid previous declaration: ${previousCheck.errors.join("; ")}`);
|
|
108
|
+
}
|
|
109
|
+
if (!isRecord(patch)) {
|
|
110
|
+
throw new Error("reviseLiveDeclaration requires a patch object");
|
|
111
|
+
}
|
|
112
|
+
const patchRecord = patch;
|
|
113
|
+
for (const key of Object.keys(patchRecord)) {
|
|
114
|
+
if (!LIVE_DECLARATION_PATCH_KEYS.includes(key)) {
|
|
115
|
+
throw new Error(`reviseLiveDeclaration refuses patch key "${key}": identity fields are immutable across revisions`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const workspaceChanged = patch.serverWorkspaceId !== undefined &&
|
|
119
|
+
patch.serverWorkspaceId !== previous.serverWorkspaceId;
|
|
120
|
+
const issuerChanged = patch.serverIssuer !== undefined &&
|
|
121
|
+
patch.serverIssuer !== previous.serverIssuer;
|
|
122
|
+
const bindingTouched = patch.bindingRevision !== undefined || workspaceChanged || issuerChanged;
|
|
123
|
+
// A revision outside the product binding preserves it; only a workspace or
|
|
124
|
+
// issuer change, or an explicit revalidation, renews it — strictly
|
|
125
|
+
// increasingly, starting the first binding at INITIAL_BINDING_REVISION.
|
|
126
|
+
let nextBindingRevision = previous.bindingRevision;
|
|
127
|
+
if (bindingTouched) {
|
|
128
|
+
if (patch.bindingRevision === undefined) {
|
|
129
|
+
throw new Error("reviseLiveDeclaration requires a fresh strictly increasing bindingRevision when the server workspace or issuer changes (revalidation)");
|
|
130
|
+
}
|
|
131
|
+
if (previous.bindingRevision === undefined) {
|
|
132
|
+
if (patch.bindingRevision !== INITIAL_BINDING_REVISION) {
|
|
133
|
+
throw new Error(`reviseLiveDeclaration requires the first product binding to carry bindingRevision ${INITIAL_BINDING_REVISION} (FROZEN L6a convention)`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else if (patch.bindingRevision <= previous.bindingRevision) {
|
|
137
|
+
throw new Error("reviseLiveDeclaration requires a strictly increasing bindingRevision (binding revisions are monotonic)");
|
|
138
|
+
}
|
|
139
|
+
nextBindingRevision = patch.bindingRevision;
|
|
140
|
+
}
|
|
141
|
+
const next = {
|
|
142
|
+
...previous,
|
|
143
|
+
authorizedRoots: patch.authorizedRoots !== undefined ? [...patch.authorizedRoots] : [...previous.authorizedRoots],
|
|
144
|
+
policyVersion: patch.policyVersion !== undefined ? patch.policyVersion : previous.policyVersion,
|
|
145
|
+
serverWorkspaceId: patch.serverWorkspaceId !== undefined
|
|
146
|
+
? patch.serverWorkspaceId
|
|
147
|
+
: previous.serverWorkspaceId,
|
|
148
|
+
serverIssuer: patch.serverIssuer !== undefined ? patch.serverIssuer : previous.serverIssuer,
|
|
149
|
+
bindingRevision: nextBindingRevision,
|
|
150
|
+
declarationRevision: previous.declarationRevision + 1,
|
|
151
|
+
};
|
|
152
|
+
const checked = validateWorkspaceDeclaration(next);
|
|
153
|
+
if (!checked.ok) {
|
|
154
|
+
throw new Error(`invalid revised declaration: ${checked.errors.join("; ")}`);
|
|
155
|
+
}
|
|
156
|
+
return next;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Revoke exactly one declaration as a pure private-purge request. The
|
|
160
|
+
* returned DTO names the single target by its opaque declarationId and
|
|
161
|
+
* carries the declaration's cloneId/worktreeId attachment and revision, so
|
|
162
|
+
* the recipient can never confuse two declarations of one tenant that
|
|
163
|
+
* happen to share public fingerprints or revisions. The host stops work,
|
|
164
|
+
* rejects delayed jobs and purges private snapshots, derived indexes and
|
|
165
|
+
* decryptability, while preserving authorized committed history.
|
|
166
|
+
* Revocation deletes no committed facts.
|
|
167
|
+
*/
|
|
168
|
+
export function revokeLiveDeclaration(declaration, reason) {
|
|
169
|
+
const checked = validateWorkspaceDeclaration(declaration);
|
|
170
|
+
if (!checked.ok) {
|
|
171
|
+
throw new Error(`invalid declaration: ${checked.errors.join("; ")}`);
|
|
172
|
+
}
|
|
173
|
+
if (!isRevocationReason(reason)) {
|
|
174
|
+
throw new Error(`invalid revocation reason: ${String(reason)}`);
|
|
175
|
+
}
|
|
176
|
+
const revocation = {
|
|
177
|
+
declarationId: declaration.declarationId,
|
|
178
|
+
tenant: declaration.tenant,
|
|
179
|
+
cloneId: declaration.cloneId,
|
|
180
|
+
worktreeId: declaration.worktreeId,
|
|
181
|
+
declarationRevision: declaration.declarationRevision,
|
|
182
|
+
reason,
|
|
183
|
+
requestPrivatePurge: {
|
|
184
|
+
purgePrivateSnapshots: true,
|
|
185
|
+
purgeDerivedIndexes: true,
|
|
186
|
+
revokeDecryptability: true,
|
|
187
|
+
preserveCommittedHistory: true,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
const revocationCheck = validateDeclarationRevocation(revocation);
|
|
191
|
+
if (!revocationCheck.ok) {
|
|
192
|
+
throw new Error(`invalid revocation: ${revocationCheck.errors.join("; ")}`);
|
|
193
|
+
}
|
|
194
|
+
return revocation;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Derive the clone identity for one common-dir — the ONE canonical clone
|
|
198
|
+
* derivation. The key components (hostScope, normalized common-dir
|
|
199
|
+
* realpath, common-dir project fingerprint) serialize through
|
|
200
|
+
* canonicalIdKey (unambiguous, NUL-refusing) and hash to the opaque
|
|
201
|
+
* cloneId: equal fingerprints under different realpaths derive distinct
|
|
202
|
+
* clones, while linked worktrees sharing one common-dir derive the same
|
|
203
|
+
* clone. The fingerprint may be absent only under explicit
|
|
204
|
+
* "missing_history" uncertainty; the hostScope + realpath components keep
|
|
205
|
+
* clones distinct even then (LIVE §12). The realpath stays host-local;
|
|
206
|
+
* only cloneId is shared. Deterministic and free of I/O.
|
|
207
|
+
*/
|
|
208
|
+
export function deriveCloneIdentity(input) {
|
|
209
|
+
if (!isRecord(input)) {
|
|
210
|
+
throw new Error("deriveCloneIdentity requires a clone input object");
|
|
211
|
+
}
|
|
212
|
+
if (!isNonEmptyString(input.hostScope)) {
|
|
213
|
+
throw new Error("deriveCloneIdentity requires a non-empty hostScope");
|
|
214
|
+
}
|
|
215
|
+
if (!isNonEmptyString(input.commonDirRealpath)) {
|
|
216
|
+
throw new Error("deriveCloneIdentity requires a non-empty commonDirRealpath");
|
|
217
|
+
}
|
|
218
|
+
const uncertainty = checkUncertainty(input.identityUncertainty);
|
|
219
|
+
if (input.commonDirProjectFingerprint !== undefined &&
|
|
220
|
+
!isNonEmptyString(input.commonDirProjectFingerprint)) {
|
|
221
|
+
throw new Error("deriveCloneIdentity requires a non-empty commonDirProjectFingerprint when present");
|
|
222
|
+
}
|
|
223
|
+
if (input.commonDirProjectFingerprint === undefined &&
|
|
224
|
+
!uncertainty.includes("missing_history")) {
|
|
225
|
+
throw new Error('deriveCloneIdentity omits commonDirProjectFingerprint only under explicit "missing_history" uncertainty');
|
|
226
|
+
}
|
|
227
|
+
if (input.oidAlgorithm !== "sha1" && input.oidAlgorithm !== "sha256") {
|
|
228
|
+
throw new Error('deriveCloneIdentity requires oidAlgorithm "sha1" or "sha256"');
|
|
229
|
+
}
|
|
230
|
+
const normalizedRealpath = normalizeCommonDirRealpath(input.commonDirRealpath);
|
|
231
|
+
// canonicalIdKey refuses NUL inside any component.
|
|
232
|
+
const canonical = canonicalIdKey("clone", [
|
|
233
|
+
input.hostScope,
|
|
234
|
+
normalizedRealpath,
|
|
235
|
+
input.commonDirProjectFingerprint ?? "",
|
|
236
|
+
]);
|
|
237
|
+
const identity = {
|
|
238
|
+
hostScope: input.hostScope,
|
|
239
|
+
commonDirRealpath: normalizedRealpath,
|
|
240
|
+
...(input.commonDirProjectFingerprint !== undefined
|
|
241
|
+
? { commonDirProjectFingerprint: input.commonDirProjectFingerprint }
|
|
242
|
+
: {}),
|
|
243
|
+
cloneId: `${ID_PREFIXES.clone}${sha256Hex(canonical)}`,
|
|
244
|
+
oidAlgorithm: input.oidAlgorithm,
|
|
245
|
+
identityUncertainty: [...uncertainty],
|
|
246
|
+
};
|
|
247
|
+
const checked = validateCloneIdentity(identity);
|
|
248
|
+
if (!checked.ok) {
|
|
249
|
+
throw new Error(`invalid derived clone identity: ${checked.errors.join("; ")}`);
|
|
250
|
+
}
|
|
251
|
+
return identity;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Derive the identity of one worktree under an already derived clone —
|
|
255
|
+
* the ONE canonical worktree derivation. Worktrees of one clone share
|
|
256
|
+
* cloneId and keep an isolated host-scoped worktreeId plus per-worktree
|
|
257
|
+
* git-dir, fingerprint and path. The worktree fingerprint may be absent
|
|
258
|
+
* only under explicit "missing_history" uncertainty (LIVE §12).
|
|
259
|
+
*/
|
|
260
|
+
export function deriveWorktreeIdentity(input) {
|
|
261
|
+
if (!isRecord(input)) {
|
|
262
|
+
throw new Error("deriveWorktreeIdentity requires a worktree input object");
|
|
263
|
+
}
|
|
264
|
+
if (!isRecord(input.clone) || !isCloneIdReference(input.clone.cloneId)) {
|
|
265
|
+
throw new Error("deriveWorktreeIdentity requires a derived clone carrying a canonical cloneId");
|
|
266
|
+
}
|
|
267
|
+
if (!isNonEmptyString(input.hostScope)) {
|
|
268
|
+
throw new Error("deriveWorktreeIdentity requires a non-empty hostScope");
|
|
269
|
+
}
|
|
270
|
+
if (!isNonEmptyString(input.worktreePath)) {
|
|
271
|
+
throw new Error("deriveWorktreeIdentity requires a non-empty worktreePath");
|
|
272
|
+
}
|
|
273
|
+
if (!isNonEmptyString(input.gitDir)) {
|
|
274
|
+
throw new Error("deriveWorktreeIdentity requires a non-empty gitDir");
|
|
275
|
+
}
|
|
276
|
+
const uncertainty = checkUncertainty(input.identityUncertainty);
|
|
277
|
+
if (input.worktreeFingerprint !== undefined &&
|
|
278
|
+
!isNonEmptyString(input.worktreeFingerprint)) {
|
|
279
|
+
throw new Error("deriveWorktreeIdentity requires a non-empty worktreeFingerprint when present");
|
|
280
|
+
}
|
|
281
|
+
if (input.worktreeFingerprint === undefined && !uncertainty.includes("missing_history")) {
|
|
282
|
+
throw new Error('deriveWorktreeIdentity omits worktreeFingerprint only under explicit "missing_history" uncertainty');
|
|
283
|
+
}
|
|
284
|
+
const canonical = canonicalIdKey("worktree", [input.hostScope, input.gitDir]);
|
|
285
|
+
const identity = {
|
|
286
|
+
cloneId: input.clone.cloneId,
|
|
287
|
+
worktreeId: `${ID_PREFIXES.worktree}${sha256Hex(canonical)}`,
|
|
288
|
+
worktreePath: input.worktreePath,
|
|
289
|
+
gitDir: input.gitDir,
|
|
290
|
+
...(input.worktreeFingerprint !== undefined
|
|
291
|
+
? { worktreeFingerprint: input.worktreeFingerprint }
|
|
292
|
+
: {}),
|
|
293
|
+
identityUncertainty: [...uncertainty],
|
|
294
|
+
};
|
|
295
|
+
const checked = validateWorktreeIdentity(identity);
|
|
296
|
+
if (!checked.ok) {
|
|
297
|
+
throw new Error(`invalid derived worktree identity: ${checked.errors.join("; ")}`);
|
|
298
|
+
}
|
|
299
|
+
return identity;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Derive the identity of one declaration — the ONE canonical declaration
|
|
303
|
+
* derivation. One declaration per activated worktree: the declarationId
|
|
304
|
+
* binds the host scope, the clone and the worktree, so two worktrees of
|
|
305
|
+
* one clone (and two clones of one project) derive distinct declarationIds
|
|
306
|
+
* even without fingerprints. Stable across every revision of the same
|
|
307
|
+
* declaration. Deterministic and free of I/O.
|
|
308
|
+
*/
|
|
309
|
+
export function deriveDeclarationIdentity(input) {
|
|
310
|
+
if (!isRecord(input)) {
|
|
311
|
+
throw new Error("deriveDeclarationIdentity requires a declaration identity input object");
|
|
312
|
+
}
|
|
313
|
+
if (!isRecord(input.clone) || !isCloneIdReference(input.clone.cloneId)) {
|
|
314
|
+
throw new Error("deriveDeclarationIdentity requires a derived clone carrying a canonical cloneId");
|
|
315
|
+
}
|
|
316
|
+
if (!isRecord(input.worktree) || !isWorktreeIdReference(input.worktree.worktreeId)) {
|
|
317
|
+
throw new Error("deriveDeclarationIdentity requires a derived worktree carrying a canonical worktreeId");
|
|
318
|
+
}
|
|
319
|
+
if (!isNonEmptyString(input.hostScope)) {
|
|
320
|
+
throw new Error("deriveDeclarationIdentity requires a non-empty hostScope");
|
|
321
|
+
}
|
|
322
|
+
const canonical = canonicalIdKey("declaration", [
|
|
323
|
+
input.hostScope,
|
|
324
|
+
input.clone.cloneId,
|
|
325
|
+
input.worktree.worktreeId,
|
|
326
|
+
]);
|
|
327
|
+
const identity = {
|
|
328
|
+
declarationId: `${ID_PREFIXES.declaration}${sha256Hex(canonical)}`,
|
|
329
|
+
cloneId: input.clone.cloneId,
|
|
330
|
+
worktreeId: input.worktree.worktreeId,
|
|
331
|
+
};
|
|
332
|
+
return identity;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Decide whether two clone references denote the same clone, by canonical
|
|
336
|
+
* identifier only: the cloneId minted by the shared canonical derivation is
|
|
337
|
+
* the clone identity, so the comparison is an equivalence (transitive and
|
|
338
|
+
* consistent between full DTOs and opaque references). Throws when either
|
|
339
|
+
* reference lacks a cloneId — equality is never guessed from components.
|
|
340
|
+
*/
|
|
341
|
+
export function isSameClone(a, b) {
|
|
342
|
+
if (!isNonEmptyString(a.cloneId) || !isNonEmptyString(b.cloneId)) {
|
|
343
|
+
throw new Error("isSameClone compares canonical clone ids: both references must carry a non-empty cloneId");
|
|
344
|
+
}
|
|
345
|
+
return a.cloneId === b.cloneId;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Classify a host lifecycle event against live declarations. Bind, upload,
|
|
349
|
+
* code-workspace creation and session open/end never activate or remove a
|
|
350
|
+
* declaration: the only outcome is "no-activation-change". Unknown kinds
|
|
351
|
+
* are refused.
|
|
352
|
+
*/
|
|
353
|
+
export function classifyLifecycleEvent(kind) {
|
|
354
|
+
if (!isNonActivatingLifecycleKind(kind)) {
|
|
355
|
+
throw new Error(`unknown lifecycle kind: ${String(kind)}`);
|
|
356
|
+
}
|
|
357
|
+
return "no-activation-change";
|
|
358
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Vendored from rhanka/engram@545a57b8911ab108e84fa1778e0f268fe11bf1de: engram-live/retention.ts.
|
|
2
|
+
// Compiled with TypeScript 5.9.3 (ES2022/NodeNext, declarations); do not hand-edit.
|
|
3
|
+
import { type LiveScope, type RetentionCoverageReportDto, type RetentionReceiptDto, type RetentionReceiptQuery, type RetentionRequestDto, type SelectedRootDto } from "./contracts.js";
|
|
4
|
+
export declare class RetentionError extends Error {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* THE injected retention port (plan/09 R1: a port, never a base). Engram
|
|
9
|
+
* defines the contract only; h2a implements durable acquisition and the
|
|
10
|
+
* private store behind it. Receipts are scoped by tenant + clone and
|
|
11
|
+
* durable across passes; the port never carries file content.
|
|
12
|
+
*/
|
|
13
|
+
export interface RetentionPort {
|
|
14
|
+
recordRequest(request: RetentionRequestDto): Promise<{
|
|
15
|
+
received: true;
|
|
16
|
+
}>;
|
|
17
|
+
recordReceipt(receipt: RetentionReceiptDto): Promise<{
|
|
18
|
+
received: true;
|
|
19
|
+
}>;
|
|
20
|
+
loadReceipts(query: RetentionReceiptQuery): Promise<{
|
|
21
|
+
receipts: readonly RetentionReceiptDto[];
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
export interface RetentionPassInput {
|
|
25
|
+
readonly roots: readonly SelectedRootDto[];
|
|
26
|
+
readonly scope: LiveScope;
|
|
27
|
+
/** Injected evaluation time for requestedAt (ms); the engine reads no clock. */
|
|
28
|
+
readonly now: number;
|
|
29
|
+
}
|
|
30
|
+
export interface RetentionPassStats {
|
|
31
|
+
readonly requests: number;
|
|
32
|
+
readonly receipts: number;
|
|
33
|
+
readonly missing: number;
|
|
34
|
+
}
|
|
35
|
+
export interface RetentionPassOutcome {
|
|
36
|
+
/** "complete": every request has its scoped durable receipt. "gaps": otherwise. */
|
|
37
|
+
readonly state: "complete" | "gaps";
|
|
38
|
+
readonly requests: readonly RetentionRequestDto[];
|
|
39
|
+
readonly coverage: readonly RetentionCoverageReportDto[];
|
|
40
|
+
readonly stats: RetentionPassStats;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Derive one retention request per distinct root OID, sorted by OID.
|
|
44
|
+
* Pure and total over valid inputs: no count cap, no age filter, no
|
|
45
|
+
* pruning — an old default branch, an expired window branch kept as a
|
|
46
|
+
* previously-ingested root, a PR head of any state and a detached HEAD
|
|
47
|
+
* each yield exactly one request.
|
|
48
|
+
*/
|
|
49
|
+
export declare function deriveRetentionRequests(roots: readonly SelectedRootDto[], scope: LiveScope, now: number): RetentionRequestDto[];
|
|
50
|
+
/**
|
|
51
|
+
* Run one retention pass: record every request, then require every scoped
|
|
52
|
+
* durable receipt. Deterministic over the injected inputs; the port is the
|
|
53
|
+
* only effect. A receipt satisfies a request only under the exact same
|
|
54
|
+
* tenant, clone, algorithm and OID; cross-scope receipts never satisfy.
|
|
55
|
+
*/
|
|
56
|
+
export declare function runRetentionPass(input: RetentionPassInput, port: RetentionPort): Promise<RetentionPassOutcome>;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Vendored from rhanka/engram@545a57b8911ab108e84fa1778e0f268fe11bf1de: engram-live/retention.ts.
|
|
2
|
+
// Compiled with TypeScript 5.9.3 (ES2022/NodeNext, declarations); do not hand-edit.
|
|
3
|
+
// @sentropic/engram-live/retention — R1 private retention of every ingested root
|
|
4
|
+
// (spec/SPEC_LIVE_GIT_INDEXING.md §4, A02/A04; plan/09 R1).
|
|
5
|
+
//
|
|
6
|
+
// The neutral retention engine. Engram derives one scoped retention request
|
|
7
|
+
// per distinct ingested root OID and requires one scoped durable receipt
|
|
8
|
+
// before any reconstructible-coverage claim:
|
|
9
|
+
//
|
|
10
|
+
// 1. derive the requests from the L2b selected roots (./refs.js): default
|
|
11
|
+
// branch even when old, active local/remote branches, PR heads of all
|
|
12
|
+
// states, detached worktree HEADs, previously ingested roots — with no
|
|
13
|
+
// commit-count and no commit-age limit, and nothing ever erased here;
|
|
14
|
+
// 2. record every request through the INJECTED retention port (a port,
|
|
15
|
+
// never a base; engram owns no database and no object store);
|
|
16
|
+
// 3. load the scoped durable receipts from that same port and classify
|
|
17
|
+
// coverage: every request needs its tenant + clone receipt; a missing
|
|
18
|
+
// receipt is an explicit object-availability gap, never a silent
|
|
19
|
+
// current claim.
|
|
20
|
+
//
|
|
21
|
+
// Neutrality: this module imports ONLY ./contracts.js (data-pure DTOs and
|
|
22
|
+
// validators). No h2a, no cluster-mesh, no sentropic, no storage driver,
|
|
23
|
+
// no network, no filesystem, no clock read — the evaluation time `now` is
|
|
24
|
+
// injected, and durability comes from the host port alone.
|
|
25
|
+
//
|
|
26
|
+
// Host contract (H: live-object-store.ts, delegate.ts — NOT implemented
|
|
27
|
+
// here): h2a consumes RetentionRequestDto through RetentionPort, acquires
|
|
28
|
+
// the pinned committed objects into its protected private Git store under
|
|
29
|
+
// private refs (no writes into the observed clone, no borrowed alternates
|
|
30
|
+
// store, no repository-controlled programs), then records a
|
|
31
|
+
// RetentionReceiptDto with an opaque coordinated-backup backupRef for L8.
|
|
32
|
+
// If removal wins the acquisition race, H records no receipt and the engine
|
|
33
|
+
// reports the gap; quotas pause further work without deleting protected
|
|
34
|
+
// roots. P1 reuses this facility for PR roots. Uncommitted plaintext never
|
|
35
|
+
// enters a request or a receipt: requests carry OIDs only, by construction.
|
|
36
|
+
import { RETENTION_COVERAGE_AXIS, validateLiveScope, validateQualifiedOid, validateRetentionReceipt, validateRetentionRequest, } from "./contracts.js";
|
|
37
|
+
export class RetentionError extends Error {
|
|
38
|
+
constructor(message) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = "RetentionError";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function checkScope(scope) {
|
|
44
|
+
const checked = validateLiveScope(scope);
|
|
45
|
+
if (!checked.ok) {
|
|
46
|
+
throw new RetentionError(`invalid retention scope: ${checked.errors.join("; ")}`);
|
|
47
|
+
}
|
|
48
|
+
if (scope.worktree !== undefined) {
|
|
49
|
+
throw new RetentionError("retention scope must be clone-scoped: worktree must be absent (retention is per tenant + clone)");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function checkNow(now) {
|
|
53
|
+
if (typeof now !== "number" || !Number.isSafeInteger(now) || now <= 0) {
|
|
54
|
+
throw new RetentionError("retention pass requires a positive safe-integer now (milliseconds)");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Derive one retention request per distinct root OID, sorted by OID.
|
|
59
|
+
* Pure and total over valid inputs: no count cap, no age filter, no
|
|
60
|
+
* pruning — an old default branch, an expired window branch kept as a
|
|
61
|
+
* previously-ingested root, a PR head of any state and a detached HEAD
|
|
62
|
+
* each yield exactly one request.
|
|
63
|
+
*/
|
|
64
|
+
export function deriveRetentionRequests(roots, scope, now) {
|
|
65
|
+
checkScope(scope);
|
|
66
|
+
checkNow(now);
|
|
67
|
+
// Immutable snapshot: the caller-owned scope object may be shared and
|
|
68
|
+
// mutated while the pass awaits the port; every derived request carries
|
|
69
|
+
// the pass-entry scope, never a later mutation.
|
|
70
|
+
const frozen = { tenant: scope.tenant, clone: scope.clone };
|
|
71
|
+
const seen = new Set();
|
|
72
|
+
const requests = [];
|
|
73
|
+
for (const root of roots) {
|
|
74
|
+
const checked = validateQualifiedOid(root.qualifiedOid);
|
|
75
|
+
if (!checked.ok) {
|
|
76
|
+
throw new RetentionError(`invalid root OID ${root.qualifiedOid.oid}: ${checked.errors.join("; ")}`);
|
|
77
|
+
}
|
|
78
|
+
if (seen.has(root.qualifiedOid.oid))
|
|
79
|
+
continue;
|
|
80
|
+
seen.add(root.qualifiedOid.oid);
|
|
81
|
+
if (root.sources.length === 0) {
|
|
82
|
+
throw new RetentionError(`root ${root.qualifiedOid.oid} carries no provenance source`);
|
|
83
|
+
}
|
|
84
|
+
const first = root.sources[0];
|
|
85
|
+
const refBacked = root.sources.find((source) => source.refName !== null);
|
|
86
|
+
const request = {
|
|
87
|
+
scope: { tenant: frozen.tenant, clone: frozen.clone },
|
|
88
|
+
algorithm: root.qualifiedOid.algorithm,
|
|
89
|
+
oid: root.qualifiedOid.oid,
|
|
90
|
+
firstSignal: first.signal,
|
|
91
|
+
refName: refBacked !== undefined ? refBacked.refName : null,
|
|
92
|
+
requestedAt: now,
|
|
93
|
+
};
|
|
94
|
+
const valid = validateRetentionRequest(request);
|
|
95
|
+
if (!valid.ok) {
|
|
96
|
+
throw new RetentionError(`derived invalid retention request for ${request.oid}: ${valid.errors.join("; ")}`);
|
|
97
|
+
}
|
|
98
|
+
requests.push(request);
|
|
99
|
+
}
|
|
100
|
+
requests.sort((left, right) => left.oid.localeCompare(right.oid));
|
|
101
|
+
return requests;
|
|
102
|
+
}
|
|
103
|
+
function receiptKey(receipt) {
|
|
104
|
+
return `${receipt.scope.tenant}:${receipt.scope.clone}:${receipt.algorithm}:${receipt.oid}`;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Run one retention pass: record every request, then require every scoped
|
|
108
|
+
* durable receipt. Deterministic over the injected inputs; the port is the
|
|
109
|
+
* only effect. A receipt satisfies a request only under the exact same
|
|
110
|
+
* tenant, clone, algorithm and OID; cross-scope receipts never satisfy.
|
|
111
|
+
*/
|
|
112
|
+
export async function runRetentionPass(input, port) {
|
|
113
|
+
checkScope(input.scope);
|
|
114
|
+
checkNow(input.now);
|
|
115
|
+
// Immutable snapshot taken before the first await: the caller-owned
|
|
116
|
+
// input.scope object may be shared and mutated while the pass awaits the
|
|
117
|
+
// port. Every query, receipt check and missing-key below uses this
|
|
118
|
+
// pass-entry scope; re-reading input.scope after an await would let a
|
|
119
|
+
// foreign-scope receipt falsely satisfy a request (false acquittement).
|
|
120
|
+
const scope = { tenant: input.scope.tenant, clone: input.scope.clone };
|
|
121
|
+
const requests = deriveRetentionRequests(input.roots, scope, input.now);
|
|
122
|
+
for (const request of requests) {
|
|
123
|
+
await port.recordRequest(request);
|
|
124
|
+
}
|
|
125
|
+
const algorithms = new Set();
|
|
126
|
+
for (const request of requests)
|
|
127
|
+
algorithms.add(request.algorithm);
|
|
128
|
+
const receipts = [];
|
|
129
|
+
if (algorithms.size === 0) {
|
|
130
|
+
return { state: "complete", requests, coverage: [], stats: { requests: 0, receipts: 0, missing: 0 } };
|
|
131
|
+
}
|
|
132
|
+
for (const algorithm of algorithms) {
|
|
133
|
+
const loaded = await port.loadReceipts({ scope, algorithm });
|
|
134
|
+
for (const receipt of loaded.receipts) {
|
|
135
|
+
const checked = validateRetentionReceipt(receipt);
|
|
136
|
+
if (!checked.ok) {
|
|
137
|
+
throw new RetentionError(`invalid retention receipt for ${receipt.oid}: ${checked.errors.join("; ")}`);
|
|
138
|
+
}
|
|
139
|
+
// The port is scoped by the query, but the engine re-checks scope:
|
|
140
|
+
// a foreign receipt that slipped through never satisfies a request.
|
|
141
|
+
if (receipt.scope.tenant !== scope.tenant || receipt.scope.clone !== scope.clone)
|
|
142
|
+
continue;
|
|
143
|
+
if (receipt.algorithm !== algorithm)
|
|
144
|
+
continue;
|
|
145
|
+
receipts.push(receipt);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const byKey = new Map();
|
|
149
|
+
for (const receipt of receipts)
|
|
150
|
+
byKey.set(receiptKey(receipt), receipt);
|
|
151
|
+
const missing = [];
|
|
152
|
+
for (const request of requests) {
|
|
153
|
+
const key = `${scope.tenant}:${scope.clone}:${request.algorithm}:${request.oid}`;
|
|
154
|
+
if (!byKey.has(key))
|
|
155
|
+
missing.push({ algorithm: request.algorithm, oid: request.oid });
|
|
156
|
+
}
|
|
157
|
+
missing.sort((left, right) => left.oid.localeCompare(right.oid));
|
|
158
|
+
if (missing.length === 0) {
|
|
159
|
+
return {
|
|
160
|
+
state: "complete",
|
|
161
|
+
requests,
|
|
162
|
+
coverage: [],
|
|
163
|
+
stats: { requests: requests.length, receipts: byKey.size, missing: 0 },
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const preview = missing.slice(0, 3).map((entry) => entry.oid.slice(0, 12)).join(", ");
|
|
167
|
+
return {
|
|
168
|
+
state: "gaps",
|
|
169
|
+
requests,
|
|
170
|
+
coverage: [{
|
|
171
|
+
axis: RETENTION_COVERAGE_AXIS,
|
|
172
|
+
state: "unavailable",
|
|
173
|
+
reason: `private retention incomplete: ${missing.length} of ${requests.length} ingested root(s) without a scoped durable retention receipt (${preview}…); no reconstructible coverage is claimed and nothing was pruned`,
|
|
174
|
+
cause: "missing-receipt",
|
|
175
|
+
missing,
|
|
176
|
+
}],
|
|
177
|
+
stats: { requests: requests.length, receipts: byKey.size, missing: missing.length },
|
|
178
|
+
};
|
|
179
|
+
}
|
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.2",
|
|
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
|
}
|