blun-king-cli 9.1.563 → 9.1.565
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/agent-spine-plugin/.claude-plugin/marketplace.json +20 -20
- package/agent-spine-plugin/.claude-plugin/plugin.json +14 -14
- package/agent-spine-plugin/.codex-plugin/plugin.json +34 -34
- package/agent-spine-plugin/.mcp.json +8 -8
- package/agent-spine-plugin/CONTRIBUTING.md +52 -0
- package/agent-spine-plugin/LICENSE +186 -186
- package/agent-spine-plugin/README.md +394 -0
- package/agent-spine-plugin/SECURITY.md +47 -0
- package/agent-spine-plugin/assets/agentspine-banner.svg +32 -32
- package/agent-spine-plugin/bin/agentspine-mcp.js +4 -4
- package/agent-spine-plugin/bin/agentspine.js +7 -7
- package/agent-spine-plugin/blun.plugin.json +33 -33
- package/agent-spine-plugin/hooks/codex.json +47 -47
- package/agent-spine-plugin/hooks/hooks.json +106 -106
- package/agent-spine-plugin/hooks/version.json +5 -5
- package/agent-spine-plugin/package.json +69 -69
- package/agent-spine-plugin/scripts/check-hosts.js +199 -199
- package/agent-spine-plugin/skill/SKILL.md +76 -76
- package/agent-spine-plugin/skills/agent-spine/SKILL.md +85 -85
- package/agent-spine-plugin/src/cli.js +1488 -1442
- package/agent-spine-plugin/src/hook.js +886 -812
- package/agent-spine-plugin/src/index.js +93 -93
- package/agent-spine-plugin/src/lib/acceptance.js +333 -333
- package/agent-spine-plugin/src/lib/attention.js +755 -755
- package/agent-spine-plugin/src/lib/audit.js +351 -342
- package/agent-spine-plugin/src/lib/authentication.js +515 -515
- package/agent-spine-plugin/src/lib/briefing.js +317 -317
- package/agent-spine-plugin/src/lib/catalog.js +167 -167
- package/agent-spine-plugin/src/lib/channel-runtime.js +665 -665
- package/agent-spine-plugin/src/lib/context.js +154 -154
- package/agent-spine-plugin/src/lib/continuity.js +338 -338
- package/agent-spine-plugin/src/lib/coordination.js +577 -577
- package/agent-spine-plugin/src/lib/documents.js +217 -217
- package/agent-spine-plugin/src/lib/feed-transport.js +499 -499
- package/agent-spine-plugin/src/lib/filesystem-retry.js +32 -32
- package/agent-spine-plugin/src/lib/gateway-runtime.js +1119 -760
- package/agent-spine-plugin/src/lib/graph.js +337 -337
- package/agent-spine-plugin/src/lib/hook-audit.js +29 -0
- package/agent-spine-plugin/src/lib/https-transport.js +392 -392
- package/agent-spine-plugin/src/lib/indexed-memory-offline.js +40 -40
- package/agent-spine-plugin/src/lib/indexed-memory.js +281 -281
- package/agent-spine-plugin/src/lib/learning.js +6923 -6466
- package/agent-spine-plugin/src/lib/object-transport.js +206 -206
- package/agent-spine-plugin/src/lib/owned-file-lock.js +143 -143
- package/agent-spine-plugin/src/lib/paths.js +109 -109
- package/agent-spine-plugin/src/lib/peer-transport.js +283 -283
- package/agent-spine-plugin/src/lib/persona-runtime.js +581 -581
- package/agent-spine-plugin/src/lib/preflight.js +702 -702
- package/agent-spine-plugin/src/lib/runtime.js +13 -13
- package/agent-spine-plugin/src/lib/selfstarter.js +892 -819
- package/agent-spine-plugin/src/lib/sharing.js +969 -969
- package/agent-spine-plugin/src/lib/source-roots.js +529 -482
- package/agent-spine-plugin/src/lib/sqlite-transport.js +501 -501
- package/agent-spine-plugin/src/lib/telegram-adapter.js +119 -119
- package/agent-spine-plugin/src/lib/voice-runtime.js +39 -39
- package/agent-spine-plugin/src/mcp.js +597 -572
- package/agent-spine-plugin/src/version.js +1 -1
- package/agent-spine-plugin/src/worker.js +202 -195
- package/bin/active-steer-priority-policy.cjs +24 -0
- package/bin/launcher-runtime.js +8 -1
- package/bin/mnemo-tool-agent-policy.cjs +22 -0
- package/bin/thinking-activity-status-policy.cjs +132 -0
- package/bin/thinking-only-guard.cjs +75 -0
- package/bin/tool-call-loop-policy.cjs +53 -0
- package/bin/turn-thinking-policy.cjs +25 -1
- package/blun.mjs +554 -64
- package/package.json +4 -1
- package/standard-skills/translate-native/LICENSE +21 -21
- package/standard-skills/translate-native/references/evaluation-protocol.md +95 -95
- package/standard-skills/translate-native/references/native-orthography.md +79 -79
- package/standard-skills/translate-native/references/native-translation-standard.md +94 -94
- package/standard-skills/translate-native/references/structured-content.md +72 -72
- package/standard-skills/translate-native/references/translationese-review.md +77 -77
- package/standard-skills/translate-native/scripts/blun_language_guard.py +697 -697
- package/standard-skills/translate-native/scripts/check_diacritics.py +353 -353
- package/standard-skills/translate-native/scripts/guard_service_client.py +264 -264
- package/standard-skills/translate-native/scripts/language_gateway.py +62 -62
- package/standard-skills/translate-native/scripts/language_quality.py +377 -377
- package/standard-skills/translate-native/scripts/pre_output_guard.py +64 -64
- package/standard-skills/translate-native/scripts/translation_guard.py +916 -916
- package/standard-tools/language-guard/blun_language_guard.py +697 -697
- package/standard-tools/language-guard/check_diacritics.py +353 -353
- package/standard-tools/language-guard/guard_service_client.py +264 -264
- package/standard-tools/language-guard/language_quality.py +377 -377
- package/standard-tools/language-guard/translation_guard.py +916 -916
|
@@ -1,206 +1,206 @@
|
|
|
1
|
-
import { lookup as dnsLookup } from "node:dns/promises";
|
|
2
|
-
import { request as httpsRequest } from "node:https";
|
|
3
|
-
import { isIP } from "node:net";
|
|
4
|
-
import {
|
|
5
|
-
buildHttpsSnapshot, fetchHttpsSnapshot, resolveHttpsEndpoint, validateHttpsSnapshot
|
|
6
|
-
} from "./https-transport.js";
|
|
7
|
-
|
|
8
|
-
const CONFIRMATION = "local-share-confirmed";
|
|
9
|
-
const MAX_TOKEN_BYTES = 8192;
|
|
10
|
-
const MAX_RESPONSE_BYTES = 16 * 1024;
|
|
11
|
-
const TOKEN_ENV_RE = /^[A-Z_][A-Z0-9_]{0,127}$/;
|
|
12
|
-
const DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
13
|
-
|
|
14
|
-
function bearerToken(tokenEnv, environment) {
|
|
15
|
-
if (tokenEnv === null || tokenEnv === undefined) return null;
|
|
16
|
-
if (!TOKEN_ENV_RE.test(tokenEnv)) throw new Error("tokenEnv must be an uppercase environment variable name");
|
|
17
|
-
const value = environment[tokenEnv];
|
|
18
|
-
if (typeof value !== "string" || !value) {
|
|
19
|
-
throw new Error(`HTTPS object token environment variable is missing: ${tokenEnv}`);
|
|
20
|
-
}
|
|
21
|
-
if (Buffer.byteLength(value) > MAX_TOKEN_BYTES || /[\0-\x1f\x7f]/.test(value)) {
|
|
22
|
-
throw new Error("HTTPS object bearer token is invalid");
|
|
23
|
-
}
|
|
24
|
-
return value;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function timeout(value) {
|
|
28
|
-
if (!Number.isInteger(value) || value < 1000 || value > 30000) {
|
|
29
|
-
throw new Error("HTTPS object timeout must be between 1000 and 30000 milliseconds");
|
|
30
|
-
}
|
|
31
|
-
return value;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function within(promise, milliseconds, message) {
|
|
35
|
-
let timer;
|
|
36
|
-
try {
|
|
37
|
-
return await Promise.race([
|
|
38
|
-
promise,
|
|
39
|
-
new Promise((_resolve, reject) => {
|
|
40
|
-
timer = setTimeout(() => reject(new Error(message)), milliseconds);
|
|
41
|
-
})
|
|
42
|
-
]);
|
|
43
|
-
} finally {
|
|
44
|
-
clearTimeout(timer);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function validateHttpsObjectBase(value) {
|
|
49
|
-
let endpoint;
|
|
50
|
-
try {
|
|
51
|
-
endpoint = new URL(value);
|
|
52
|
-
} catch {
|
|
53
|
-
throw new Error("HTTPS object base URL is invalid");
|
|
54
|
-
}
|
|
55
|
-
if (endpoint.protocol !== "https:") throw new Error("HTTPS object publishing requires an https:// URL");
|
|
56
|
-
if (endpoint.username || endpoint.password) {
|
|
57
|
-
throw new Error("credentials must not be embedded in an HTTPS object base URL");
|
|
58
|
-
}
|
|
59
|
-
if (endpoint.search || endpoint.hash) {
|
|
60
|
-
throw new Error("HTTPS object base URLs cannot contain a query or fragment");
|
|
61
|
-
}
|
|
62
|
-
if (!endpoint.hostname || /%(?:2f|5c)/i.test(endpoint.pathname)) {
|
|
63
|
-
throw new Error("HTTPS object base URL contains an ambiguous path");
|
|
64
|
-
}
|
|
65
|
-
endpoint.pathname = endpoint.pathname.replace(/\/+$/, "");
|
|
66
|
-
return endpoint;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function httpsObjectUrl(baseUrl, snapshotDigest) {
|
|
70
|
-
if (!DIGEST_RE.test(snapshotDigest || "")) throw new Error("snapshot digest is invalid");
|
|
71
|
-
const endpoint = validateHttpsObjectBase(baseUrl);
|
|
72
|
-
endpoint.pathname = `${endpoint.pathname}/objects/${snapshotDigest}.json`;
|
|
73
|
-
return endpoint.toString();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function upload({
|
|
77
|
-
url, body, digest, token, timeoutMs, allowPrivateNetwork, lookup, request
|
|
78
|
-
}) {
|
|
79
|
-
const resolved = await within(
|
|
80
|
-
resolveHttpsEndpoint(url, { allowPrivateNetwork, lookup }),
|
|
81
|
-
timeoutMs,
|
|
82
|
-
"HTTPS object DNS resolution timed out"
|
|
83
|
-
);
|
|
84
|
-
const selected = resolved.addresses[0];
|
|
85
|
-
const headers = {
|
|
86
|
-
accept: "application/json",
|
|
87
|
-
"accept-encoding": "identity",
|
|
88
|
-
"content-length": String(body.length),
|
|
89
|
-
"content-type": "application/vnd.agentspine.snapshot+json",
|
|
90
|
-
"if-none-match": "*",
|
|
91
|
-
"user-agent": "AgentSpine/0.1 HTTPS object transport",
|
|
92
|
-
"x-agentspine-digest": `sha256:${digest}`
|
|
93
|
-
};
|
|
94
|
-
if (token) headers.authorization = `Bearer ${token}`;
|
|
95
|
-
return new Promise((resolvePromise, rejectPromise) => {
|
|
96
|
-
let deadline;
|
|
97
|
-
let settled = false;
|
|
98
|
-
const reject = (error) => {
|
|
99
|
-
if (settled) return;
|
|
100
|
-
settled = true;
|
|
101
|
-
clearTimeout(deadline);
|
|
102
|
-
rejectPromise(error);
|
|
103
|
-
};
|
|
104
|
-
const resolve = (value) => {
|
|
105
|
-
if (settled) return;
|
|
106
|
-
settled = true;
|
|
107
|
-
clearTimeout(deadline);
|
|
108
|
-
resolvePromise(value);
|
|
109
|
-
};
|
|
110
|
-
const options = {
|
|
111
|
-
protocol: "https:", hostname: resolved.lookupHostname,
|
|
112
|
-
port: resolved.endpoint.port || 443,
|
|
113
|
-
path: resolved.endpoint.pathname, method: "PUT", headers,
|
|
114
|
-
agent: false, autoSelectFamily: false,
|
|
115
|
-
servername: isIP(resolved.lookupHostname) ? undefined : resolved.lookupHostname,
|
|
116
|
-
lookup: (_hostname, lookupOptions, callback) => {
|
|
117
|
-
const family = typeof lookupOptions === "object" && lookupOptions.family
|
|
118
|
-
? Number(lookupOptions.family) : 0;
|
|
119
|
-
const usable = family
|
|
120
|
-
? resolved.addresses.find((item) => item.family === family)
|
|
121
|
-
: selected;
|
|
122
|
-
if (!usable) return callback(new Error("no vetted DNS address matches the requested family"));
|
|
123
|
-
return callback(null, usable.address, usable.family);
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
const active = request(options, (response) => {
|
|
127
|
-
const status = Number(response.statusCode || 0);
|
|
128
|
-
if (status >= 300 && status < 400 && status !== 412) {
|
|
129
|
-
response.resume();
|
|
130
|
-
reject(new Error("HTTPS object redirects are not followed"));
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const chunks = [];
|
|
134
|
-
let bytes = 0;
|
|
135
|
-
response.on("data", (chunk) => {
|
|
136
|
-
bytes += chunk.length;
|
|
137
|
-
if (bytes > MAX_RESPONSE_BYTES) {
|
|
138
|
-
active.destroy(new Error("HTTPS object response exceeds the 16 KiB limit"));
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
chunks.push(chunk);
|
|
142
|
-
});
|
|
143
|
-
response.on("end", () => {
|
|
144
|
-
if (![201, 204, 412].includes(status)) {
|
|
145
|
-
reject(new Error(`HTTPS object publish failed with status ${status}`));
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
resolve({ status, responseBytes: bytes });
|
|
149
|
-
});
|
|
150
|
-
response.on("error", reject);
|
|
151
|
-
});
|
|
152
|
-
active.setTimeout(timeoutMs, () => active.destroy(new Error("HTTPS object publish timed out")));
|
|
153
|
-
deadline = setTimeout(() => active.destroy(new Error("HTTPS object publish timed out")), timeoutMs);
|
|
154
|
-
active.on("error", reject);
|
|
155
|
-
active.end(body);
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export async function putHttpsSnapshot({
|
|
160
|
-
baseUrl, snapshot, tokenEnv = null, timeoutMs = 10000,
|
|
161
|
-
allowPrivateNetwork = false, confirmation = null,
|
|
162
|
-
environment = process.env, lookup = dnsLookup, request = httpsRequest
|
|
163
|
-
}) {
|
|
164
|
-
if (confirmation !== CONFIRMATION) {
|
|
165
|
-
throw new Error("HTTPS object publishing requires explicit local owner confirmation");
|
|
166
|
-
}
|
|
167
|
-
timeout(timeoutMs);
|
|
168
|
-
const validated = validateHttpsSnapshot(structuredClone(snapshot));
|
|
169
|
-
const objectUrl = httpsObjectUrl(baseUrl, validated.digest);
|
|
170
|
-
const token = bearerToken(tokenEnv, environment);
|
|
171
|
-
const body = Buffer.from(JSON.stringify(validated));
|
|
172
|
-
const result = await upload({
|
|
173
|
-
url: objectUrl, body, digest: validated.digest, token, timeoutMs,
|
|
174
|
-
allowPrivateNetwork, lookup, request
|
|
175
|
-
});
|
|
176
|
-
const fetched = await fetchHttpsSnapshot({
|
|
177
|
-
url: objectUrl, tokenEnv, timeoutMs, allowPrivateNetwork,
|
|
178
|
-
confirmation, environment, lookup, request
|
|
179
|
-
});
|
|
180
|
-
if (fetched.snapshot.digest !== validated.digest) {
|
|
181
|
-
throw new Error("HTTPS object read-back does not match the published snapshot");
|
|
182
|
-
}
|
|
183
|
-
return {
|
|
184
|
-
created: result.status !== 412,
|
|
185
|
-
alreadyExisted: result.status === 412,
|
|
186
|
-
verified: true,
|
|
187
|
-
objectUrl,
|
|
188
|
-
snapshotId: validated.snapshotId,
|
|
189
|
-
snapshotDigest: validated.digest,
|
|
190
|
-
scopeId: validated.scopeId,
|
|
191
|
-
adapterId: validated.adapterId,
|
|
192
|
-
events: validated.events.length,
|
|
193
|
-
authenticatedWrite: Boolean(token),
|
|
194
|
-
authority: "context-only"
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export async function publishHttpsSnapshot({
|
|
199
|
-
root = process.cwd(), directory, baseUrl, snapshotId, now = new Date(), ...options
|
|
200
|
-
}) {
|
|
201
|
-
if (options.confirmation !== CONFIRMATION) {
|
|
202
|
-
throw new Error("HTTPS object publishing requires explicit local owner confirmation");
|
|
203
|
-
}
|
|
204
|
-
const snapshot = await buildHttpsSnapshot({ root, directory, snapshotId, now });
|
|
205
|
-
return putHttpsSnapshot({ baseUrl, snapshot, ...options });
|
|
206
|
-
}
|
|
1
|
+
import { lookup as dnsLookup } from "node:dns/promises";
|
|
2
|
+
import { request as httpsRequest } from "node:https";
|
|
3
|
+
import { isIP } from "node:net";
|
|
4
|
+
import {
|
|
5
|
+
buildHttpsSnapshot, fetchHttpsSnapshot, resolveHttpsEndpoint, validateHttpsSnapshot
|
|
6
|
+
} from "./https-transport.js";
|
|
7
|
+
|
|
8
|
+
const CONFIRMATION = "local-share-confirmed";
|
|
9
|
+
const MAX_TOKEN_BYTES = 8192;
|
|
10
|
+
const MAX_RESPONSE_BYTES = 16 * 1024;
|
|
11
|
+
const TOKEN_ENV_RE = /^[A-Z_][A-Z0-9_]{0,127}$/;
|
|
12
|
+
const DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
13
|
+
|
|
14
|
+
function bearerToken(tokenEnv, environment) {
|
|
15
|
+
if (tokenEnv === null || tokenEnv === undefined) return null;
|
|
16
|
+
if (!TOKEN_ENV_RE.test(tokenEnv)) throw new Error("tokenEnv must be an uppercase environment variable name");
|
|
17
|
+
const value = environment[tokenEnv];
|
|
18
|
+
if (typeof value !== "string" || !value) {
|
|
19
|
+
throw new Error(`HTTPS object token environment variable is missing: ${tokenEnv}`);
|
|
20
|
+
}
|
|
21
|
+
if (Buffer.byteLength(value) > MAX_TOKEN_BYTES || /[\0-\x1f\x7f]/.test(value)) {
|
|
22
|
+
throw new Error("HTTPS object bearer token is invalid");
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function timeout(value) {
|
|
28
|
+
if (!Number.isInteger(value) || value < 1000 || value > 30000) {
|
|
29
|
+
throw new Error("HTTPS object timeout must be between 1000 and 30000 milliseconds");
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function within(promise, milliseconds, message) {
|
|
35
|
+
let timer;
|
|
36
|
+
try {
|
|
37
|
+
return await Promise.race([
|
|
38
|
+
promise,
|
|
39
|
+
new Promise((_resolve, reject) => {
|
|
40
|
+
timer = setTimeout(() => reject(new Error(message)), milliseconds);
|
|
41
|
+
})
|
|
42
|
+
]);
|
|
43
|
+
} finally {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function validateHttpsObjectBase(value) {
|
|
49
|
+
let endpoint;
|
|
50
|
+
try {
|
|
51
|
+
endpoint = new URL(value);
|
|
52
|
+
} catch {
|
|
53
|
+
throw new Error("HTTPS object base URL is invalid");
|
|
54
|
+
}
|
|
55
|
+
if (endpoint.protocol !== "https:") throw new Error("HTTPS object publishing requires an https:// URL");
|
|
56
|
+
if (endpoint.username || endpoint.password) {
|
|
57
|
+
throw new Error("credentials must not be embedded in an HTTPS object base URL");
|
|
58
|
+
}
|
|
59
|
+
if (endpoint.search || endpoint.hash) {
|
|
60
|
+
throw new Error("HTTPS object base URLs cannot contain a query or fragment");
|
|
61
|
+
}
|
|
62
|
+
if (!endpoint.hostname || /%(?:2f|5c)/i.test(endpoint.pathname)) {
|
|
63
|
+
throw new Error("HTTPS object base URL contains an ambiguous path");
|
|
64
|
+
}
|
|
65
|
+
endpoint.pathname = endpoint.pathname.replace(/\/+$/, "");
|
|
66
|
+
return endpoint;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function httpsObjectUrl(baseUrl, snapshotDigest) {
|
|
70
|
+
if (!DIGEST_RE.test(snapshotDigest || "")) throw new Error("snapshot digest is invalid");
|
|
71
|
+
const endpoint = validateHttpsObjectBase(baseUrl);
|
|
72
|
+
endpoint.pathname = `${endpoint.pathname}/objects/${snapshotDigest}.json`;
|
|
73
|
+
return endpoint.toString();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function upload({
|
|
77
|
+
url, body, digest, token, timeoutMs, allowPrivateNetwork, lookup, request
|
|
78
|
+
}) {
|
|
79
|
+
const resolved = await within(
|
|
80
|
+
resolveHttpsEndpoint(url, { allowPrivateNetwork, lookup }),
|
|
81
|
+
timeoutMs,
|
|
82
|
+
"HTTPS object DNS resolution timed out"
|
|
83
|
+
);
|
|
84
|
+
const selected = resolved.addresses[0];
|
|
85
|
+
const headers = {
|
|
86
|
+
accept: "application/json",
|
|
87
|
+
"accept-encoding": "identity",
|
|
88
|
+
"content-length": String(body.length),
|
|
89
|
+
"content-type": "application/vnd.agentspine.snapshot+json",
|
|
90
|
+
"if-none-match": "*",
|
|
91
|
+
"user-agent": "AgentSpine/0.1 HTTPS object transport",
|
|
92
|
+
"x-agentspine-digest": `sha256:${digest}`
|
|
93
|
+
};
|
|
94
|
+
if (token) headers.authorization = `Bearer ${token}`;
|
|
95
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
96
|
+
let deadline;
|
|
97
|
+
let settled = false;
|
|
98
|
+
const reject = (error) => {
|
|
99
|
+
if (settled) return;
|
|
100
|
+
settled = true;
|
|
101
|
+
clearTimeout(deadline);
|
|
102
|
+
rejectPromise(error);
|
|
103
|
+
};
|
|
104
|
+
const resolve = (value) => {
|
|
105
|
+
if (settled) return;
|
|
106
|
+
settled = true;
|
|
107
|
+
clearTimeout(deadline);
|
|
108
|
+
resolvePromise(value);
|
|
109
|
+
};
|
|
110
|
+
const options = {
|
|
111
|
+
protocol: "https:", hostname: resolved.lookupHostname,
|
|
112
|
+
port: resolved.endpoint.port || 443,
|
|
113
|
+
path: resolved.endpoint.pathname, method: "PUT", headers,
|
|
114
|
+
agent: false, autoSelectFamily: false,
|
|
115
|
+
servername: isIP(resolved.lookupHostname) ? undefined : resolved.lookupHostname,
|
|
116
|
+
lookup: (_hostname, lookupOptions, callback) => {
|
|
117
|
+
const family = typeof lookupOptions === "object" && lookupOptions.family
|
|
118
|
+
? Number(lookupOptions.family) : 0;
|
|
119
|
+
const usable = family
|
|
120
|
+
? resolved.addresses.find((item) => item.family === family)
|
|
121
|
+
: selected;
|
|
122
|
+
if (!usable) return callback(new Error("no vetted DNS address matches the requested family"));
|
|
123
|
+
return callback(null, usable.address, usable.family);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const active = request(options, (response) => {
|
|
127
|
+
const status = Number(response.statusCode || 0);
|
|
128
|
+
if (status >= 300 && status < 400 && status !== 412) {
|
|
129
|
+
response.resume();
|
|
130
|
+
reject(new Error("HTTPS object redirects are not followed"));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const chunks = [];
|
|
134
|
+
let bytes = 0;
|
|
135
|
+
response.on("data", (chunk) => {
|
|
136
|
+
bytes += chunk.length;
|
|
137
|
+
if (bytes > MAX_RESPONSE_BYTES) {
|
|
138
|
+
active.destroy(new Error("HTTPS object response exceeds the 16 KiB limit"));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
chunks.push(chunk);
|
|
142
|
+
});
|
|
143
|
+
response.on("end", () => {
|
|
144
|
+
if (![201, 204, 412].includes(status)) {
|
|
145
|
+
reject(new Error(`HTTPS object publish failed with status ${status}`));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
resolve({ status, responseBytes: bytes });
|
|
149
|
+
});
|
|
150
|
+
response.on("error", reject);
|
|
151
|
+
});
|
|
152
|
+
active.setTimeout(timeoutMs, () => active.destroy(new Error("HTTPS object publish timed out")));
|
|
153
|
+
deadline = setTimeout(() => active.destroy(new Error("HTTPS object publish timed out")), timeoutMs);
|
|
154
|
+
active.on("error", reject);
|
|
155
|
+
active.end(body);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function putHttpsSnapshot({
|
|
160
|
+
baseUrl, snapshot, tokenEnv = null, timeoutMs = 10000,
|
|
161
|
+
allowPrivateNetwork = false, confirmation = null,
|
|
162
|
+
environment = process.env, lookup = dnsLookup, request = httpsRequest
|
|
163
|
+
}) {
|
|
164
|
+
if (confirmation !== CONFIRMATION) {
|
|
165
|
+
throw new Error("HTTPS object publishing requires explicit local owner confirmation");
|
|
166
|
+
}
|
|
167
|
+
timeout(timeoutMs);
|
|
168
|
+
const validated = validateHttpsSnapshot(structuredClone(snapshot));
|
|
169
|
+
const objectUrl = httpsObjectUrl(baseUrl, validated.digest);
|
|
170
|
+
const token = bearerToken(tokenEnv, environment);
|
|
171
|
+
const body = Buffer.from(JSON.stringify(validated));
|
|
172
|
+
const result = await upload({
|
|
173
|
+
url: objectUrl, body, digest: validated.digest, token, timeoutMs,
|
|
174
|
+
allowPrivateNetwork, lookup, request
|
|
175
|
+
});
|
|
176
|
+
const fetched = await fetchHttpsSnapshot({
|
|
177
|
+
url: objectUrl, tokenEnv, timeoutMs, allowPrivateNetwork,
|
|
178
|
+
confirmation, environment, lookup, request
|
|
179
|
+
});
|
|
180
|
+
if (fetched.snapshot.digest !== validated.digest) {
|
|
181
|
+
throw new Error("HTTPS object read-back does not match the published snapshot");
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
created: result.status !== 412,
|
|
185
|
+
alreadyExisted: result.status === 412,
|
|
186
|
+
verified: true,
|
|
187
|
+
objectUrl,
|
|
188
|
+
snapshotId: validated.snapshotId,
|
|
189
|
+
snapshotDigest: validated.digest,
|
|
190
|
+
scopeId: validated.scopeId,
|
|
191
|
+
adapterId: validated.adapterId,
|
|
192
|
+
events: validated.events.length,
|
|
193
|
+
authenticatedWrite: Boolean(token),
|
|
194
|
+
authority: "context-only"
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export async function publishHttpsSnapshot({
|
|
199
|
+
root = process.cwd(), directory, baseUrl, snapshotId, now = new Date(), ...options
|
|
200
|
+
}) {
|
|
201
|
+
if (options.confirmation !== CONFIRMATION) {
|
|
202
|
+
throw new Error("HTTPS object publishing requires explicit local owner confirmation");
|
|
203
|
+
}
|
|
204
|
+
const snapshot = await buildHttpsSnapshot({ root, directory, snapshotId, now });
|
|
205
|
+
return putHttpsSnapshot({ baseUrl, snapshot, ...options });
|
|
206
|
+
}
|