superbee 0.3.0-pre.6 → 0.4.0-pre.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/superbee.mjs +854 -158
- package/package.json +1 -1
- package/references/hosted-checkout.md +21 -6
package/dist/superbee.mjs
CHANGED
|
@@ -44,7 +44,7 @@ var __toESM = (mod, isNodeMode, target2) => (target2 = mod != null ? __create(__
|
|
|
44
44
|
var define_SUPERBEE_BUILD_IDENTITY_default;
|
|
45
45
|
var init_define_SUPERBEE_BUILD_IDENTITY = __esm({
|
|
46
46
|
"<define:__SUPERBEE_BUILD_IDENTITY__>"() {
|
|
47
|
-
define_SUPERBEE_BUILD_IDENTITY_default = { schema: "superbee.build-identity.v1", package: { name: "superbee", version: "0.
|
|
47
|
+
define_SUPERBEE_BUILD_IDENTITY_default = { schema: "superbee.build-identity.v1", package: { name: "superbee", version: "0.4.0-pre.1" }, source: { commit: "2b1084b2af86558ac242ab74c14fa5b4f9f2dbb4", dirty: false }, artifact: { channel: "npm-package" }, compatibility_contracts: { skill: 1, hook: 1, mcp: 1 } };
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
|
|
@@ -9416,6 +9416,9 @@ var init_mutation = __esm({
|
|
|
9416
9416
|
});
|
|
9417
9417
|
|
|
9418
9418
|
// ../core/src/kinds.ts
|
|
9419
|
+
function isConventionId(id) {
|
|
9420
|
+
return id.startsWith(CONVENTIONS_PREFIX);
|
|
9421
|
+
}
|
|
9419
9422
|
function buildKindRegistry(docs, initialWarnings = [], options = {}) {
|
|
9420
9423
|
const accepted = /* @__PURE__ */ new Map();
|
|
9421
9424
|
const warnings = [...initialWarnings];
|
|
@@ -20551,6 +20554,18 @@ var init_reference = __esm({
|
|
|
20551
20554
|
import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
|
|
20552
20555
|
import { lstat as lstat2, readdir as readdir2, rm, stat as stat2, unlink as unlink2 } from "node:fs/promises";
|
|
20553
20556
|
import { join as join7 } from "node:path";
|
|
20557
|
+
function withDefinitionWrites(binding, stated) {
|
|
20558
|
+
const { definition_writes: _previous, ...rest } = binding;
|
|
20559
|
+
return stated === null ? rest : { ...rest, definition_writes: stated };
|
|
20560
|
+
}
|
|
20561
|
+
async function recordDefinitionWrites(home2, binding, stated) {
|
|
20562
|
+
const current = await readBinding(home2, binding.checkout_id);
|
|
20563
|
+
if (current === null) return binding;
|
|
20564
|
+
if ((current.definition_writes ?? null) === stated) return current;
|
|
20565
|
+
const next = withDefinitionWrites(current, stated);
|
|
20566
|
+
await writeBinding(home2, next);
|
|
20567
|
+
return next;
|
|
20568
|
+
}
|
|
20554
20569
|
function sameFolder(a, b) {
|
|
20555
20570
|
return a.dev === b.dev && a.ino === b.ino && (!a.birth || !b.birth || a.birth === b.birth);
|
|
20556
20571
|
}
|
|
@@ -20618,7 +20633,9 @@ async function writeBinding(home2, binding) {
|
|
|
20618
20633
|
}
|
|
20619
20634
|
async function readBinding(home2, checkoutId) {
|
|
20620
20635
|
const value = await readJson(home2, join7(checkoutDir(home2, checkoutId), "binding.json"));
|
|
20621
|
-
|
|
20636
|
+
if (!isBinding(value) || value.checkout_id !== checkoutId) return null;
|
|
20637
|
+
const stated = value.definition_writes;
|
|
20638
|
+
return stated === void 0 || stated === "allowed" || stated === "refused" ? value : { ...value, definition_writes: "refused" };
|
|
20622
20639
|
}
|
|
20623
20640
|
async function indexCheckoutPath(home2, binding) {
|
|
20624
20641
|
await writeUserStateFileAtomic0600(
|
|
@@ -24032,12 +24049,19 @@ function idleBounded(body, deadlineMs, abort) {
|
|
|
24032
24049
|
})
|
|
24033
24050
|
);
|
|
24034
24051
|
}
|
|
24052
|
+
function requestHeaders(credentials, extra, type) {
|
|
24053
|
+
const headers = new Headers(extra);
|
|
24054
|
+
for (const [name, value] of Object.entries(credentials)) headers.set(name, value);
|
|
24055
|
+
headers.set("Content-Type", type);
|
|
24056
|
+
headers.set("Accept", "application/json");
|
|
24057
|
+
return headers;
|
|
24058
|
+
}
|
|
24035
24059
|
function createFetchCarrier(options) {
|
|
24036
24060
|
const fetcher = options.fetch ?? fetch;
|
|
24037
24061
|
const deadlineMs = options.deadlineMs ?? 15e3;
|
|
24038
24062
|
const bindingHeader = options.bindingHeader ?? "X-Superbee-Checkout";
|
|
24039
24063
|
const base = new URL(options.baseUrl);
|
|
24040
|
-
async function send(path53,
|
|
24064
|
+
async function send(path53, body, signal, extra, deadline) {
|
|
24041
24065
|
if (!path53.startsWith("/")) throw new TypeError(`hosted route '${path53}' must be absolute`);
|
|
24042
24066
|
let credentials;
|
|
24043
24067
|
try {
|
|
@@ -24050,14 +24074,29 @@ function createFetchCarrier(options) {
|
|
|
24050
24074
|
return await fetcher(new URL(path53, base), {
|
|
24051
24075
|
method: "POST",
|
|
24052
24076
|
redirect: "error",
|
|
24053
|
-
headers:
|
|
24054
|
-
body:
|
|
24077
|
+
headers: requestHeaders(credentials, extra, body.type),
|
|
24078
|
+
body: body.content,
|
|
24055
24079
|
signal: AbortSignal.any([signal, deadline])
|
|
24056
24080
|
});
|
|
24057
24081
|
} catch (cause) {
|
|
24058
24082
|
throw new HostedCarrierError("unavailable", { cause });
|
|
24059
24083
|
}
|
|
24060
24084
|
}
|
|
24085
|
+
const jsonBody = (input) => ({ type: "application/json", content: JSON.stringify(input) });
|
|
24086
|
+
const owned = /* @__PURE__ */ new Set(["x-superbee-write-request", bindingHeader.toLowerCase(), RECREATE_HEADER.toLowerCase(), VIA_HEADER.toLowerCase(), ACCEPT_DELETES_HEADER.toLowerCase()]);
|
|
24087
|
+
async function answer2(path53, body, signal, extra, maximum) {
|
|
24088
|
+
if (!Number.isSafeInteger(maximum) || maximum <= 0) throw new HostedCarrierError("denied");
|
|
24089
|
+
const deadline = AbortSignal.timeout(deadlineMs);
|
|
24090
|
+
const response = await send(path53, body, signal, extra, deadline);
|
|
24091
|
+
let read;
|
|
24092
|
+
try {
|
|
24093
|
+
read = await readBounded(response, maximum);
|
|
24094
|
+
} catch (error51) {
|
|
24095
|
+
if (response.ok) throw error51 instanceof HostedCarrierError ? error51 : new HostedCarrierError("unavailable", { cause: error51 });
|
|
24096
|
+
read = void 0;
|
|
24097
|
+
}
|
|
24098
|
+
return { status: response.status, headers: response.headers, body: read };
|
|
24099
|
+
}
|
|
24061
24100
|
return {
|
|
24062
24101
|
async json(path53, input, signal, request2) {
|
|
24063
24102
|
if (request2.writeRequest !== void 0 && !WRITE_REQUEST.test(request2.writeRequest)) throw new HostedCarrierError("denied");
|
|
@@ -24071,23 +24110,26 @@ function createFetchCarrier(options) {
|
|
|
24071
24110
|
if (request2.recreate !== void 0) extra[RECREATE_HEADER] = request2.recreate;
|
|
24072
24111
|
if (request2.via !== void 0) extra[VIA_HEADER] = request2.via;
|
|
24073
24112
|
if (request2.acceptDeletes !== void 0) extra[ACCEPT_DELETES_HEADER] = String(request2.acceptDeletes);
|
|
24074
|
-
|
|
24075
|
-
|
|
24076
|
-
|
|
24077
|
-
|
|
24078
|
-
|
|
24079
|
-
|
|
24080
|
-
|
|
24081
|
-
|
|
24113
|
+
return answer2(path53, jsonBody(input), signal, extra, request2.maximum);
|
|
24114
|
+
},
|
|
24115
|
+
async bytes(path53, body, signal, request2) {
|
|
24116
|
+
if (!(body instanceof Uint8Array)) throw new HostedCarrierError("denied");
|
|
24117
|
+
if (request2.writeRequest !== void 0 && !WRITE_REQUEST.test(request2.writeRequest)) throw new HostedCarrierError("denied");
|
|
24118
|
+
const extra = {};
|
|
24119
|
+
for (const [name, value] of Object.entries(request2.headers ?? {})) {
|
|
24120
|
+
const lower = name.toLowerCase();
|
|
24121
|
+
if (!ROUTE_HEADER.test(lower) || owned.has(lower) || lower in extra || typeof value !== "string" || !ROUTE_HEADER_VALUE.test(value)) throw new HostedCarrierError("denied");
|
|
24122
|
+
extra[lower] = value;
|
|
24082
24123
|
}
|
|
24083
|
-
|
|
24124
|
+
if (request2.writeRequest !== void 0) extra["X-Superbee-Write-Request"] = request2.writeRequest;
|
|
24125
|
+
return answer2(path53, { type: "application/octet-stream", content: body }, signal, extra, request2.maximum);
|
|
24084
24126
|
},
|
|
24085
24127
|
async stream(path53, input, signal) {
|
|
24086
24128
|
const idle = new AbortController();
|
|
24087
24129
|
const firstByte = setTimeout(() => idle.abort(), deadlineMs);
|
|
24088
24130
|
let response;
|
|
24089
24131
|
try {
|
|
24090
|
-
response = await send(path53, input, signal, {}, idle.signal);
|
|
24132
|
+
response = await send(path53, jsonBody(input), signal, {}, idle.signal);
|
|
24091
24133
|
} finally {
|
|
24092
24134
|
clearTimeout(firstByte);
|
|
24093
24135
|
}
|
|
@@ -24099,7 +24141,7 @@ function createFetchCarrier(options) {
|
|
|
24099
24141
|
}
|
|
24100
24142
|
};
|
|
24101
24143
|
}
|
|
24102
|
-
var ACCEPT_DELETES_HEADER, MAXIMUM_ACCEPTED_DELETIONS, RECREATE_HEADER, VIA_HEADER, HostedAnswerTooLarge, HostedCarrierError, WRITE_REQUEST, BINDING;
|
|
24144
|
+
var ACCEPT_DELETES_HEADER, MAXIMUM_ACCEPTED_DELETIONS, RECREATE_HEADER, VIA_HEADER, HostedAnswerTooLarge, HostedCarrierError, WRITE_REQUEST, BINDING, ROUTE_HEADER, ROUTE_HEADER_VALUE;
|
|
24103
24145
|
var init_carrier = __esm({
|
|
24104
24146
|
"../core/src/hosted-transport/carrier.ts"() {
|
|
24105
24147
|
"use strict";
|
|
@@ -24127,10 +24169,43 @@ var init_carrier = __esm({
|
|
|
24127
24169
|
};
|
|
24128
24170
|
WRITE_REQUEST = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
24129
24171
|
BINDING = /^sha256:[a-f0-9]{64}$/;
|
|
24172
|
+
ROUTE_HEADER = /^x-superbee-[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
24173
|
+
ROUTE_HEADER_VALUE = /^[\x20-\x7e]{0,1024}$/;
|
|
24130
24174
|
}
|
|
24131
24175
|
});
|
|
24132
24176
|
|
|
24133
24177
|
// ../core/src/hosted-transport/answer-rows.ts
|
|
24178
|
+
function definitionFindingsText(details) {
|
|
24179
|
+
const findings = isRecord7(details) && Array.isArray(details.findings) ? details.findings : [];
|
|
24180
|
+
const clauses = [];
|
|
24181
|
+
for (const finding of findings) {
|
|
24182
|
+
if (!isRecord7(finding)) continue;
|
|
24183
|
+
const rule = findingText(finding.rule);
|
|
24184
|
+
if (!rule) continue;
|
|
24185
|
+
const type = findingText(finding.type);
|
|
24186
|
+
const conventionId = findingText(finding.conventionId);
|
|
24187
|
+
const field = findingText(finding.field);
|
|
24188
|
+
const detail = findingText(finding.detail);
|
|
24189
|
+
const where = [type ? `Kind '${type}'` : conventionId ? `'${conventionId}'` : "", field ? `field '${field}'` : ""].filter(Boolean).join(" ");
|
|
24190
|
+
const instances = isRecord7(finding.instances) ? finding.instances : void 0;
|
|
24191
|
+
const total = instances && Number.isSafeInteger(instances.count) && instances.count > 0 ? instances.count : 0;
|
|
24192
|
+
const ids = instances && Array.isArray(instances.ids) ? instances.ids.map(findingText).filter((id) => id !== void 0) : [];
|
|
24193
|
+
const shown2 = ids.slice(0, FINDING_IDS_SHOWN);
|
|
24194
|
+
const documents = total > 0 ? `: ${total} document${total === 1 ? "" : "s"}${shown2.length > 0 ? `, ${shown2.join(", ")}${total > shown2.length ? ", ..." : ""}` : ""}` : "";
|
|
24195
|
+
clauses.push(`${rule}${where ? ` on ${where}` : ""}${detail ? ` (${detail})` : ""}${documents}`);
|
|
24196
|
+
}
|
|
24197
|
+
const more = isRecord7(details) && details.truncated === true;
|
|
24198
|
+
let text4 = "";
|
|
24199
|
+
for (const [index2, clause] of clauses.entries()) {
|
|
24200
|
+
const next = text4 === "" ? clause : `${text4}; ${clause}`;
|
|
24201
|
+
const rest = clauses.length - index2;
|
|
24202
|
+
if (new TextEncoder().encode(next).byteLength > DEFINITION_FINDINGS_TEXT_BYTES - 32) {
|
|
24203
|
+
return text4 === "" ? `${clause.slice(0, 512)}...${rest > 1 ? `; and ${rest - 1} more` : ""}` : `${text4}; and ${rest} more`;
|
|
24204
|
+
}
|
|
24205
|
+
text4 = next;
|
|
24206
|
+
}
|
|
24207
|
+
return more && text4 !== "" ? `${text4}; and more` : text4;
|
|
24208
|
+
}
|
|
24134
24209
|
function parseWriteResult(raw, expected) {
|
|
24135
24210
|
const refuse = () => new HostedAnswerError("the write result envelope is not one the client admits");
|
|
24136
24211
|
if (!isRecord7(raw) || !onlyKeys(raw, ["ok", "operationId", "data", "error"]) || typeof raw.operationId !== "string" || !expected.operationIds.includes(raw.operationId)) throw refuse();
|
|
@@ -24144,7 +24219,8 @@ function parseWriteResult(raw, expected) {
|
|
|
24144
24219
|
}
|
|
24145
24220
|
if (raw.ok !== false) throw refuse();
|
|
24146
24221
|
const error51 = raw.error;
|
|
24147
|
-
if (raw.data !== void 0 || !isRecord7(error51) || !onlyKeys(error51, ERROR_KEYS) || !WRITE_ERROR_CODES.includes(error51.code) || typeof error51.message !== "string" || error51.retryable !== false || error51.writeState !== "not_applied" && error51.writeState !== "unknown" || error51.currentVersion !== void 0 && !isContentVersion(error51.currentVersion) || error51.code === "write_outcome_unknown" && error51.writeState !== "unknown" || (error51.code === "request_capacity" ? error51.scope !== "principal" && error51.scope !== "bundle" || error51.writeState !== "not_applied" || error51.resetAt !== void 0 && (typeof error51.resetAt !== "string" || !Number.isFinite(Date.parse(error51.resetAt))) : error51.scope !== void 0 || error51.resetAt !== void 0) || (error51.code === "deletions_held" ? operationId !== DELETE_OPERATION_ID || error51.writeState !== "not_applied" || !count2(error51.deletions) || !count2(error51.baseline) : error51.deletions !== void 0 || error51.baseline !== void 0)
|
|
24222
|
+
if (raw.data !== void 0 || !isRecord7(error51) || !onlyKeys(error51, ERROR_KEYS) || !WRITE_ERROR_CODES.includes(error51.code) || typeof error51.message !== "string" || error51.retryable !== false || error51.writeState !== "not_applied" && error51.writeState !== "unknown" || error51.currentVersion !== void 0 && !isContentVersion(error51.currentVersion) || error51.code === "write_outcome_unknown" && error51.writeState !== "unknown" || (error51.code === "request_capacity" ? error51.scope !== "principal" && error51.scope !== "bundle" || error51.writeState !== "not_applied" || error51.resetAt !== void 0 && (typeof error51.resetAt !== "string" || !Number.isFinite(Date.parse(error51.resetAt))) : error51.scope !== void 0 || error51.resetAt !== void 0) || (error51.code === "deletions_held" ? operationId !== DELETE_OPERATION_ID || error51.writeState !== "not_applied" || !count2(error51.deletions) || !count2(error51.baseline) : error51.deletions !== void 0 || error51.baseline !== void 0) || // A model-change refusal says nothing was applied; its findings ride on it alone.
|
|
24223
|
+
error51.code === "definition_incompatible" && error51.writeState !== "not_applied" || error51.definitionDetails !== void 0 && error51.code !== "definition_incompatible") throw refuse();
|
|
24148
24224
|
return { ok: false, operationId, error: { ...error51 } };
|
|
24149
24225
|
}
|
|
24150
24226
|
function updateRow(answer2) {
|
|
@@ -24234,13 +24310,14 @@ function decodeOutcomeAnswer(value, expected) {
|
|
|
24234
24310
|
throw malformed4();
|
|
24235
24311
|
}
|
|
24236
24312
|
}
|
|
24237
|
-
var CAPACITY_REFUSAL_CODES, DELETIONS_HELD_REFUSAL_CODE, UPDATE_ANSWER_ROWS, OUTCOME_ANSWER_ROWS, READ_ANSWER_ROWS, WRITE_ERROR_CODES, DELETE_OPERATION_ID, HostedAnswerError, isRecord7, onlyKeys, DATA_KEYS, DELETE_DATA_KEYS, ERROR_KEYS, count2, BEFORE_DISPATCH, rowsByAnswer, HostedOutcomeError, OUTCOME_CONTENT_BYTES;
|
|
24313
|
+
var CAPACITY_REFUSAL_CODES, DELETIONS_HELD_REFUSAL_CODE, UPDATE_ANSWER_ROWS, OUTCOME_ANSWER_ROWS, READ_ANSWER_ROWS, WRITE_ERROR_CODES, DELETE_OPERATION_ID, FINDING_IDS_SHOWN, DEFINITION_FINDINGS_TEXT_BYTES, findingText, HostedAnswerError, isRecord7, onlyKeys, DATA_KEYS, DELETE_DATA_KEYS, ERROR_KEYS, count2, BEFORE_DISPATCH, rowsByAnswer, HostedOutcomeError, OUTCOME_CONTENT_BYTES;
|
|
24238
24314
|
var init_answer_rows = __esm({
|
|
24239
24315
|
"../core/src/hosted-transport/answer-rows.ts"() {
|
|
24240
24316
|
"use strict";
|
|
24241
24317
|
init_define_SUPERBEE_BUILD_IDENTITY();
|
|
24242
24318
|
init_define_SUPERBEE_UPDATE_POLICY();
|
|
24243
24319
|
init_version_transport();
|
|
24320
|
+
init_host_text();
|
|
24244
24321
|
CAPACITY_REFUSAL_CODES = Object.freeze({ principal: "REQUEST_CAPACITY_PRINCIPAL", bundle: "REQUEST_CAPACITY_BUNDLE" });
|
|
24245
24322
|
DELETIONS_HELD_REFUSAL_CODE = "DELETIONS_HELD";
|
|
24246
24323
|
UPDATE_ANSWER_ROWS = Object.freeze([
|
|
@@ -24261,6 +24338,8 @@ var init_answer_rows = __esm({
|
|
|
24261
24338
|
// The remaining not_applied codes of the write schema: definitive once recorded, like the transient ones.
|
|
24262
24339
|
{ answer: "200 result_too_large", recorded: "settled-only", outcome: "refused" },
|
|
24263
24340
|
{ answer: "200 field_action_refused", recorded: "settled-only", outcome: "refused" },
|
|
24341
|
+
// A model change the host's compatibility check refused (designs/hosted-model-evolution.md 4.6).
|
|
24342
|
+
{ answer: "200 definition_incompatible", recorded: "settled-only", outcome: "refused" },
|
|
24264
24343
|
{ answer: "200 document_exists", recorded: "settled-only", outcome: "refused" },
|
|
24265
24344
|
{ answer: "200 candidate_unavailable", recorded: "settled-only", outcome: "refused" },
|
|
24266
24345
|
{ answer: "200 candidate_recovery_unavailable", recorded: "settled-only", outcome: "refused" },
|
|
@@ -24337,9 +24416,17 @@ var init_answer_rows = __esm({
|
|
|
24337
24416
|
"document_id_not_canonical",
|
|
24338
24417
|
"document_id_collision",
|
|
24339
24418
|
"request_capacity",
|
|
24340
|
-
"deletions_held"
|
|
24419
|
+
"deletions_held",
|
|
24420
|
+
"definition_incompatible"
|
|
24341
24421
|
]);
|
|
24342
24422
|
DELETE_OPERATION_ID = "documents.delete.v1";
|
|
24423
|
+
FINDING_IDS_SHOWN = 5;
|
|
24424
|
+
DEFINITION_FINDINGS_TEXT_BYTES = 2048;
|
|
24425
|
+
findingText = (value) => {
|
|
24426
|
+
if (typeof value !== "string" || value.length > 256) return void 0;
|
|
24427
|
+
const text4 = stripHostText(value, 256);
|
|
24428
|
+
return text4.length > 0 ? text4 : void 0;
|
|
24429
|
+
};
|
|
24343
24430
|
HostedAnswerError = class extends Error {
|
|
24344
24431
|
name = "HostedAnswerError";
|
|
24345
24432
|
};
|
|
@@ -24347,7 +24434,7 @@ var init_answer_rows = __esm({
|
|
|
24347
24434
|
onlyKeys = (value, allowed) => Object.keys(value).every((key) => allowed.includes(key));
|
|
24348
24435
|
DATA_KEYS = ["bundleId", "documentId", "version", "changed", "scope"];
|
|
24349
24436
|
DELETE_DATA_KEYS = [...DATA_KEYS, "deletedVersion", "deleted"];
|
|
24350
|
-
ERROR_KEYS = ["code", "message", "retryable", "writeState", "currentVersion", "diagnostics", "fieldActionDetails", "candidate", "retentionUnavailable", "scope", "resetAt", "deletions", "baseline"];
|
|
24437
|
+
ERROR_KEYS = ["code", "message", "retryable", "writeState", "currentVersion", "diagnostics", "fieldActionDetails", "definitionDetails", "candidate", "retentionUnavailable", "scope", "resetAt", "deletions", "baseline"];
|
|
24351
24438
|
count2 = (value) => Number.isSafeInteger(value) && value >= 1;
|
|
24352
24439
|
BEFORE_DISPATCH = /* @__PURE__ */ new Map([
|
|
24353
24440
|
[429, { code: "request_capacity", lenient: true }],
|
|
@@ -24594,7 +24681,8 @@ function decodeHostedCapabilities(value) {
|
|
|
24594
24681
|
bound: Object.freeze({ documents: bound.documents, bytes: bound.bytes }),
|
|
24595
24682
|
paged,
|
|
24596
24683
|
root,
|
|
24597
|
-
operationsRetentionMs: retention ?? DEFAULT_OPERATIONS_RETENTION_MS
|
|
24684
|
+
operationsRetentionMs: retention ?? DEFAULT_OPERATIONS_RETENTION_MS,
|
|
24685
|
+
definitionWrites: body.definitionWrites === void 0 ? null : body.definitionWrites === "allowed" ? "allowed" : "refused"
|
|
24598
24686
|
});
|
|
24599
24687
|
}
|
|
24600
24688
|
function boundedLines(body, maximum) {
|
|
@@ -25034,7 +25122,9 @@ function createWholeDocumentTransport(options) {
|
|
|
25034
25122
|
if (error51.code === "version_conflict" && intent.base === null) return servedHead(intent, "conflict", error51.currentVersion);
|
|
25035
25123
|
if (error51.code === "version_conflict") return error51.currentVersion === void 0 ? servedHead(intent) : { kind: "conflict", actual: error51.currentVersion };
|
|
25036
25124
|
const row3 = UPDATE_ANSWER_ROWS.find((candidate) => candidate.answer === `200 ${error51.code}`) ?? updateRow("200 other");
|
|
25037
|
-
|
|
25125
|
+
if (row3.code) return denial(row3.code, error51.message);
|
|
25126
|
+
const findings = error51.code === "definition_incompatible" ? definitionFindingsText(error51.definitionDetails) : "";
|
|
25127
|
+
return { kind: "refused", code: error51.code, message: findings ? `${error51.message} Findings: ${findings}.` : error51.message };
|
|
25038
25128
|
}
|
|
25039
25129
|
function prepare(intent) {
|
|
25040
25130
|
const request2 = wholeDocumentRequest(bundleId, intent, options.okfVersion);
|
|
@@ -30277,9 +30367,17 @@ function qualifyingCarrier(carrier, prefix, slug) {
|
|
|
30277
30367
|
if (bundleId.includes("/")) throw new TypeError("a qualified reference sent through a qualifying client");
|
|
30278
30368
|
return { ...input, bundleId: hostedBundleReferenceText({ slug, bundleId }) };
|
|
30279
30369
|
};
|
|
30370
|
+
const bytes = carrier.bytes?.bind(carrier);
|
|
30280
30371
|
return {
|
|
30281
30372
|
json: async (route, input, signal, options) => carrier.json(route, qualify(route, input), signal, options),
|
|
30282
|
-
stream: async (route, input, signal) => carrier.stream(route, qualify(route, input), signal)
|
|
30373
|
+
stream: async (route, input, signal) => carrier.stream(route, qualify(route, input), signal),
|
|
30374
|
+
// A raw body names no bundle in a body; only unscoped routes carry one.
|
|
30375
|
+
...bytes ? {
|
|
30376
|
+
bytes: async (route, body, signal, options) => {
|
|
30377
|
+
if (scoped(route)) throw new TypeError(`a raw body sent to the bundle-scoped route ${route} through a qualifying client`);
|
|
30378
|
+
return bytes(route, body, signal, options);
|
|
30379
|
+
}
|
|
30380
|
+
} : {}
|
|
30283
30381
|
};
|
|
30284
30382
|
}
|
|
30285
30383
|
function createHostedSyncClient(options) {
|
|
@@ -30480,7 +30578,15 @@ var init_client = __esm({
|
|
|
30480
30578
|
BUNDLES_BYTES = 256 * 1024;
|
|
30481
30579
|
AGENT_AUDIENCE_PATH = /^\/agents\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/mcp$/;
|
|
30482
30580
|
BUNDLE_LIST_CAP = 100;
|
|
30483
|
-
UNSCOPED_ROUTES = Object.freeze([
|
|
30581
|
+
UNSCOPED_ROUTES = Object.freeze([
|
|
30582
|
+
"whoami",
|
|
30583
|
+
"bundles",
|
|
30584
|
+
"bundle-create",
|
|
30585
|
+
"bundle-create-begin",
|
|
30586
|
+
"bundle-create-stage",
|
|
30587
|
+
"bundle-create-blob",
|
|
30588
|
+
"bundle-create-commit"
|
|
30589
|
+
]);
|
|
30484
30590
|
}
|
|
30485
30591
|
});
|
|
30486
30592
|
|
|
@@ -30488,12 +30594,31 @@ var init_client = __esm({
|
|
|
30488
30594
|
import { createHash as createHash10 } from "node:crypto";
|
|
30489
30595
|
import { promises as fs11 } from "node:fs";
|
|
30490
30596
|
import path29 from "node:path";
|
|
30491
|
-
function
|
|
30597
|
+
function underFolded(rel, folder) {
|
|
30598
|
+
const folded = foldedPath(rel);
|
|
30599
|
+
return folded === folder || folded.startsWith(`${folder}/`);
|
|
30600
|
+
}
|
|
30601
|
+
function heldPathReason(rel, options = {}) {
|
|
30492
30602
|
if (isReservedFile(rel)) return "reserved_file";
|
|
30493
30603
|
if (!rel.endsWith(".md")) return "not_a_document";
|
|
30494
|
-
if (
|
|
30604
|
+
if (HELD_FOLDERS.some((folder) => underFolded(rel, folder))) return "convention_folder";
|
|
30605
|
+
if (underFolded(rel, "conventions") && !(options.definitionWrites === "allowed" && isConventionId(rel))) return "convention_folder";
|
|
30495
30606
|
return null;
|
|
30496
30607
|
}
|
|
30608
|
+
function heldTypeReason(rel, type, definitionWrites) {
|
|
30609
|
+
if (type === "View") return `'${conceptIdFromPath(rel)}' is a View registration, which the host installs; it does not sync`;
|
|
30610
|
+
if (type === CONVENTION_TYPE && !(definitionWrites === "allowed" && isConventionId(rel))) return `'${conceptIdFromPath(rel)}' is a Kind convention outside conventions/; the host reads Kinds only from conventions/`;
|
|
30611
|
+
if (definitionWrites === "allowed" && isConventionId(rel) && type !== CONVENTION_TYPE) return `'${conceptIdFromPath(rel)}' is under conventions/ but is not a Convention; only Kind conventions sync there`;
|
|
30612
|
+
return null;
|
|
30613
|
+
}
|
|
30614
|
+
function heldPathMessage(rel, definitionWrites = null, sender = "sync") {
|
|
30615
|
+
if (underFolded(rel, "views-registry")) return `${rel} is under views-registry/, which holds the bundle's View registrations; they are installed on the host and do not sync`;
|
|
30616
|
+
if (underFolded(rel, "conventions") && definitionWrites === "refused") return `${rel} is under conventions/, which holds the bundle's model: ${DEFINITIONS_REFUSED.why}; ${DEFINITIONS_REFUSED.instead}`;
|
|
30617
|
+
if (underFolded(rel, "conventions") && definitionWrites === "allowed") {
|
|
30618
|
+
return sender === "app" ? `${rel} is under conventions/, which holds the bundle's model; change it with the kind and recipe commands` : `${rel} is not under conventions/ as spelled; the bundle's Kinds sync only from the folder named exactly conventions/`;
|
|
30619
|
+
}
|
|
30620
|
+
return `${rel} is under ${rel.split("/")[0]}/, which holds conventions edited in the Superbee app`;
|
|
30621
|
+
}
|
|
30497
30622
|
async function readDeletionWindow(store) {
|
|
30498
30623
|
const raw = await store.readMeta(DELETION_WINDOW_KEY);
|
|
30499
30624
|
const hold = raw?.hold && Array.isArray(raw.hold.ids) && typeof raw.hold.token === "string" ? { ids: raw.hold.ids.filter((id) => typeof id === "string"), token: raw.hold.token } : null;
|
|
@@ -30700,7 +30825,10 @@ function foldedPath(id) {
|
|
|
30700
30825
|
function held(id, rel, reason, message) {
|
|
30701
30826
|
return { id, path: rel, reason, message };
|
|
30702
30827
|
}
|
|
30703
|
-
function unsendable(id, rel, bytes, stored, context) {
|
|
30828
|
+
function unsendable(id, rel, bytes, stored, context, fence) {
|
|
30829
|
+
if (fence && heldPathReason(rel, { definitionWrites: fence.definitionWrites }) === "convention_folder") {
|
|
30830
|
+
return held(id, rel, "convention_folder", heldPathMessage(rel, fence.definitionWrites, fence.sender));
|
|
30831
|
+
}
|
|
30704
30832
|
if (bytes.byteLength > WHOLE_DOCUMENT_BOUNDS.payloadBytes) {
|
|
30705
30833
|
return held(id, rel, "too_large", `'${id}' is over the ${WHOLE_DOCUMENT_BOUNDS.payloadBytes / 1024} KiB a sync write carries`);
|
|
30706
30834
|
}
|
|
@@ -30721,6 +30849,8 @@ function unsendable(id, rel, bytes, stored, context) {
|
|
|
30721
30849
|
if (Buffer.byteLength(JSON.stringify(frontmatter)) > FRONTMATTER_JSON_BYTES || Object.keys(frontmatter).length > FRONTMATTER_KEY_LIMIT) {
|
|
30722
30850
|
return held(id, rel, "too_large", `'${id}' has more frontmatter than the host accepts (${FRONTMATTER_JSON_BYTES / 1024} KiB, ${FRONTMATTER_KEY_LIMIT} fields)`);
|
|
30723
30851
|
}
|
|
30852
|
+
const typed = fence ? heldTypeReason(rel, frontmatter.type, fence.definitionWrites) : null;
|
|
30853
|
+
if (typed !== null) return held(id, rel, "not_sendable", typed);
|
|
30724
30854
|
const storedType = stored?.frontmatter.type;
|
|
30725
30855
|
if (typeof storedType === "string" && storedType !== frontmatter.type) {
|
|
30726
30856
|
return held(id, rel, "type_change", `'${id}' changes type from '${storedType}' to '${String(frontmatter.type)}', which sync cannot send`);
|
|
@@ -30783,11 +30913,7 @@ async function scanCheckout(context) {
|
|
|
30783
30913
|
continue;
|
|
30784
30914
|
}
|
|
30785
30915
|
}
|
|
30786
|
-
|
|
30787
|
-
report.held.push(held(id, rel, "convention_folder", `${rel} is under ${rel.split("/")[0]}/, which holds conventions edited in the Superbee app`));
|
|
30788
|
-
continue;
|
|
30789
|
-
}
|
|
30790
|
-
const refusal = unsendable(id, rel, bytes, stored.document?.doc ?? null, context);
|
|
30916
|
+
const refusal = unsendable(id, rel, bytes, stored.document?.doc ?? null, context, { definitionWrites: context.definitionWrites ?? null });
|
|
30791
30917
|
if (refusal) {
|
|
30792
30918
|
report.held.push(refusal);
|
|
30793
30919
|
continue;
|
|
@@ -30824,6 +30950,14 @@ async function scanCheckout(context) {
|
|
|
30824
30950
|
delete projection.files[id];
|
|
30825
30951
|
continue;
|
|
30826
30952
|
}
|
|
30953
|
+
const definitionWrites = context.definitionWrites ?? null;
|
|
30954
|
+
const refusedPath = heldPathReason(rel, { definitionWrites }) === "convention_folder";
|
|
30955
|
+
const refusedType = refusedPath ? null : heldTypeReason(rel, stored.document.doc.frontmatter.type, definitionWrites);
|
|
30956
|
+
if (refusedPath || refusedType !== null) {
|
|
30957
|
+
delete projection.files[id];
|
|
30958
|
+
report.held.push(held(id, rel, refusedPath ? "convention_folder" : "not_sendable", `${refusedPath ? heldPathMessage(rel, definitionWrites) : refusedType}; the host's version is placed back in the folder`));
|
|
30959
|
+
continue;
|
|
30960
|
+
}
|
|
30827
30961
|
if (stored.intents.some((row3) => row3.state === "conflict")) {
|
|
30828
30962
|
report.held.push(held(id, rel, "deleted_locally", `${rel} was deleted while '${id}' has a conflict to resolve; resolve it first`));
|
|
30829
30963
|
continue;
|
|
@@ -31114,7 +31248,7 @@ async function recoverPlacements(folder, projection) {
|
|
|
31114
31248
|
}
|
|
31115
31249
|
delete projection.discarded;
|
|
31116
31250
|
}
|
|
31117
|
-
var PROJECTION_FILE, PROJECTION_SCHEMA, PROJECTION_BYTES, FRONTMATTER_JSON_BYTES,
|
|
31251
|
+
var PROJECTION_FILE, PROJECTION_SCHEMA, PROJECTION_BYTES, FRONTMATTER_JSON_BYTES, HELD_FOLDERS, DEFINITIONS_REFUSED, MIN_HELD_DELETIONS, DELETION_WINDOW_MS, DELETION_WINDOW_KEY, UTF8;
|
|
31118
31252
|
var init_sync_scan = __esm({
|
|
31119
31253
|
"../cli/src/hosted/sync-scan.ts"() {
|
|
31120
31254
|
"use strict";
|
|
@@ -31132,7 +31266,11 @@ var init_sync_scan = __esm({
|
|
|
31132
31266
|
PROJECTION_SCHEMA = 2;
|
|
31133
31267
|
PROJECTION_BYTES = 8 * 1024 * 1024;
|
|
31134
31268
|
FRONTMATTER_JSON_BYTES = 16 * 1024;
|
|
31135
|
-
|
|
31269
|
+
HELD_FOLDERS = ["views", "views-registry"];
|
|
31270
|
+
DEFINITIONS_REFUSED = Object.freeze({
|
|
31271
|
+
why: "you don't have permission to change this bundle's model",
|
|
31272
|
+
instead: "ask whoever manages access to it"
|
|
31273
|
+
});
|
|
31136
31274
|
MIN_HELD_DELETIONS = 3;
|
|
31137
31275
|
DELETION_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
31138
31276
|
DELETION_WINDOW_KEY = "cli-deletion-window";
|
|
@@ -31196,6 +31334,13 @@ function hostedCheckoutRefusal(row3, words, binding, extra = {}) {
|
|
|
31196
31334
|
help: "choose another folder for a local bundle"
|
|
31197
31335
|
});
|
|
31198
31336
|
}
|
|
31337
|
+
if (row3.definitions && binding.definition_writes === "refused") {
|
|
31338
|
+
const { why, instead } = DEFINITIONS_REFUSED;
|
|
31339
|
+
return new CliError("FORBIDDEN", `'${words}' refused: ${why} (bundle ${binding.bundle_id} on ${binding.origin}); ${instead}`, {
|
|
31340
|
+
details: { ...details, reason: "definitions_refused" },
|
|
31341
|
+
help: instead
|
|
31342
|
+
});
|
|
31343
|
+
}
|
|
31199
31344
|
if (row3.instead) {
|
|
31200
31345
|
return new CliError("FORBIDDEN", `'${words}' cannot sync from a hosted checkout (${row3.why}): ${row3.instead}`, { details, help: row3.instead });
|
|
31201
31346
|
}
|
|
@@ -31211,9 +31356,23 @@ async function assertAllowedInHostedCheckout(command, args, context = {}) {
|
|
|
31211
31356
|
if (skip) return;
|
|
31212
31357
|
const binding = await checkoutFor(command, dir, context.home ?? homedir12(), context.cwd ?? process.cwd());
|
|
31213
31358
|
if (!binding) return;
|
|
31359
|
+
if (row3.definitions && binding.definition_writes === "allowed") return;
|
|
31214
31360
|
const sub = row3.words.length > 1 ? args.find((token) => !token.startsWith("-")) : void 0;
|
|
31215
31361
|
throw hostedCheckoutRefusal(row3, sub ? `${command} ${sub}` : command, binding);
|
|
31216
31362
|
}
|
|
31363
|
+
async function assertKindOnlyRecipe(words, dir, recipe2, context = {}) {
|
|
31364
|
+
const artifacts = [...recipe2.docs.map((doc2) => doc2.id), ...recipe2.pages.flatMap((page) => [page.registry.id, page.entry]), ...recipe2.references.map((reference) => reference.doc.id)];
|
|
31365
|
+
const outside = artifacts.filter((id) => !isConventionId(id));
|
|
31366
|
+
if (outside.length === 0) return;
|
|
31367
|
+
const binding = await checkoutFor("recipe", dir, context.home ?? homedir12(), context.cwd ?? process.cwd());
|
|
31368
|
+
if (!binding) return;
|
|
31369
|
+
throw hostedCheckoutRefusal(
|
|
31370
|
+
{ words: words.split(" "), reason: "not_syncable", why: `recipe '${recipe2.id}' installs ${outside.length} artifact(s) outside conventions/, such as Views and References, and a checkout sends Kind conventions only`, instead: KINDS_INSTEAD },
|
|
31371
|
+
words,
|
|
31372
|
+
binding,
|
|
31373
|
+
{ recipe: recipe2.id, artifacts: outside.slice(0, 20).join(", "), artifacts_total: String(outside.length) }
|
|
31374
|
+
);
|
|
31375
|
+
}
|
|
31217
31376
|
function hostedHeldWriteRefusal(binding, held2) {
|
|
31218
31377
|
const instead = HELD_INSTEAD[held2.reason];
|
|
31219
31378
|
return hostedCheckoutRefusal(
|
|
@@ -31286,6 +31445,7 @@ var init_refusals = __esm({
|
|
|
31286
31445
|
"use strict";
|
|
31287
31446
|
init_define_SUPERBEE_BUILD_IDENTITY();
|
|
31288
31447
|
init_define_SUPERBEE_UPDATE_POLICY();
|
|
31448
|
+
init_src();
|
|
31289
31449
|
init_bundle2();
|
|
31290
31450
|
init_errors4();
|
|
31291
31451
|
init_command_text();
|
|
@@ -31297,20 +31457,20 @@ var init_refusals = __esm({
|
|
|
31297
31457
|
KINDS_INSTEAD = "to design Kinds, work in a local or Git bundle and publish it";
|
|
31298
31458
|
HOSTED_CHECKOUT_REFUSALS = Object.freeze([
|
|
31299
31459
|
{ words: ["doc", "verify"], reason: "not_syncable", why: "verification is a managed field the host records" },
|
|
31300
|
-
{ words: ["kind", "*"], reason: "not_syncable", why: "a hosted bundle's Kinds cannot be changed from a checkout", instead: KINDS_INSTEAD },
|
|
31301
|
-
{ words: ["recipe", "add"], reason: "not_syncable", why: "recipes change a hosted bundle's Kinds, which cannot be changed from a checkout", instead: KINDS_INSTEAD },
|
|
31302
|
-
{ words: ["recipe", "evolve"], reason: "not_syncable", why: "recipes change a hosted bundle's Kinds, which cannot be changed from a checkout", instead: KINDS_INSTEAD },
|
|
31460
|
+
{ words: ["kind", "*"], reason: "not_syncable", why: "a hosted bundle's Kinds cannot be changed from a checkout", instead: KINDS_INSTEAD, definitions: true },
|
|
31461
|
+
{ words: ["recipe", "add"], reason: "not_syncable", why: "recipes change a hosted bundle's Kinds, which cannot be changed from a checkout", instead: KINDS_INSTEAD, definitions: true },
|
|
31462
|
+
{ words: ["recipe", "evolve"], reason: "not_syncable", why: "recipes change a hosted bundle's Kinds, which cannot be changed from a checkout", instead: KINDS_INSTEAD, definitions: true },
|
|
31303
31463
|
{ words: ["artifact", "*"], reason: "not_syncable", why: "artifacts carry blobs, which do not sync" },
|
|
31304
31464
|
{
|
|
31305
31465
|
words: ["promote"],
|
|
31306
31466
|
reason: "not_syncable",
|
|
31307
|
-
why: "a key that is not a .md document is stored as a blob
|
|
31467
|
+
why: "a key that is not a .md document is stored as a blob; blobs, reserved files and View files do not sync, and conventions change only through the kind and recipe commands",
|
|
31308
31468
|
when: (args) => heldKey(docKey(args) ?? "")
|
|
31309
31469
|
},
|
|
31310
31470
|
{
|
|
31311
31471
|
words: ["delete"],
|
|
31312
31472
|
reason: "not_syncable",
|
|
31313
|
-
why: "a key that is not a .md document is a blob
|
|
31473
|
+
why: "a key that is not a .md document is a blob; blobs, reserved files and View files do not sync, and conventions change only through the kind and recipe commands (delete a document with doc delete)",
|
|
31314
31474
|
when: (args) => {
|
|
31315
31475
|
const key = docKey(args);
|
|
31316
31476
|
return key !== void 0 && heldKey(key);
|
|
@@ -31324,7 +31484,7 @@ var init_refusals = __esm({
|
|
|
31324
31484
|
when: (args) => !args.some((token) => token === "--check" || token.startsWith("--check="))
|
|
31325
31485
|
},
|
|
31326
31486
|
{ words: ["serve"], reason: "not_syncable", why: "the served bundle accepts writes and deletes that do not sync" },
|
|
31327
|
-
{ words: ["ui"], reason: "not_syncable", why: "the local app writes Views
|
|
31487
|
+
{ words: ["ui"], reason: "not_syncable", why: "the local app writes Views, which do not sync, and conventions outside the kind and recipe commands" },
|
|
31328
31488
|
{ words: ["init"], reason: "checkout_target", why: "the folder is a hosted checkout, not a local bundle" }
|
|
31329
31489
|
].map((row3) => Object.freeze({ ...row3, words: Object.freeze([...row3.words]) })));
|
|
31330
31490
|
HELD_INSTEAD = {
|
|
@@ -34519,6 +34679,7 @@ function refusalRow(id, row3, accessWithdrawn = false) {
|
|
|
34519
34679
|
if (SIGN_IN_CODES.has(code3) && accessWithdrawn) return { id, state: "refused", reason: "access_withdrawn", version: null, message: ACCESS_WITHDRAWN_MESSAGE };
|
|
34520
34680
|
if (SIGN_IN_CODES.has(code3)) return { id, state: "paused", reason: "sign_in", version: null, message: "The hosted session ended before this change was sent; sign in and run sync again." };
|
|
34521
34681
|
if (BUSY_REFUSAL_CODES.has(code3)) return { id, state: "paused", reason: "busy", version: null, message: "The host was busy and did not apply this change; run sync again." };
|
|
34682
|
+
if (code3 === "definition_incompatible") return { id, state: "refused", reason: code3, version: null, message: `The host refused this model change (${code3}): ${message} Fix the documents it names (the sync that sends them sends this change again), or edit the Kind and run sync again.` };
|
|
34522
34683
|
return { id, state: "refused", reason: code3, version: null, message: `The host refused this document (${code3}): ${message} Edit the file and run sync again.` };
|
|
34523
34684
|
}
|
|
34524
34685
|
function waitingRow(id, pausedBy, notSent) {
|
|
@@ -34858,13 +35019,14 @@ async function withSession(binding, deps, resumeCommand, body, options = {}) {
|
|
|
34858
35019
|
} catch (error51) {
|
|
34859
35020
|
throw await readFailure(error51, { binding, target: target2, client }, resumeCommand, await unsent());
|
|
34860
35021
|
}
|
|
35022
|
+
const bound = await recordDefinitionWrites(deps.auth.home, binding, capabilities.definitionWrites);
|
|
34861
35023
|
projection = await readProjection(deps.auth.home, binding.checkout_id, store);
|
|
34862
35024
|
await recoverPlacements(binding.path, projection);
|
|
34863
35025
|
const record5 = projection;
|
|
34864
35026
|
const persist = () => writeProjection(deps.auth.home, binding.checkout_id, record5);
|
|
34865
35027
|
await persist();
|
|
34866
35028
|
return await body({
|
|
34867
|
-
binding,
|
|
35029
|
+
binding: bound,
|
|
34868
35030
|
target: target2,
|
|
34869
35031
|
reader,
|
|
34870
35032
|
capabilities,
|
|
@@ -34889,18 +35051,23 @@ async function withSession(binding, deps, resumeCommand, body, options = {}) {
|
|
|
34889
35051
|
throw lockFailure(error51, binding.path);
|
|
34890
35052
|
});
|
|
34891
35053
|
}
|
|
34892
|
-
function createsFirst(store, blocked) {
|
|
35054
|
+
function createsFirst(store, blocked, models) {
|
|
34893
35055
|
return new Proxy(store, {
|
|
34894
35056
|
get(inner, prop) {
|
|
34895
35057
|
if (prop === "listIntents") {
|
|
34896
35058
|
return async (state) => {
|
|
34897
|
-
const rows = await inner.listIntents(state)
|
|
35059
|
+
const rows = (await inner.listIntents(state)).filter(
|
|
35060
|
+
(row3) => !(state === "pending" && (row3.base === null && row3.kind !== DOCUMENT_DELETE_KIND && blocked.has(row3.target) || !models && isConventionId(row3.target)))
|
|
35061
|
+
);
|
|
34898
35062
|
if (state !== "pending") return rows;
|
|
34899
35063
|
const deleting = (row3) => row3.kind === DOCUMENT_DELETE_KIND;
|
|
35064
|
+
const model = (row3) => isConventionId(row3.target);
|
|
34900
35065
|
return [
|
|
34901
|
-
...rows.filter((row3) =>
|
|
34902
|
-
...rows.filter((row3) => row3.base
|
|
34903
|
-
...rows.filter(deleting)
|
|
35066
|
+
...rows.filter((row3) => model(row3) && !deleting(row3)),
|
|
35067
|
+
...rows.filter((row3) => !model(row3) && row3.base === null && !deleting(row3)),
|
|
35068
|
+
...rows.filter((row3) => !model(row3) && row3.base !== null && !deleting(row3)),
|
|
35069
|
+
...rows.filter((row3) => !model(row3) && deleting(row3)),
|
|
35070
|
+
...rows.filter((row3) => model(row3) && deleting(row3))
|
|
34904
35071
|
];
|
|
34905
35072
|
};
|
|
34906
35073
|
}
|
|
@@ -34909,18 +35076,18 @@ function createsFirst(store, blocked) {
|
|
|
34909
35076
|
}
|
|
34910
35077
|
});
|
|
34911
35078
|
}
|
|
34912
|
-
async function
|
|
34913
|
-
|
|
35079
|
+
async function requeueRefused(store, eligible) {
|
|
35080
|
+
const requeued = [];
|
|
34914
35081
|
const unsettled = await store.listIntents(UNSETTLED_STATES);
|
|
34915
35082
|
for (const row3 of unsettled) {
|
|
34916
|
-
if (row3.state !== "refused" || !row3.refusal || !
|
|
35083
|
+
if (row3.state !== "refused" || !row3.refusal || !eligible(row3)) continue;
|
|
34917
35084
|
if (unsettled.some((other) => other.after === row3.requestId)) continue;
|
|
34918
35085
|
const current = await store.readWithJournal(row3.target);
|
|
34919
35086
|
const latest = current.intents.filter((intent) => intent.state !== "acknowledged").at(-1);
|
|
34920
35087
|
if (latest?.requestId !== row3.requestId) continue;
|
|
34921
35088
|
const supersede = { requestId: row3.requestId, expectedState: "refused", expectedAttempts: row3.attempts };
|
|
34922
35089
|
if (row3.kind === DOCUMENT_DELETE_KIND) {
|
|
34923
|
-
if (await supersedeDeletion(store, row3)) requeued
|
|
35090
|
+
if (await supersedeDeletion(store, row3)) requeued.push(row3.target);
|
|
34924
35091
|
continue;
|
|
34925
35092
|
}
|
|
34926
35093
|
if (!current.document) continue;
|
|
@@ -34938,10 +35105,37 @@ async function requeueBusy(store) {
|
|
|
34938
35105
|
},
|
|
34939
35106
|
supersede
|
|
34940
35107
|
});
|
|
34941
|
-
requeued
|
|
35108
|
+
requeued.push(row3.target);
|
|
34942
35109
|
}
|
|
34943
35110
|
return requeued;
|
|
34944
35111
|
}
|
|
35112
|
+
function intentFrontmatter(row3, okfVersion) {
|
|
35113
|
+
const text4 = row3.kind === DOCUMENT_DELETE_KIND ? row3.baseContent : row3.content;
|
|
35114
|
+
if (!text4) return null;
|
|
35115
|
+
try {
|
|
35116
|
+
return parseMarkdown(text4, row3.target, { okfVersion }).frontmatter;
|
|
35117
|
+
} catch {
|
|
35118
|
+
return null;
|
|
35119
|
+
}
|
|
35120
|
+
}
|
|
35121
|
+
function recordLanded(landed, row3, okfVersion) {
|
|
35122
|
+
const frontmatter = intentFrontmatter(row3, okfVersion);
|
|
35123
|
+
if (isConventionId(row3.target)) {
|
|
35124
|
+
if (typeof frontmatter?.governs === "string") landed.governed.add(frontmatter.governs);
|
|
35125
|
+
landed.conventionChanged = true;
|
|
35126
|
+
} else if (typeof frontmatter?.type === "string") landed.types.add(frontmatter.type);
|
|
35127
|
+
}
|
|
35128
|
+
function definitionRetry(row3, landed, okfVersion, models) {
|
|
35129
|
+
const code3 = row3.refusal?.code;
|
|
35130
|
+
if (isConventionId(row3.target)) {
|
|
35131
|
+
if (!models || code3 !== "definition_incompatible") return false;
|
|
35132
|
+
const governs = intentFrontmatter(row3, okfVersion)?.governs;
|
|
35133
|
+
return landed.conventionChanged || typeof governs === "string" && landed.types.has(governs);
|
|
35134
|
+
}
|
|
35135
|
+
if (code3 !== "validation_failed") return false;
|
|
35136
|
+
const type = intentFrontmatter(row3, okfVersion)?.type;
|
|
35137
|
+
return typeof type === "string" && landed.governed.has(type);
|
|
35138
|
+
}
|
|
34945
35139
|
async function caseCollidingCreates(store) {
|
|
34946
35140
|
const creates = (await store.listIntents("pending")).filter((row3) => row3.base === null);
|
|
34947
35141
|
if (creates.length === 0) return [];
|
|
@@ -34996,13 +35190,23 @@ async function pushChanges(session, deps, acceptDeletes, heldSink = {}) {
|
|
|
34996
35190
|
heldSink.value ??= { deletions, baseline };
|
|
34997
35191
|
}
|
|
34998
35192
|
});
|
|
34999
|
-
const
|
|
35193
|
+
const models = session.capabilities.definitionWrites === "allowed";
|
|
35194
|
+
const withheld = models ? [] : [...new Set((await store.listIntents("pending")).filter((row3) => isConventionId(row3.target)).map((row3) => row3.target))].map((id) => ({
|
|
35195
|
+
id,
|
|
35196
|
+
path: `${id}.md`,
|
|
35197
|
+
reason: "convention_folder",
|
|
35198
|
+
message: heldPathMessage(`${id}.md`, session.capabilities.definitionWrites)
|
|
35199
|
+
}));
|
|
35200
|
+
const ordered = createsFirst(store, new Set(collisions.map((row3) => row3.id)), models);
|
|
35000
35201
|
let signInRequired = false;
|
|
35202
|
+
const landed = { types: /* @__PURE__ */ new Set(), governed: /* @__PURE__ */ new Set(), conventionChanged: false };
|
|
35203
|
+
const retried = /* @__PURE__ */ new Set();
|
|
35001
35204
|
for (let pass = 0; pass < PUSH_PASSES; pass += 1) {
|
|
35002
35205
|
const report = await push2(ordered, transport, { remote: reader, write: { ...deps.write, settlement: WHOLE_DOCUMENT_SETTLEMENT } });
|
|
35003
35206
|
for (const settled of report.settled) {
|
|
35004
35207
|
if (settled.state !== "acknowledged") continue;
|
|
35005
35208
|
const row3 = await store.readIntent(settled.requestId);
|
|
35209
|
+
if (row3) recordLanded(landed, row3, session.okfVersion);
|
|
35006
35210
|
acknowledged.set(settled.target, row3?.acknowledgedVersion === DELETION_VERSION ? "" : row3?.acknowledgedVersion ?? "");
|
|
35007
35211
|
if (row3?.kind === DOCUMENT_DELETE_KIND) {
|
|
35008
35212
|
deleted.add(settled.target);
|
|
@@ -35014,10 +35218,19 @@ async function pushChanges(session, deps, acceptDeletes, heldSink = {}) {
|
|
|
35014
35218
|
signInRequired = /^(AUTH_REQUIRED|UNAUTHORIZED)\b/.test(control?.reason ?? "") && !denied;
|
|
35015
35219
|
break;
|
|
35016
35220
|
}
|
|
35017
|
-
if (pass === PUSH_PASSES - 1
|
|
35221
|
+
if (pass === PUSH_PASSES - 1) break;
|
|
35222
|
+
const model = /* @__PURE__ */ new Set();
|
|
35223
|
+
const requeued = await requeueRefused(store, (row3) => {
|
|
35224
|
+
if (busyRefusal(row3)) return true;
|
|
35225
|
+
if (retried.has(row3.target) || !definitionRetry(row3, landed, session.okfVersion, models)) return false;
|
|
35226
|
+
model.add(row3.target);
|
|
35227
|
+
return true;
|
|
35228
|
+
});
|
|
35229
|
+
if (requeued.length === 0) break;
|
|
35230
|
+
for (const id of requeued) if (model.has(id)) retried.add(id);
|
|
35018
35231
|
await (deps.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms))))(50 + Math.floor(Math.random() * 200));
|
|
35019
35232
|
}
|
|
35020
|
-
return { acknowledged, deleted, deletedIntents, signInRequired, accessWithdrawn: denied, notSent: null, collisions, via };
|
|
35233
|
+
return { acknowledged, deleted, deletedIntents, signInRequired, accessWithdrawn: denied, notSent: null, collisions: [...collisions, ...withheld], via };
|
|
35021
35234
|
}
|
|
35022
35235
|
async function forgetPullDigest(store) {
|
|
35023
35236
|
const marker = await store.readMeta("pull");
|
|
@@ -35109,6 +35322,7 @@ async function runSync(binding, values, deps, mode) {
|
|
|
35109
35322
|
okfVersion: session.okfVersion,
|
|
35110
35323
|
local,
|
|
35111
35324
|
projection,
|
|
35325
|
+
definitionWrites: session.capabilities.definitionWrites,
|
|
35112
35326
|
...acceptDeletes !== void 0 ? { acceptDeletes, confirmAccept: confirmAtTerminal(binding, terminal) } : {}
|
|
35113
35327
|
});
|
|
35114
35328
|
await session.persist();
|
|
@@ -35256,7 +35470,7 @@ async function pullOnly(binding, session, deps, resumeCommand) {
|
|
|
35256
35470
|
{
|
|
35257
35471
|
const { store, local, reader, projection } = session;
|
|
35258
35472
|
await reclaimInFlight(local);
|
|
35259
|
-
await scanCheckout({ folder: binding.path, bundleId: binding.bundle_id, okfVersion: session.okfVersion, local, projection });
|
|
35473
|
+
await scanCheckout({ folder: binding.path, bundleId: binding.bundle_id, okfVersion: session.okfVersion, local, projection, definitionWrites: session.capabilities.definitionWrites });
|
|
35260
35474
|
await session.persist();
|
|
35261
35475
|
let report;
|
|
35262
35476
|
try {
|
|
@@ -35499,7 +35713,7 @@ async function runInspect2(binding, values, deps, mode) {
|
|
|
35499
35713
|
}
|
|
35500
35714
|
async function assertSendable(session, id, file2, bytes, resumeCommand) {
|
|
35501
35715
|
const stored = await session.store.readWithJournal(id);
|
|
35502
|
-
const held2 = unsendable(id, `${id}.md`, bytes, stored.document?.doc ?? null, { bundleId: session.binding.bundle_id, okfVersion: session.okfVersion });
|
|
35716
|
+
const held2 = unsendable(id, `${id}.md`, bytes, stored.document?.doc ?? null, { bundleId: session.binding.bundle_id, okfVersion: session.okfVersion }, { definitionWrites: session.capabilities.definitionWrites });
|
|
35503
35717
|
if (held2) throw new CliError("CONFLICT", `${file2} cannot be sent: ${held2.message}`, { details: { reason: held2.reason, id, file: file2 }, help: `edit ${file2}, then re-run: ${resumeCommand}` });
|
|
35504
35718
|
}
|
|
35505
35719
|
async function resolveFolder(session, id, choice, conflict, resumeCommand) {
|
|
@@ -35691,7 +35905,7 @@ async function hostedSync(argv2, binding, partial2 = {}) {
|
|
|
35691
35905
|
if (values["restore-deletes"]) return runRestore(binding, deps, mode);
|
|
35692
35906
|
return runSync(binding, values, deps, mode);
|
|
35693
35907
|
}
|
|
35694
|
-
var HOSTED_SYNC_USAGE, DEFAULT_ROW_LIMIT, INSPECT_PREVIEW_CHARS2, PUSH_PASSES, GIT_ONLY_FLAGS;
|
|
35908
|
+
var HOSTED_SYNC_USAGE, DEFAULT_ROW_LIMIT, INSPECT_PREVIEW_CHARS2, PUSH_PASSES, GIT_ONLY_FLAGS, busyRefusal;
|
|
35695
35909
|
var init_sync2 = __esm({
|
|
35696
35910
|
"../cli/src/hosted/sync.ts"() {
|
|
35697
35911
|
"use strict";
|
|
@@ -35783,6 +35997,7 @@ unless SUPERBEE_VIA says otherwise. SUPERBEE_ACTOR does not set it.
|
|
|
35783
35997
|
INSPECT_PREVIEW_CHARS2 = 4e3;
|
|
35784
35998
|
PUSH_PASSES = 3;
|
|
35785
35999
|
GIT_ONLY_FLAGS = ["establish", "pull-only", "show-incoming", "yes", "body-out", "migrate"];
|
|
36000
|
+
busyRefusal = (row3) => row3.refusal !== void 0 && BUSY_REFUSAL_CODES.has(row3.refusal.code);
|
|
35786
36001
|
}
|
|
35787
36002
|
});
|
|
35788
36003
|
|
|
@@ -84039,6 +84254,7 @@ async function recipeEvolve(argv2, stdout) {
|
|
|
84039
84254
|
throw new CliError("USAGE", loaded.error.message, { help: `${cliInvocation()} recipes` });
|
|
84040
84255
|
}
|
|
84041
84256
|
const remote = await resolveRemoteFlag(values.remote, values.dir);
|
|
84257
|
+
if (remote === void 0) await assertKindOnlyRecipe("recipe evolve", values.dir, loaded.recipe);
|
|
84042
84258
|
const bundle = await openBundle(values.dir, remote);
|
|
84043
84259
|
const target2 = values.dir !== void 0 ? commandFragment` --dir ${commandQuoted(values.dir)}` : remote !== void 0 ? commandFragment` --remote ${commandQuoted(remote)}` : commandLiteral("");
|
|
84044
84260
|
const planCommand = `${cliInvocation()} recipe evolve ${commandQuoted(ref)}${target2}`;
|
|
@@ -84094,6 +84310,7 @@ async function recipeAdd(argv2, stdout) {
|
|
|
84094
84310
|
throw new CliError("USAGE", loaded.error.message, { help: `${cliInvocation()} recipes` });
|
|
84095
84311
|
}
|
|
84096
84312
|
const remote = await resolveRemoteFlag(values.remote, values.dir);
|
|
84313
|
+
if (remote === void 0) await assertKindOnlyRecipe("recipe add", values.dir, loaded.recipe);
|
|
84097
84314
|
const bundle = await openBundle(values.dir, remote);
|
|
84098
84315
|
const result3 = await applyRecipe(bundle, loaded.recipe);
|
|
84099
84316
|
const registry2 = await loadKinds(bundle);
|
|
@@ -84140,6 +84357,7 @@ var init_recipe = __esm({
|
|
|
84140
84357
|
init_recipes();
|
|
84141
84358
|
init_recipe_evolution();
|
|
84142
84359
|
init_recipe_source();
|
|
84360
|
+
init_refusals();
|
|
84143
84361
|
init_command_text();
|
|
84144
84362
|
RECIPE_USAGE = `superbee recipe \u2014 apply a recipe to this bundle
|
|
84145
84363
|
|
|
@@ -84220,7 +84438,9 @@ async function classifyCheckout(binding, home2, store) {
|
|
|
84220
84438
|
okfVersion: await storeOkfVersion(store),
|
|
84221
84439
|
local: openLocalBundle(binding.checkout_id, { backend: store }),
|
|
84222
84440
|
projection: await readProjection(home2, binding.checkout_id, store),
|
|
84223
|
-
preview: true
|
|
84441
|
+
preview: true,
|
|
84442
|
+
// What the host said at the last sync: a preview makes no request.
|
|
84443
|
+
definitionWrites: binding.definition_writes ?? null
|
|
84224
84444
|
});
|
|
84225
84445
|
for (const id of report.pending) result3.unsent.add(id);
|
|
84226
84446
|
for (const id of report.conflicted) result3.conflicts.add(id);
|
|
@@ -119715,7 +119935,7 @@ function checkoutGuard(binding, home2, folder, options) {
|
|
|
119715
119935
|
}
|
|
119716
119936
|
const rel = pathFromConceptId(id);
|
|
119717
119937
|
const reason = heldPathReason(rel);
|
|
119718
|
-
if (reason === "convention_folder") refuse({ id, path: rel, reason, message:
|
|
119938
|
+
if (reason === "convention_folder") refuse({ id, path: rel, reason, message: heldPathMessage(rel, binding.definition_writes ?? null, "app") });
|
|
119719
119939
|
if (reason !== null) refuse({ id, path: rel, reason, message: `${rel} is a reserved OKF file, which sync does not send` });
|
|
119720
119940
|
return rel;
|
|
119721
119941
|
};
|
|
@@ -119738,7 +119958,7 @@ function checkoutGuard(binding, home2, folder, options) {
|
|
|
119738
119958
|
const rel = documentPath(id);
|
|
119739
119959
|
const bytes = Buffer.from(stringifyDoc(doc2.frontmatter, doc2.body ?? ""), "utf8");
|
|
119740
119960
|
const stored = await storedDocument(binding, home2, folder, id);
|
|
119741
|
-
const held2 = unsendable(id, rel, bytes, stored.frontmatter ? { frontmatter: stored.frontmatter } : null, { bundleId: binding.bundle_id, okfVersion: stored.okfVersion });
|
|
119961
|
+
const held2 = unsendable(id, rel, bytes, stored.frontmatter ? { frontmatter: stored.frontmatter } : null, { bundleId: binding.bundle_id, okfVersion: stored.okfVersion }, { definitionWrites: null, sender: "app" });
|
|
119742
119962
|
if (held2) refuse(held2);
|
|
119743
119963
|
const current = await folder.read(id).then((read) => read, () => null);
|
|
119744
119964
|
const expected = writeOptions?.expectedVersion;
|
|
@@ -119755,7 +119975,7 @@ function checkoutGuard(binding, home2, folder, options) {
|
|
|
119755
119975
|
case "deleteBlob": {
|
|
119756
119976
|
const key = String(args[0]);
|
|
119757
119977
|
const reason = heldPathReason(key) ?? "not_a_document";
|
|
119758
|
-
const message = reason === "convention_folder" ?
|
|
119978
|
+
const message = reason === "convention_folder" ? heldPathMessage(key, binding.definition_writes ?? null, "app") : reason === "reserved_file" ? `${key} is a reserved OKF file, which sync does not send` : `${key} is not a .md document; files other than documents do not sync`;
|
|
119759
119979
|
return refuse({ id: key, path: key, reason, message });
|
|
119760
119980
|
}
|
|
119761
119981
|
default:
|
|
@@ -128609,7 +128829,7 @@ Options:
|
|
|
128609
128829
|
|
|
128610
128830
|
// ../cli/src/hosted/publish-state.ts
|
|
128611
128831
|
import { createHash as createHash20 } from "node:crypto";
|
|
128612
|
-
import { unlink as unlink10 } from "node:fs/promises";
|
|
128832
|
+
import { readdir as readdir6, unlink as unlink10 } from "node:fs/promises";
|
|
128613
128833
|
import path45 from "node:path";
|
|
128614
128834
|
function recordDir(home2) {
|
|
128615
128835
|
return path45.join(userStateDir(home2), "hosted-publish");
|
|
@@ -128639,6 +128859,23 @@ async function writePendingCreate(home2, canonical2, pending) {
|
|
|
128639
128859
|
await writeUserStateFileAtomic0600(home2, recordDir(home2), recordName2(canonical2, pendingSuffix(pending.bundle_id)), `${JSON.stringify(pending)}
|
|
128640
128860
|
`);
|
|
128641
128861
|
}
|
|
128862
|
+
async function listPendingCreates(home2) {
|
|
128863
|
+
let names;
|
|
128864
|
+
try {
|
|
128865
|
+
names = await readdir6(recordDir(home2));
|
|
128866
|
+
} catch {
|
|
128867
|
+
return [];
|
|
128868
|
+
}
|
|
128869
|
+
const records = [];
|
|
128870
|
+
for (const name of names.filter((entry) => /^[0-9a-f]{64}-[0-9a-f]{64}\.json$/.test(entry))) {
|
|
128871
|
+
try {
|
|
128872
|
+
const value = JSON.parse(await readUserStateFile(home2, path45.join(recordDir(home2), name), 1024 * 1024));
|
|
128873
|
+
if (typeof value.request_id === "string" && typeof value.bundle_id === "string" && typeof value.host === "string") records.push(value);
|
|
128874
|
+
} catch {
|
|
128875
|
+
}
|
|
128876
|
+
}
|
|
128877
|
+
return records;
|
|
128878
|
+
}
|
|
128642
128879
|
function clearPendingCreate(home2, canonical2, bundleId) {
|
|
128643
128880
|
return clearRecord(home2, canonical2, pendingSuffix(bundleId));
|
|
128644
128881
|
}
|
|
@@ -128912,7 +129149,7 @@ async function rebindToWorkspace(canonical2, bound, options, deps, mode) {
|
|
|
128912
129149
|
}
|
|
128913
129150
|
async function bindFolderInPlace(input) {
|
|
128914
129151
|
const { canonical: canonical2, target: target2, bundleId, deps, resume: resume2 } = input;
|
|
128915
|
-
const { identity, reader, listed: isListed, workspace, reference } = input.connection;
|
|
129152
|
+
const { identity, reader, listed: isListed, workspace, reference, definitionWrites } = input.connection;
|
|
128916
129153
|
const home2 = deps.auth.home;
|
|
128917
129154
|
const placedFiles = /* @__PURE__ */ new Map();
|
|
128918
129155
|
return withCheckoutLock(canonical2, async () => {
|
|
@@ -128921,7 +129158,7 @@ async function bindFolderInPlace(input) {
|
|
|
128921
129158
|
}
|
|
128922
129159
|
const identityNow = await folderIdentity(canonical2);
|
|
128923
129160
|
if (!identityNow) throw new CliError("RUNTIME", `${canonical2} disappeared while binding it`, { help: "retry the same command" });
|
|
128924
|
-
const binding = {
|
|
129161
|
+
const binding = withDefinitionWrites({
|
|
128925
129162
|
schema: 1,
|
|
128926
129163
|
checkout_id: newCheckoutId(),
|
|
128927
129164
|
path: canonical2,
|
|
@@ -128936,7 +129173,7 @@ async function bindFolderInPlace(input) {
|
|
|
128936
129173
|
created_at: new Date(deps.auth.now()).toISOString(),
|
|
128937
129174
|
state: "hydrating",
|
|
128938
129175
|
folder_identity: identityNow
|
|
128939
|
-
};
|
|
129176
|
+
}, definitionWrites);
|
|
128940
129177
|
await writeBinding(home2, binding);
|
|
128941
129178
|
let store;
|
|
128942
129179
|
try {
|
|
@@ -129061,7 +129298,7 @@ var init_checkout_adopt = __esm({
|
|
|
129061
129298
|
|
|
129062
129299
|
// ../cli/src/commands/checkout.ts
|
|
129063
129300
|
import { homedir as homedir37 } from "node:os";
|
|
129064
|
-
import { lstat as lstat9, mkdir as mkdir4, readFile as readFile5, readdir as
|
|
129301
|
+
import { lstat as lstat9, mkdir as mkdir4, readFile as readFile5, readdir as readdir7, realpath as realpath11, rmdir as rmdir3, stat as stat8, unlink as unlink11 } from "node:fs/promises";
|
|
129065
129302
|
import path47 from "node:path";
|
|
129066
129303
|
import { parseArgs as parseArgs39 } from "node:util";
|
|
129067
129304
|
function checkoutDeps(partial2) {
|
|
@@ -129081,7 +129318,7 @@ async function entryKind(target2) {
|
|
|
129081
129318
|
throw error51;
|
|
129082
129319
|
}
|
|
129083
129320
|
if (!info.isDirectory()) return "other";
|
|
129084
|
-
return (await
|
|
129321
|
+
return (await readdir7(target2)).length === 0 ? "empty-dir" : "dir";
|
|
129085
129322
|
}
|
|
129086
129323
|
function bindingView(binding) {
|
|
129087
129324
|
return {
|
|
@@ -129321,7 +129558,7 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
129321
129558
|
});
|
|
129322
129559
|
}
|
|
129323
129560
|
await assertStandaloneFolder(folder);
|
|
129324
|
-
const { identity, reader, listed: listed2, workspace, reference } = await connectHostedBundle(typed, target2, values.workspace, deps, resume2);
|
|
129561
|
+
const { identity, reader, listed: listed2, workspace, reference, definitionWrites } = await connectHostedBundle(typed, target2, values.workspace, deps, resume2);
|
|
129325
129562
|
const canonical2 = await canonicalFolder(folder);
|
|
129326
129563
|
let createdFolder = false;
|
|
129327
129564
|
const placed = /* @__PURE__ */ new Map();
|
|
@@ -129348,7 +129585,7 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
129348
129585
|
}
|
|
129349
129586
|
const identityNow = await folderIdentity(canonical2);
|
|
129350
129587
|
if (!identityNow) throw new CliError("RUNTIME", `${canonical2} disappeared during checkout`, { help: "retry the same command" });
|
|
129351
|
-
const binding = {
|
|
129588
|
+
const binding = withDefinitionWrites({
|
|
129352
129589
|
schema: 1,
|
|
129353
129590
|
checkout_id: newCheckoutId(),
|
|
129354
129591
|
path: canonical2,
|
|
@@ -129363,7 +129600,7 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
129363
129600
|
created_at: new Date(deps.auth.now()).toISOString(),
|
|
129364
129601
|
state: "hydrating",
|
|
129365
129602
|
folder_identity: identityNow
|
|
129366
|
-
};
|
|
129603
|
+
}, definitionWrites);
|
|
129367
129604
|
await writeBinding(deps.auth.home, binding);
|
|
129368
129605
|
let store;
|
|
129369
129606
|
try {
|
|
@@ -129485,7 +129722,7 @@ async function connectHostedBundle(typed, target2, workspaceFlag, deps, resume2)
|
|
|
129485
129722
|
}
|
|
129486
129723
|
if (ids.length > Math.min(CHECKOUT_DOCUMENT_LIMIT, capabilities.bound.documents)) throw tooLarge(reference, target2, ids.length);
|
|
129487
129724
|
assertProjectable(ids, bundleId, target2);
|
|
129488
|
-
return { identity, reader, listed: listed2, workspace: tenantId ?? workspace, reference, client };
|
|
129725
|
+
return { identity, reader, listed: listed2, workspace: tenantId ?? workspace, reference, client, definitionWrites: capabilities.definitionWrites };
|
|
129489
129726
|
}
|
|
129490
129727
|
function catalogLabels(bundleId) {
|
|
129491
129728
|
const base = bundleId.slice(0, 60).replace(/[._-]+$/, "");
|
|
@@ -129640,7 +129877,7 @@ Examples:
|
|
|
129640
129877
|
// ../cli/src/commands/export.ts
|
|
129641
129878
|
import { randomBytes as randomBytes9 } from "node:crypto";
|
|
129642
129879
|
import { homedir as homedir38 } from "node:os";
|
|
129643
|
-
import { link as link3, lstat as lstat10, mkdir as mkdir5, open as open4, readdir as
|
|
129880
|
+
import { link as link3, lstat as lstat10, mkdir as mkdir5, open as open4, readdir as readdir8, realpath as realpath12, rename as rename2, rm as rm2, unlink as unlink12 } from "node:fs/promises";
|
|
129644
129881
|
import path48 from "node:path";
|
|
129645
129882
|
import { parseArgs as parseArgs40 } from "node:util";
|
|
129646
129883
|
function exportDeps(partial2) {
|
|
@@ -129660,7 +129897,7 @@ async function entryKind2(target2) {
|
|
|
129660
129897
|
throw error51;
|
|
129661
129898
|
}
|
|
129662
129899
|
if (!info.isDirectory()) return "other";
|
|
129663
|
-
return (await
|
|
129900
|
+
return (await readdir8(target2)).length === 0 ? "empty-dir" : "dir";
|
|
129664
129901
|
}
|
|
129665
129902
|
function shown(paths) {
|
|
129666
129903
|
return [...paths].sort().slice(0, SHOWN);
|
|
@@ -129837,7 +130074,7 @@ async function removeAbandonedStaging(target2) {
|
|
|
129837
130074
|
let removed = 0;
|
|
129838
130075
|
let names;
|
|
129839
130076
|
try {
|
|
129840
|
-
names = await
|
|
130077
|
+
names = await readdir8(path48.dirname(target2));
|
|
129841
130078
|
} catch {
|
|
129842
130079
|
return 0;
|
|
129843
130080
|
}
|
|
@@ -130772,20 +131009,20 @@ async function planPublish(folder, options) {
|
|
|
130772
131009
|
skipped.push({ path: entry.rel, reason: "not a plain file" });
|
|
130773
131010
|
continue;
|
|
130774
131011
|
}
|
|
130775
|
-
const
|
|
131012
|
+
const bytes = await fs15.readFile(path50.join(folder, entry.rel));
|
|
130776
131013
|
if (isReservedFile(entry.rel)) {
|
|
130777
|
-
const text4 = utf83(
|
|
131014
|
+
const text4 = utf83(bytes);
|
|
130778
131015
|
if (text4 === null) {
|
|
130779
131016
|
blockers.push({ path: entry.rel, reason: "not_utf8", message: `${entry.rel} is a reserved file that is not UTF-8 text` });
|
|
130780
131017
|
continue;
|
|
130781
131018
|
}
|
|
130782
|
-
if (
|
|
131019
|
+
if (bytes.byteLength > PUBLISH_BOUNDS.reservedBytes) {
|
|
130783
131020
|
blockers.push({ path: entry.rel, reason: "too_large", message: `${entry.rel} is over the ${PUBLISH_BOUNDS.reservedBytes / 1024} KiB a reserved file may hold` });
|
|
130784
131021
|
continue;
|
|
130785
131022
|
}
|
|
130786
131023
|
const dir = path50.posix.dirname(entry.rel);
|
|
130787
131024
|
reserved.push({ dir: dir === "." ? "" : dir, name: path50.posix.basename(entry.rel), content: text4 });
|
|
130788
|
-
if (entry.rel !== "index.md") extras[entry.rel] = digest2(
|
|
131025
|
+
if (entry.rel !== "index.md") extras[entry.rel] = digest2(bytes);
|
|
130789
131026
|
continue;
|
|
130790
131027
|
}
|
|
130791
131028
|
if (!entry.rel.endsWith(".md")) {
|
|
@@ -130795,12 +131032,12 @@ async function planPublish(folder, options) {
|
|
|
130795
131032
|
blockers.push({ path: entry.rel, reason: "unsafe_path", message: `${entry.rel} cannot be a file key (${error51.message})` });
|
|
130796
131033
|
continue;
|
|
130797
131034
|
}
|
|
130798
|
-
if (
|
|
130799
|
-
blockers.push({ path: entry.rel, reason: "too_large", message: `${entry.rel} is over the
|
|
131035
|
+
if (bytes.byteLength > STAGED_PUBLISH_BOUNDS.blobBytes) {
|
|
131036
|
+
blockers.push({ path: entry.rel, reason: "too_large", message: `${entry.rel} is over the 16 MiB a file may hold` });
|
|
130800
131037
|
continue;
|
|
130801
131038
|
}
|
|
130802
|
-
blobs2.push({ key: entry.rel, contentType: contentTypeOf(entry.rel),
|
|
130803
|
-
extras[entry.rel] = digest2(
|
|
131039
|
+
blobs2.push({ key: entry.rel, contentType: contentTypeOf(entry.rel), bytes });
|
|
131040
|
+
extras[entry.rel] = digest2(bytes);
|
|
130804
131041
|
continue;
|
|
130805
131042
|
}
|
|
130806
131043
|
const id = conceptIdFromPath(entry.rel);
|
|
@@ -130814,14 +131051,14 @@ async function planPublish(folder, options) {
|
|
|
130814
131051
|
blockers.push({ path: entry.rel, reason: "document_id_not_canonical", message: `${entry.rel} is not in its canonical spelling (a segment starts or ends with a space or an invisible format character, holds a format character other than a joiner, or is not NFC); rename it` });
|
|
130815
131052
|
continue;
|
|
130816
131053
|
}
|
|
130817
|
-
const refusal = unsendable(id, entry.rel,
|
|
131054
|
+
const refusal = unsendable(id, entry.rel, bytes, null, { bundleId: "publish", okfVersion });
|
|
130818
131055
|
if (refusal) {
|
|
130819
131056
|
blockers.push({ path: entry.rel, reason: refusal.reason, message: refusal.message });
|
|
130820
131057
|
continue;
|
|
130821
131058
|
}
|
|
130822
131059
|
let request2;
|
|
130823
131060
|
try {
|
|
130824
|
-
request2 = wholeDocumentRequest("publish", { kind: "document.write", target: id, base: null, content: utf83(
|
|
131061
|
+
request2 = wholeDocumentRequest("publish", { kind: "document.write", target: id, base: null, content: utf83(bytes) }, okfVersion);
|
|
130825
131062
|
} catch (error51) {
|
|
130826
131063
|
if (error51 instanceof WholeDocumentInputError) {
|
|
130827
131064
|
blockers.push({ path: entry.rel, reason: "not_sendable", message: error51.message });
|
|
@@ -130831,7 +131068,7 @@ async function planPublish(folder, options) {
|
|
|
130831
131068
|
}
|
|
130832
131069
|
if (request2.kind === "delete") throw new Error(`'${id}' became a delete request`);
|
|
130833
131070
|
documents.push({ id, frontmatter: request2.payload.frontmatter, body: request2.payload.body ?? "" });
|
|
130834
|
-
documentFiles.push({ id, rel: entry.rel, text: utf83(
|
|
131071
|
+
documentFiles.push({ id, rel: entry.rel, text: utf83(bytes) });
|
|
130835
131072
|
}
|
|
130836
131073
|
if (rootIndex === null) blockers.push({ path: "index.md", reason: "no_root_index", message: "a hosted bundle needs a root index.md" });
|
|
130837
131074
|
if (edition === "unsupported") {
|
|
@@ -130852,9 +131089,7 @@ async function planPublish(folder, options) {
|
|
|
130852
131089
|
claimed.set(key, file2);
|
|
130853
131090
|
}
|
|
130854
131091
|
}
|
|
130855
|
-
if (documents.length > PUBLISH_BOUNDS.documents) blockers.push({ path: ".", reason: "too_many_documents", message: `${documents.length} documents; a hosted bundle is created with at most ${PUBLISH_BOUNDS.documents}` });
|
|
130856
131092
|
if (reserved.length > PUBLISH_BOUNDS.reserved) blockers.push({ path: ".", reason: "too_many_reserved", message: `${reserved.length} reserved files; at most ${PUBLISH_BOUNDS.reserved}` });
|
|
130857
|
-
if (blobs2.length > PUBLISH_BOUNDS.blobs) blockers.push({ path: ".", reason: "too_many_files", message: `${blobs2.length} files that are not documents; at most ${PUBLISH_BOUNDS.blobs}` });
|
|
130858
131093
|
let history = [];
|
|
130859
131094
|
let historyPlan = { mode: "current-only", versions: 0, skipped: 0, note: "history starts at publish" };
|
|
130860
131095
|
if (options.history) {
|
|
@@ -130873,24 +131108,49 @@ async function planPublish(folder, options) {
|
|
|
130873
131108
|
skipped: skippedVersions,
|
|
130874
131109
|
note: "earlier Git versions are imported as labeled, unverified history (imported:git/<commit>); nothing reads them back yet"
|
|
130875
131110
|
};
|
|
130876
|
-
if (history.length > PUBLISH_BOUNDS.history || documents.length + reserved.length + blobs2.length + history.length > PUBLISH_BOUNDS.objects) {
|
|
130877
|
-
blockers.push({
|
|
130878
|
-
path: ".",
|
|
130879
|
-
reason: "too_much_history",
|
|
130880
|
-
message: `${history.length} earlier versions: one creation carries at most ${PUBLISH_BOUNDS.history} versions and ${PUBLISH_BOUNDS.objects} objects in all; publish without --with-history`
|
|
130881
|
-
});
|
|
130882
|
-
}
|
|
130883
131111
|
}
|
|
130884
131112
|
}
|
|
130885
|
-
|
|
130886
|
-
|
|
130887
|
-
|
|
130888
|
-
|
|
130889
|
-
|
|
130890
|
-
|
|
130891
|
-
|
|
131113
|
+
const draft = { documents, reserved, blobs: blobs2, history };
|
|
131114
|
+
const sendPath = publishPath(draft);
|
|
131115
|
+
if (sendPath.mode === "staged") blockers.push(...stagedBlockers(draft));
|
|
131116
|
+
return { documents, reserved, blobs: blobs2, history, path: sendPath, skipped, blockers, historyPlan, extras };
|
|
131117
|
+
}
|
|
131118
|
+
function publishPath(plan) {
|
|
131119
|
+
const bounds = PUBLISH_BOUNDS;
|
|
131120
|
+
const objects = plan.documents.length + plan.reserved.length + plan.blobs.length + plan.history.length;
|
|
131121
|
+
const why = plan.documents.length > bounds.documents ? `${plan.documents.length} documents (one request carries at most ${bounds.documents})` : plan.blobs.length > bounds.blobs ? `${plan.blobs.length} other files (one request carries at most ${bounds.blobs})` : plan.history.length > bounds.history ? `${plan.history.length} earlier versions (one request carries at most ${bounds.history})` : objects > bounds.objects ? `${objects} objects in all (one request carries at most ${bounds.objects})` : plan.blobs.some((blob) => blob.bytes.byteLength > bounds.blobBytes) ? `a file over 1 MB (${plan.blobs.find((blob) => blob.bytes.byteLength > bounds.blobBytes).key})` : null;
|
|
131122
|
+
if (why !== null) return { mode: "staged", why };
|
|
131123
|
+
const base643 = plan.blobs.reduce((sum, blob) => sum + Math.ceil(blob.bytes.byteLength / 3) * 4, 0);
|
|
131124
|
+
const bytes = base643 > bounds.requestBytes ? base643 : Buffer.byteLength(JSON.stringify(createBody(plan, WIDEST_TARGET)));
|
|
131125
|
+
if (bytes > bounds.requestBytes) return { mode: "staged", why: `${Math.ceil(bytes / 1024)} KiB as one request (one request carries at most ${bounds.requestBytes / MiB} MiB)` };
|
|
131126
|
+
return { mode: "one-shot" };
|
|
131127
|
+
}
|
|
131128
|
+
function stagedBlockers(plan) {
|
|
131129
|
+
const bounds = STAGED_PUBLISH_BOUNDS;
|
|
131130
|
+
const blockers = [];
|
|
131131
|
+
const noHistory = "publish without --with-history, or with fewer earlier versions";
|
|
131132
|
+
if (plan.documents.length > bounds.documents) blockers.push({ path: ".", reason: "too_many_documents", message: `${plan.documents.length} documents; a hosted bundle is created with at most ${bounds.documents}` });
|
|
131133
|
+
if (plan.blobs.length > bounds.blobs) blockers.push({ path: ".", reason: "too_many_files", message: `${plan.blobs.length} files that are not documents; at most ${bounds.blobs}` });
|
|
131134
|
+
if (plan.history.length > bounds.history) blockers.push({ path: ".", reason: "too_much_history", message: `${plan.history.length} earlier versions; a creation carries at most ${bounds.history}: ${noHistory}` });
|
|
131135
|
+
const content3 = stagedContent(plan);
|
|
131136
|
+
const current = distinctBytes([...content3.documents, ...content3.reserved, ...content3.blobs]);
|
|
131137
|
+
if (current > bounds.currentBytes) blockers.push({ path: ".", reason: "too_large", message: `the bundle's current files are ${Math.ceil(current / MiB)} MiB; a creation carries at most ${bounds.currentBytes / MiB} MiB` });
|
|
131138
|
+
const history = distinctBytes(content3.history);
|
|
131139
|
+
if (history > bounds.historyBytes) blockers.push({ path: ".", reason: "too_much_history", message: `the earlier versions are ${Math.ceil(history / MiB)} MiB; a creation carries at most ${bounds.historyBytes / MiB} MiB: ${noHistory}` });
|
|
131140
|
+
const parts = packParts(content3, content3.objects.keys()).length;
|
|
131141
|
+
if (parts > bounds.parts) blockers.push({ path: ".", reason: "too_many_parts", message: `the bundle needs ${parts} parts; a creation holds at most ${bounds.parts}: ${plan.history.length > 0 ? noHistory : "publish fewer files"}` });
|
|
131142
|
+
const manifest = Buffer.byteLength(JSON.stringify(manifestBody(content3, WIDEST_TARGET)));
|
|
131143
|
+
if (manifest > bounds.manifestBytes) {
|
|
131144
|
+
blockers.push({
|
|
131145
|
+
path: ".",
|
|
131146
|
+
reason: "manifest_too_large",
|
|
131147
|
+
message: `the list of everything sent is ${Math.ceil(manifest / 1024)} KiB; the host takes at most ${bounds.manifestBytes / MiB} MiB: ${plan.history.length > 0 ? noHistory : "publish fewer files"}`
|
|
131148
|
+
});
|
|
130892
131149
|
}
|
|
130893
|
-
return
|
|
131150
|
+
return blockers;
|
|
131151
|
+
}
|
|
131152
|
+
function distinctBytes(entries) {
|
|
131153
|
+
return [...new Map(entries.map((entry) => [entry.version, entry.bytes])).values()].reduce((sum, bytes) => sum + bytes, 0);
|
|
130894
131154
|
}
|
|
130895
131155
|
function createBody(plan, target2) {
|
|
130896
131156
|
return {
|
|
@@ -130899,14 +131159,89 @@ function createBody(plan, target2) {
|
|
|
130899
131159
|
name: target2.name,
|
|
130900
131160
|
documents: plan.documents,
|
|
130901
131161
|
reserved: plan.reserved,
|
|
130902
|
-
blobs: plan.blobs,
|
|
131162
|
+
blobs: plan.blobs.map((blob) => ({ key: blob.key, contentType: blob.contentType, base64: blob.bytes.toString("base64") })),
|
|
130903
131163
|
...plan.history.length > 0 ? { history: plan.history } : {}
|
|
130904
131164
|
};
|
|
130905
131165
|
}
|
|
130906
131166
|
function planDigest(body) {
|
|
130907
131167
|
return digest2(Buffer.from(JSON.stringify(body), "utf8"));
|
|
130908
131168
|
}
|
|
130909
|
-
|
|
131169
|
+
function stagedContent(plan) {
|
|
131170
|
+
const wire = (object5) => ({ ...object5, frontmatter: JSON.parse(JSON.stringify(object5.frontmatter)) });
|
|
131171
|
+
const objects = /* @__PURE__ */ new Map();
|
|
131172
|
+
const keep = (version3, object5) => {
|
|
131173
|
+
if (!objects.has(version3)) objects.set(version3, object5);
|
|
131174
|
+
};
|
|
131175
|
+
const stored = (text4) => ({ version: versionOfBytes(text4), bytes: Buffer.byteLength(text4, "utf8") });
|
|
131176
|
+
const conventions = [];
|
|
131177
|
+
const documents = plan.documents.map((sent) => {
|
|
131178
|
+
const doc2 = wire(sent);
|
|
131179
|
+
const text4 = stringifyDoc(doc2.frontmatter, doc2.body);
|
|
131180
|
+
const entry = stored(text4);
|
|
131181
|
+
keep(entry.version, { kind: "documents", value: doc2 });
|
|
131182
|
+
if (matchesFilter({ id: doc2.id, frontmatter: parseMarkdown(text4, doc2.id).frontmatter }, { prefix: CONVENTIONS_PREFIX, type: CONVENTION_TYPE })) conventions.push(doc2);
|
|
131183
|
+
return { id: doc2.id, ...entry };
|
|
131184
|
+
});
|
|
131185
|
+
const reserved = plan.reserved.map((file2) => {
|
|
131186
|
+
const entry = stored(file2.content);
|
|
131187
|
+
keep(entry.version, { kind: "reserved", value: file2 });
|
|
131188
|
+
return { dir: file2.dir, name: file2.name, ...entry };
|
|
131189
|
+
});
|
|
131190
|
+
const ordinals = /* @__PURE__ */ new Map();
|
|
131191
|
+
const history = plan.history.map((sent) => {
|
|
131192
|
+
const row3 = wire(sent);
|
|
131193
|
+
const ordinal = (ordinals.get(row3.documentId) ?? 0) + 1;
|
|
131194
|
+
ordinals.set(row3.documentId, ordinal);
|
|
131195
|
+
const entry = stored(stringifyDoc(row3.frontmatter, row3.body));
|
|
131196
|
+
keep(entry.version, { kind: "history", value: { ...row3, ordinal } });
|
|
131197
|
+
return { documentId: row3.documentId, ordinal, label: row3.label, author: row3.author, authoredAt: row3.authoredAt, ...entry };
|
|
131198
|
+
});
|
|
131199
|
+
const blobBytes = /* @__PURE__ */ new Map();
|
|
131200
|
+
const blobs2 = plan.blobs.map((blob) => {
|
|
131201
|
+
const version3 = blobVersion(blob.bytes);
|
|
131202
|
+
if (!blobBytes.has(version3)) blobBytes.set(version3, { key: blob.key, bytes: blob.bytes });
|
|
131203
|
+
return { key: blob.key, contentType: blob.contentType, version: version3, bytes: blob.bytes.byteLength };
|
|
131204
|
+
});
|
|
131205
|
+
const root = plan.reserved.find((file2) => file2.dir === "" && file2.name === "index.md")?.content ?? "";
|
|
131206
|
+
return { root, conventions, documents, reserved, blobs: blobs2, history, objects, blobBytes };
|
|
131207
|
+
}
|
|
131208
|
+
function manifestBody(content3, target2) {
|
|
131209
|
+
return {
|
|
131210
|
+
workspace: target2.workspace,
|
|
131211
|
+
bundleId: target2.bundleId,
|
|
131212
|
+
name: target2.name,
|
|
131213
|
+
root: content3.root,
|
|
131214
|
+
conventions: content3.conventions,
|
|
131215
|
+
documents: content3.documents,
|
|
131216
|
+
reserved: content3.reserved,
|
|
131217
|
+
blobs: content3.blobs,
|
|
131218
|
+
history: content3.history
|
|
131219
|
+
};
|
|
131220
|
+
}
|
|
131221
|
+
function packParts(content3, versions) {
|
|
131222
|
+
const bounds = STAGED_PUBLISH_BOUNDS;
|
|
131223
|
+
const parts = [];
|
|
131224
|
+
let part = null;
|
|
131225
|
+
let size = 0;
|
|
131226
|
+
const envelope = 512;
|
|
131227
|
+
for (const version3 of new Set(versions)) {
|
|
131228
|
+
const object5 = content3.objects.get(version3);
|
|
131229
|
+
if (!object5) continue;
|
|
131230
|
+
const bytes = Buffer.byteLength(JSON.stringify(object5.value)) + 1;
|
|
131231
|
+
if (part === null || part.versions.length >= bounds.partObjects || size + bytes > bounds.partTargetBytes) {
|
|
131232
|
+
part = { documents: [], reserved: [], history: [], versions: [] };
|
|
131233
|
+
parts.push(part);
|
|
131234
|
+
size = envelope;
|
|
131235
|
+
}
|
|
131236
|
+
if (object5.kind === "documents") part.documents.push(object5.value);
|
|
131237
|
+
else if (object5.kind === "reserved") part.reserved.push(object5.value);
|
|
131238
|
+
else part.history.push(object5.value);
|
|
131239
|
+
part.versions.push(version3);
|
|
131240
|
+
size += bytes;
|
|
131241
|
+
}
|
|
131242
|
+
return parts;
|
|
131243
|
+
}
|
|
131244
|
+
var MiB, PUBLISH_BOUNDS, STAGED_PUBLISH_BOUNDS, CONTENT_TYPES, HOSTED_KIND_WRITE, FORMAT_AT_EDGE, FORMAT_INSIDE, WIDEST_TARGET;
|
|
130910
131245
|
var init_publish_plan = __esm({
|
|
130911
131246
|
"../cli/src/hosted/publish-plan.ts"() {
|
|
130912
131247
|
"use strict";
|
|
@@ -130915,9 +131250,11 @@ var init_publish_plan = __esm({
|
|
|
130915
131250
|
init_src2();
|
|
130916
131251
|
init_src();
|
|
130917
131252
|
init_hosted_transport();
|
|
131253
|
+
init_versioning();
|
|
130918
131254
|
init_projection();
|
|
130919
131255
|
init_sync_scan();
|
|
130920
131256
|
init_src();
|
|
131257
|
+
MiB = 1024 * 1024;
|
|
130921
131258
|
PUBLISH_BOUNDS = Object.freeze({
|
|
130922
131259
|
objects: 1500,
|
|
130923
131260
|
documents: 1e3,
|
|
@@ -130926,7 +131263,22 @@ var init_publish_plan = __esm({
|
|
|
130926
131263
|
history: 1e3,
|
|
130927
131264
|
reservedBytes: 64 * 1024,
|
|
130928
131265
|
blobBytes: 1e6,
|
|
130929
|
-
requestBytes: 3 *
|
|
131266
|
+
requestBytes: 3 * MiB
|
|
131267
|
+
});
|
|
131268
|
+
STAGED_PUBLISH_BOUNDS = Object.freeze({
|
|
131269
|
+
documents: 1e4,
|
|
131270
|
+
reserved: 1e3,
|
|
131271
|
+
blobs: 1e3,
|
|
131272
|
+
history: 5e3,
|
|
131273
|
+
blobBytes: 16 * MiB,
|
|
131274
|
+
currentBytes: 64 * MiB,
|
|
131275
|
+
historyBytes: 64 * MiB,
|
|
131276
|
+
manifestBytes: 3 * MiB,
|
|
131277
|
+
/** The host's cap on one part's body; parts are packed to {@link partTargetBytes}. */
|
|
131278
|
+
partBytes: 3 * MiB,
|
|
131279
|
+
partTargetBytes: 1 * MiB,
|
|
131280
|
+
partObjects: 500,
|
|
131281
|
+
parts: 400
|
|
130930
131282
|
});
|
|
130931
131283
|
CONTENT_TYPES = {
|
|
130932
131284
|
".txt": "text/plain",
|
|
@@ -130951,6 +131303,246 @@ var init_publish_plan = __esm({
|
|
|
130951
131303
|
HOSTED_KIND_WRITE = Object.freeze({ strict: true, persistActor: true, producer: "process:superbee-hosted" });
|
|
130952
131304
|
FORMAT_AT_EDGE = new RegExp("^\\p{Cf}|\\p{Cf}$", "u");
|
|
130953
131305
|
FORMAT_INSIDE = new RegExp("(?![\\u200c\\u200d])\\p{Cf}", "u");
|
|
131306
|
+
WIDEST_TARGET = Object.freeze({ workspace: "w".repeat(64), bundleId: "b".repeat(128), name: "n".repeat(128) });
|
|
131307
|
+
}
|
|
131308
|
+
});
|
|
131309
|
+
|
|
131310
|
+
// ../cli/src/hosted/publish-staged.ts
|
|
131311
|
+
function isFinalBeforeReservation(refusal) {
|
|
131312
|
+
return !refusal.reserved && refusal.route === "bundle-create-begin" && ["bundle_exists", "document_id_collision", "document_id_not_canonical", "validation_failed", "result_too_large"].includes(refusal.code);
|
|
131313
|
+
}
|
|
131314
|
+
async function runStagedCreate(request2) {
|
|
131315
|
+
const { client, requestId: requestId2, target: target2, content: content3 } = request2;
|
|
131316
|
+
const sleep = request2.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
|
|
131317
|
+
const progress = request2.progress ?? (() => {
|
|
131318
|
+
});
|
|
131319
|
+
const route = (name) => `${client.prefix}/${name}`;
|
|
131320
|
+
let reserved = false;
|
|
131321
|
+
let planHash = "";
|
|
131322
|
+
let requests = 0;
|
|
131323
|
+
let bytesSent = 0;
|
|
131324
|
+
const objects = content3.objects.size + content3.blobBytes.size;
|
|
131325
|
+
const contentBytes = [...content3.objects.values()].reduce((sum, object5) => sum + Buffer.byteLength(JSON.stringify(object5.value)), 0) + [...content3.blobBytes.values()].reduce((sum, blob) => sum + blob.bytes.byteLength, 0);
|
|
131326
|
+
const maximumRequests = REQUEST_ALLOWANCE + REQUESTS_PER_OBJECT * objects;
|
|
131327
|
+
const maximumBytes = BYTES_ALLOWANCE + BYTES_PER_CONTENT_BYTE * contentBytes;
|
|
131328
|
+
const unknownOutcome = (why) => new CliError("TRANSIENT", `${why}; the bundle may be partly created`, {
|
|
131329
|
+
details: { reason: "write_outcome_unknown", bundle_id: target2.bundleId, workspace: target2.workspace, host: client.target.origin, request_id: requestId2, retryable: true },
|
|
131330
|
+
help: `re-run the same command; it resumes the same creation, sending only what the host is missing: ${request2.resume}`
|
|
131331
|
+
});
|
|
131332
|
+
async function send(name, body, headers) {
|
|
131333
|
+
for (let attempt = 0; ; attempt++) {
|
|
131334
|
+
requests += 1;
|
|
131335
|
+
bytesSent += body instanceof Uint8Array ? body.byteLength : 0;
|
|
131336
|
+
if (requests > maximumRequests || bytesSent > maximumBytes) {
|
|
131337
|
+
throw unknownOutcome(`${client.target.origin} did not finish the creation within ${maximumRequests} requests and ${Math.ceil(maximumBytes / (1024 * 1024))} MiB sent`);
|
|
131338
|
+
}
|
|
131339
|
+
let answer2;
|
|
131340
|
+
try {
|
|
131341
|
+
if (body instanceof Uint8Array) {
|
|
131342
|
+
if (!client.carrier.bytes) throw new CliError("RUNTIME", "this client cannot send a file's raw bytes");
|
|
131343
|
+
answer2 = await client.carrier.bytes(route(name), body, client.signal, { maximum: ANSWER_BYTES, writeRequest: requestId2, ...headers ? { headers } : {} });
|
|
131344
|
+
} else {
|
|
131345
|
+
answer2 = await client.carrier.json(route(name), body, client.signal, { maximum: ANSWER_BYTES, writeRequest: requestId2 });
|
|
131346
|
+
}
|
|
131347
|
+
} catch (error51) {
|
|
131348
|
+
if (error51 instanceof HostedCarrierError && error51.code === "unavailable") throw unknownOutcome(`the answer from ${client.target.origin} to ${name} did not arrive`);
|
|
131349
|
+
throw hostedFailure(error51, client.target, request2.resume);
|
|
131350
|
+
}
|
|
131351
|
+
if (answer2.status !== 503) return answer2;
|
|
131352
|
+
const delay3 = RETRY_DELAYS_MS[attempt];
|
|
131353
|
+
if (delay3 === void 0) throw unknownOutcome(`${client.target.origin} kept answering ${name} 503`);
|
|
131354
|
+
await sleep(delay3);
|
|
131355
|
+
}
|
|
131356
|
+
}
|
|
131357
|
+
function read(name, answer2) {
|
|
131358
|
+
const envelope = answer2.body ?? {};
|
|
131359
|
+
const code3 = typeof envelope.error?.code === "string" ? envelope.error.code : null;
|
|
131360
|
+
const message = typeof envelope.error?.message === "string" ? envelope.error.message : "";
|
|
131361
|
+
if (answer2.status === 200 && envelope.ok === true && typeof envelope.data === "object" && envelope.data !== null) return envelope.data;
|
|
131362
|
+
if (answer2.status === 200 && envelope.ok === false && code3 === "staged_manifest_missing") return "no_manifest";
|
|
131363
|
+
if (answer2.status === 200 && envelope.ok === false && code3 === "request_conflict" && envelope.error?.retryable === true) return "busy";
|
|
131364
|
+
if (answer2.status === 200 && envelope.ok === false && code3 === "validation_failed" && VERSION_MISMATCH.test(message)) {
|
|
131365
|
+
throw new CliError("RUNTIME", `${client.target.origin} stores an object differently from this CLI (client/host contract mismatch)`, {
|
|
131366
|
+
details: { reason: "staged_mismatch", route: name, host_message: message, host: client.target.origin, request_id: requestId2, retryable: false },
|
|
131367
|
+
help: `upgrade Superbee (npm install -g superbee), then re-run the same command, which resumes this creation: ${request2.resume}`
|
|
131368
|
+
});
|
|
131369
|
+
}
|
|
131370
|
+
if (answer2.status === 200 && envelope.ok === false && code3 !== null) throw new StagedRefusal(code3, message, reserved, name);
|
|
131371
|
+
if (answer2.status === 429 && code3 === "bundle_create_limit") throw new StagedRefusal(code3, message, reserved, name);
|
|
131372
|
+
if (answer2.status === 400 && !reserved) throw new StagedRefusal(code3 ?? "invalid_input", message || `the host refused ${name} as malformed`, false, name);
|
|
131373
|
+
throw hostedFailure(new RemoteError(`hosted ${name} answered ${answer2.status}`, code3 ?? "RUNTIME", answer2.status), client.target, request2.resume);
|
|
131374
|
+
}
|
|
131375
|
+
async function status2(name, data) {
|
|
131376
|
+
const missing2 = data.missing;
|
|
131377
|
+
const staged = data.staged;
|
|
131378
|
+
if (data.state !== "staging" && data.state !== "importing" && data.state !== "created" || typeof data.planHash !== "string" || !VERSION.test(data.planHash) || !isStringList(missing2?.versions) || !isStringList(missing2?.blobs) || typeof staged?.versions !== "number" || typeof staged.blobVersions !== "number") {
|
|
131379
|
+
throw hostedFailure(new MalformedAnswer(`not a staged creation's status`, route(name)), client.target, request2.resume);
|
|
131380
|
+
}
|
|
131381
|
+
const answer2 = data;
|
|
131382
|
+
planHash = answer2.planHash;
|
|
131383
|
+
if (answer2.state !== "staging" && !reserved) {
|
|
131384
|
+
reserved = true;
|
|
131385
|
+
await request2.onReserved?.();
|
|
131386
|
+
}
|
|
131387
|
+
return answer2;
|
|
131388
|
+
}
|
|
131389
|
+
const manifest = manifestBody(content3, target2);
|
|
131390
|
+
async function begin() {
|
|
131391
|
+
let data = read("bundle-create-begin", await send("bundle-create-begin", manifest));
|
|
131392
|
+
for (let retry = 0; data === "busy"; retry++) {
|
|
131393
|
+
if (retry >= CONFLICT_RETRIES) {
|
|
131394
|
+
throw new CliError("TRANSIENT", `an earlier commit of this creation may still be running on ${client.target.origin}`, {
|
|
131395
|
+
details: { reason: "commit_running", bundle_id: target2.bundleId, workspace: target2.workspace, host: client.target.origin, request_id: requestId2, retryable: true },
|
|
131396
|
+
help: `re-run the same command in a minute: ${request2.resume}`
|
|
131397
|
+
});
|
|
131398
|
+
}
|
|
131399
|
+
progress({ phase: "wait", seconds: CONFLICT_RETRY_MS / 1e3, reason: "an earlier commit of this creation may still be running" });
|
|
131400
|
+
await sleep(CONFLICT_RETRY_MS);
|
|
131401
|
+
data = read("bundle-create-begin", await send("bundle-create-begin", manifest));
|
|
131402
|
+
}
|
|
131403
|
+
if (data === "no_manifest") throw hostedFailure(new MalformedAnswer("begin answered that no manifest is staged", route("bundle-create-begin")), client.target, request2.resume);
|
|
131404
|
+
const answer2 = await status2("bundle-create-begin", data);
|
|
131405
|
+
progress({ phase: "begin", state: answer2.state, versions: content3.objects.size, staged: answer2.staged.versions, blobs: content3.blobBytes.size, stagedBlobs: answer2.staged.blobVersions });
|
|
131406
|
+
return answer2;
|
|
131407
|
+
}
|
|
131408
|
+
async function fill(from) {
|
|
131409
|
+
const unknown3 = [...from.missing.versions.filter((v2) => !content3.objects.has(v2)), ...from.missing.blobs.filter((v2) => !content3.blobBytes.has(v2))];
|
|
131410
|
+
if (unknown3.length > 0) {
|
|
131411
|
+
throw new CliError("RUNTIME", `${client.target.origin} asks for ${unknown3.length} object(s) this folder does not hold (client/host contract mismatch)`, {
|
|
131412
|
+
details: { reason: "staged_mismatch", versions: unknown3.slice(0, 5), host: client.target.origin, request_id: requestId2, retryable: false },
|
|
131413
|
+
help: "upgrade Superbee (npm install -g superbee), then re-run the same command"
|
|
131414
|
+
});
|
|
131415
|
+
}
|
|
131416
|
+
const blobs2 = from.missing.blobs;
|
|
131417
|
+
let next = 0;
|
|
131418
|
+
let gone = false;
|
|
131419
|
+
const upload = async () => {
|
|
131420
|
+
try {
|
|
131421
|
+
await uploadEach();
|
|
131422
|
+
} catch (error51) {
|
|
131423
|
+
gone = true;
|
|
131424
|
+
throw error51;
|
|
131425
|
+
}
|
|
131426
|
+
};
|
|
131427
|
+
const uploadEach = async () => {
|
|
131428
|
+
for (let at = next++; at < blobs2.length && !gone; at = next++) {
|
|
131429
|
+
const version3 = blobs2[at];
|
|
131430
|
+
const blob = content3.blobBytes.get(version3);
|
|
131431
|
+
const data = read(
|
|
131432
|
+
"bundle-create-blob",
|
|
131433
|
+
await send("bundle-create-blob", blob.bytes, {
|
|
131434
|
+
"X-Superbee-Workspace": target2.workspace,
|
|
131435
|
+
"X-Superbee-Bundle": target2.bundleId,
|
|
131436
|
+
"X-Superbee-Blob-Version": version3,
|
|
131437
|
+
"X-Superbee-Plan-Hash": planHash
|
|
131438
|
+
})
|
|
131439
|
+
);
|
|
131440
|
+
if (data === "no_manifest" || data === "busy") {
|
|
131441
|
+
gone = true;
|
|
131442
|
+
return;
|
|
131443
|
+
}
|
|
131444
|
+
progress({ phase: "blob", blob: at + 1, blobs: blobs2.length, key: blob.key, bytes: blob.bytes.byteLength });
|
|
131445
|
+
}
|
|
131446
|
+
};
|
|
131447
|
+
let failure3 = null;
|
|
131448
|
+
const settled = await Promise.allSettled(Array.from({ length: Math.min(BLOBS_IN_FLIGHT, blobs2.length) }, upload));
|
|
131449
|
+
for (const result3 of settled) if (result3.status === "rejected" && failure3 === null) failure3 = result3.reason;
|
|
131450
|
+
if (failure3 !== null) throw failure3;
|
|
131451
|
+
if (gone) return "no_manifest";
|
|
131452
|
+
const parts = packParts(content3, from.missing.versions);
|
|
131453
|
+
let last = null;
|
|
131454
|
+
for (const [index2, part] of parts.entries()) {
|
|
131455
|
+
const data = read(
|
|
131456
|
+
"bundle-create-stage",
|
|
131457
|
+
await send("bundle-create-stage", { workspace: target2.workspace, bundleId: target2.bundleId, planHash, documents: part.documents, reserved: part.reserved, history: part.history })
|
|
131458
|
+
);
|
|
131459
|
+
if (data === "no_manifest" || data === "busy") return "no_manifest";
|
|
131460
|
+
last = await status2("bundle-create-stage", data);
|
|
131461
|
+
progress({ phase: "stage", part: index2 + 1, parts: parts.length, objects: part.versions.length });
|
|
131462
|
+
}
|
|
131463
|
+
return blobs2.length > 0 ? null : last;
|
|
131464
|
+
}
|
|
131465
|
+
const outstanding = (s) => s.missing.versions.length + s.missing.blobs.length;
|
|
131466
|
+
const missingKey = (s) => `${outstanding(s)}:${s.missing.versions[0] ?? ""}:${s.missing.blobs[0] ?? ""}`;
|
|
131467
|
+
let current = await begin();
|
|
131468
|
+
let lastFilled = null;
|
|
131469
|
+
let stalls = 0;
|
|
131470
|
+
let commits = 0;
|
|
131471
|
+
for (; ; ) {
|
|
131472
|
+
if (current !== null && current.state !== "created" && outstanding(current) > 0) {
|
|
131473
|
+
const key = missingKey(current);
|
|
131474
|
+
stalls = key === lastFilled ? stalls + 1 : 0;
|
|
131475
|
+
if (stalls >= MAXIMUM_STALLS) {
|
|
131476
|
+
throw new CliError("RUNTIME", `${client.target.origin} keeps naming the same objects as missing after they were sent ${MAXIMUM_STALLS} times`, {
|
|
131477
|
+
details: { reason: "staged_stall", missing: { versions: current.missing.versions.slice(0, 5), blobs: current.missing.blobs.slice(0, 5) }, host: client.target.origin, request_id: requestId2, retryable: true },
|
|
131478
|
+
help: `re-run the same command later; it resumes this creation: ${request2.resume}`
|
|
131479
|
+
});
|
|
131480
|
+
}
|
|
131481
|
+
lastFilled = key;
|
|
131482
|
+
const after = await fill(current);
|
|
131483
|
+
if (after === "no_manifest") {
|
|
131484
|
+
current = await begin();
|
|
131485
|
+
continue;
|
|
131486
|
+
}
|
|
131487
|
+
if (after !== null && outstanding(after) > 0) {
|
|
131488
|
+
current = after;
|
|
131489
|
+
continue;
|
|
131490
|
+
}
|
|
131491
|
+
}
|
|
131492
|
+
commits += 1;
|
|
131493
|
+
const answer2 = await send("bundle-create-commit", { workspace: target2.workspace, bundleId: target2.bundleId, planHash });
|
|
131494
|
+
const data = read("bundle-create-commit", answer2);
|
|
131495
|
+
if (data === "no_manifest" || data === "busy") {
|
|
131496
|
+
current = await begin();
|
|
131497
|
+
continue;
|
|
131498
|
+
}
|
|
131499
|
+
if (typeof data.state !== "string") {
|
|
131500
|
+
return data;
|
|
131501
|
+
}
|
|
131502
|
+
current = await status2("bundle-create-commit", data);
|
|
131503
|
+
progress({ phase: "commit", call: commits, state: current.state, ...current.written ? { written: current.written } : {} });
|
|
131504
|
+
}
|
|
131505
|
+
}
|
|
131506
|
+
var STAGED_CREATE_ROUTES, ANSWER_BYTES, BLOBS_IN_FLIGHT, RETRY_DELAYS_MS, REQUEST_ALLOWANCE, REQUESTS_PER_OBJECT, BYTES_PER_CONTENT_BYTE, BYTES_ALLOWANCE, MAXIMUM_STALLS, CONFLICT_RETRY_MS, CONFLICT_RETRIES, StagedRefusal, VERSION_MISMATCH, VERSION, isStringList;
|
|
131507
|
+
var init_publish_staged = __esm({
|
|
131508
|
+
"../cli/src/hosted/publish-staged.ts"() {
|
|
131509
|
+
"use strict";
|
|
131510
|
+
init_define_SUPERBEE_BUILD_IDENTITY();
|
|
131511
|
+
init_define_SUPERBEE_UPDATE_POLICY();
|
|
131512
|
+
init_src();
|
|
131513
|
+
init_hosted_transport();
|
|
131514
|
+
init_errors4();
|
|
131515
|
+
init_client();
|
|
131516
|
+
init_publish_plan();
|
|
131517
|
+
STAGED_CREATE_ROUTES = Object.freeze(["bundle-create-begin", "bundle-create-stage", "bundle-create-blob", "bundle-create-commit"]);
|
|
131518
|
+
ANSWER_BYTES = 256 * 1024;
|
|
131519
|
+
BLOBS_IN_FLIGHT = 2;
|
|
131520
|
+
RETRY_DELAYS_MS = Object.freeze([500, 1e3, 2e3, 4e3]);
|
|
131521
|
+
REQUEST_ALLOWANCE = 2e3;
|
|
131522
|
+
REQUESTS_PER_OBJECT = 4;
|
|
131523
|
+
BYTES_PER_CONTENT_BYTE = 3;
|
|
131524
|
+
BYTES_ALLOWANCE = 64 * 1024 * 1024;
|
|
131525
|
+
MAXIMUM_STALLS = 3;
|
|
131526
|
+
CONFLICT_RETRY_MS = 6e4;
|
|
131527
|
+
CONFLICT_RETRIES = 5;
|
|
131528
|
+
StagedRefusal = class extends Error {
|
|
131529
|
+
code;
|
|
131530
|
+
hostMessage;
|
|
131531
|
+
/** True when the creation was reserved: the request id must be kept to finish it. */
|
|
131532
|
+
reserved;
|
|
131533
|
+
/** The route that answered it. */
|
|
131534
|
+
route;
|
|
131535
|
+
constructor(code3, hostMessage, reserved, route) {
|
|
131536
|
+
super(`${code3}: ${hostMessage}`);
|
|
131537
|
+
this.code = code3;
|
|
131538
|
+
this.hostMessage = hostMessage;
|
|
131539
|
+
this.reserved = reserved;
|
|
131540
|
+
this.route = route;
|
|
131541
|
+
}
|
|
131542
|
+
};
|
|
131543
|
+
VERSION_MISMATCH = /version or size is not the manifest's/i;
|
|
131544
|
+
VERSION = /^sha256:[0-9a-f]{64}$/;
|
|
131545
|
+
isStringList = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
130954
131546
|
}
|
|
130955
131547
|
});
|
|
130956
131548
|
|
|
@@ -130963,6 +131555,8 @@ import { parseArgs as parseArgs42 } from "node:util";
|
|
|
130963
131555
|
function publishDeps(partial2) {
|
|
130964
131556
|
return {
|
|
130965
131557
|
stdout: partial2.stdout ?? ((text4) => void process.stdout.write(text4)),
|
|
131558
|
+
stderr: partial2.stderr ?? ((text4) => void process.stderr.write(text4)),
|
|
131559
|
+
...partial2.sleep ? { sleep: partial2.sleep } : {},
|
|
130966
131560
|
auth: partial2.auth ?? defaultHostedAuthDeps(homedir40()),
|
|
130967
131561
|
cwd: partial2.cwd ?? process.cwd(),
|
|
130968
131562
|
...partial2.fetch ? { fetch: partial2.fetch } : {}
|
|
@@ -131058,6 +131652,7 @@ function summary(plan) {
|
|
|
131058
131652
|
documents: plan.documents.length,
|
|
131059
131653
|
reserved_files: plan.reserved.length,
|
|
131060
131654
|
other_files: plan.blobs.length,
|
|
131655
|
+
sent: plan.path.mode === "one-shot" ? "one request" : `staged: ${plan.path.why}; a list of everything, parts of about 1 MiB, each file, then commits until created (resumable)`,
|
|
131061
131656
|
history: { mode: plan.historyPlan.mode, versions: plan.historyPlan.versions, ...plan.historyPlan.skipped > 0 ? { skipped: plan.historyPlan.skipped } : {}, note: plan.historyPlan.note }
|
|
131062
131657
|
};
|
|
131063
131658
|
}
|
|
@@ -131069,11 +131664,17 @@ function createRefusal(code3, message, context) {
|
|
|
131069
131664
|
case "workspace_not_found":
|
|
131070
131665
|
return new CliError("NOT_FOUND", `'${context.workspace}' is not one of your workspaces on ${context.target.origin}`, { details, help: `${cliInvocation()} whoami --host ${commandToken(bindingHostArgument(context.target))}` });
|
|
131071
131666
|
case "bundle_create_unavailable":
|
|
131072
|
-
case "bundle_create_limit":
|
|
131073
|
-
|
|
131074
|
-
|
|
131075
|
-
|
|
131076
|
-
|
|
131667
|
+
case "bundle_create_limit": {
|
|
131668
|
+
const open5 = code3 === "bundle_create_limit" && /staged creations open/i.test(message);
|
|
131669
|
+
return new CliError(
|
|
131670
|
+
"FORBIDDEN",
|
|
131671
|
+
open5 ? `you have too many unfinished large publishes open in ${context.workspace}` : code3 === "bundle_create_limit" ? `your bundle creation limit in ${context.workspace} is used up` : `${context.workspace} does not offer bundle creation to this client`,
|
|
131672
|
+
{
|
|
131673
|
+
details: open5 && context.openCreations ? { ...details, open: context.openCreations.map((record5) => ({ bundle_id: record5.bundle_id, ...record5.folder ? { folder: record5.folder } : {} })) } : details,
|
|
131674
|
+
help: open5 ? `finish one by re-running its publish${context.openCreations && context.openCreations.length > 0 ? ` (unfinished from here: ${context.openCreations.map((record5) => `'${record5.bundle_id}'${record5.folder ? ` from ${record5.folder}` : ""}`).join(", ")})` : ""}, or wait for one to expire 7 days after it began` : "ask a workspace admin in the Superbee app"
|
|
131675
|
+
}
|
|
131676
|
+
);
|
|
131677
|
+
}
|
|
131077
131678
|
case "request_conflict":
|
|
131078
131679
|
return new CliError("CONFLICT", `an unfinished publish of '${context.bundleId}' from this folder carried other contents, and the host holds the id for it`, {
|
|
131079
131680
|
details,
|
|
@@ -131083,6 +131684,103 @@ function createRefusal(code3, message, context) {
|
|
|
131083
131684
|
return new CliError("USAGE", `${context.target.origin} refused the bundle (${code3}): ${message}`, { details, help: "fix the files it names, then preview again" });
|
|
131084
131685
|
}
|
|
131085
131686
|
}
|
|
131687
|
+
function progressLine(event, json2) {
|
|
131688
|
+
if (json2) return `${JSON.stringify({ event: "publish.progress", ...event })}
|
|
131689
|
+
`;
|
|
131690
|
+
const mib = (bytes) => `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
131691
|
+
switch (event.phase) {
|
|
131692
|
+
case "begin":
|
|
131693
|
+
return `publish: ${event.state}: ${event.staged}/${event.versions} objects and ${event.stagedBlobs}/${event.blobs} files on the host
|
|
131694
|
+
`;
|
|
131695
|
+
case "stage":
|
|
131696
|
+
return `publish: sent part ${event.part}/${event.parts} (${event.objects} objects)
|
|
131697
|
+
`;
|
|
131698
|
+
case "blob":
|
|
131699
|
+
return `publish: sent file ${event.blob}/${event.blobs} ${event.key} (${mib(event.bytes)})
|
|
131700
|
+
`;
|
|
131701
|
+
case "commit":
|
|
131702
|
+
return `publish: commit ${event.call}: ${event.state}${event.written ? ` (written: ${Object.entries(event.written).map(([kind2, count5]) => `${count5} ${kind2}`).join(", ")})` : ""}
|
|
131703
|
+
`;
|
|
131704
|
+
case "wait":
|
|
131705
|
+
return `publish: waiting ${event.seconds} s: ${event.reason}
|
|
131706
|
+
`;
|
|
131707
|
+
}
|
|
131708
|
+
}
|
|
131709
|
+
async function sendCreation(plan, context) {
|
|
131710
|
+
const { workspace, bundleId, name, home: home2, canonical: canonical2, target: target2, client, yesCommand } = context;
|
|
131711
|
+
const staged = plan.path.mode === "staged" ? stagedContent(plan) : null;
|
|
131712
|
+
const body = staged ? manifestBody(staged, { workspace, bundleId, name }) : createBody(plan, { workspace, bundleId, name });
|
|
131713
|
+
const digest3 = planDigest(body);
|
|
131714
|
+
const earlier = await readPendingCreate(home2, canonical2, bundleId);
|
|
131715
|
+
const resumes = earlier !== null && earlier.host === target2.origin && earlier.workspace === workspace;
|
|
131716
|
+
const requestId2 = resumes ? earlier.request_id : randomUUID6();
|
|
131717
|
+
const record5 = resumes ? { ...earlier, ...staged ? { staged: true, folder: canonical2 } : {} } : { request_id: requestId2, host: target2.origin, workspace, bundle_id: bundleId, digest: digest3, ...staged ? { staged: true, folder: canonical2 } : {} };
|
|
131718
|
+
if (!resumes || staged && earlier.staged !== true) await writePendingCreate(home2, canonical2, record5);
|
|
131719
|
+
const refusalContext = { bundleId, workspace, target: target2, resume: yesCommand, folder: canonical2 };
|
|
131720
|
+
if (staged) {
|
|
131721
|
+
let data;
|
|
131722
|
+
try {
|
|
131723
|
+
data = await runStagedCreate({
|
|
131724
|
+
client,
|
|
131725
|
+
requestId: requestId2,
|
|
131726
|
+
target: { workspace, bundleId, name },
|
|
131727
|
+
content: staged,
|
|
131728
|
+
resume: String(yesCommand),
|
|
131729
|
+
progress: (event) => context.deps.stderr(progressLine(event, context.json)),
|
|
131730
|
+
...context.deps.sleep ? { sleep: context.deps.sleep } : {},
|
|
131731
|
+
// Once the host holds the id for this request, only this request id can finish it.
|
|
131732
|
+
onReserved: async () => {
|
|
131733
|
+
if (record5.reserved !== true) await writePendingCreate(home2, canonical2, { ...record5, reserved: true });
|
|
131734
|
+
}
|
|
131735
|
+
});
|
|
131736
|
+
} catch (error51) {
|
|
131737
|
+
if (error51 instanceof StagedRefusal) {
|
|
131738
|
+
if (record5.reserved !== true && isFinalBeforeReservation(error51)) await clearPendingCreate(home2, canonical2, bundleId);
|
|
131739
|
+
const openCreations = error51.code === "bundle_create_limit" ? (await listPendingCreates(home2)).filter((other) => other.staged === true && other.host === target2.origin && other.workspace === workspace && other.request_id !== requestId2) : void 0;
|
|
131740
|
+
throw createRefusal(error51.code, error51.hostMessage, { ...refusalContext, ...openCreations ? { openCreations } : {} });
|
|
131741
|
+
}
|
|
131742
|
+
throw error51;
|
|
131743
|
+
}
|
|
131744
|
+
await clearPendingCreate(home2, canonical2, bundleId);
|
|
131745
|
+
return data;
|
|
131746
|
+
}
|
|
131747
|
+
let answer2;
|
|
131748
|
+
try {
|
|
131749
|
+
answer2 = await client.carrier.json(`${client.prefix}/bundle-create`, body, client.signal, { maximum: CREATE_ANSWER_BYTES, writeRequest: requestId2 });
|
|
131750
|
+
} catch (error51) {
|
|
131751
|
+
if (error51 instanceof HostedCarrierError && error51.code === "unavailable") {
|
|
131752
|
+
throw new CliError("TRANSIENT", `the answer from ${target2.origin} did not arrive; the bundle may be partly created`, {
|
|
131753
|
+
details: { reason: "write_outcome_unknown", bundle_id: bundleId, workspace, host: target2.origin, request_id: requestId2, retryable: true },
|
|
131754
|
+
help: `re-run the same command; it re-sends the same request, which finishes or confirms the creation: ${yesCommand}`
|
|
131755
|
+
});
|
|
131756
|
+
}
|
|
131757
|
+
throw hostedFailure(error51, target2, yesCommand);
|
|
131758
|
+
}
|
|
131759
|
+
const envelope = answer2.body ?? {};
|
|
131760
|
+
const code3 = typeof envelope.error?.code === "string" ? envelope.error.code : null;
|
|
131761
|
+
const hostMessage = typeof envelope.error?.message === "string" ? envelope.error.message : "";
|
|
131762
|
+
if (answer2.status === 503 && code3 === "write_outcome_unknown") {
|
|
131763
|
+
throw new CliError("TRANSIENT", `${target2.origin} may have partly created '${bundleId}'`, {
|
|
131764
|
+
details: { reason: "write_outcome_unknown", bundle_id: bundleId, workspace, host: target2.origin, request_id: requestId2, retryable: true },
|
|
131765
|
+
help: `re-run the same command; it re-sends the same request, which finishes or confirms the creation: ${yesCommand}`
|
|
131766
|
+
});
|
|
131767
|
+
}
|
|
131768
|
+
const forgettable = record5.reserved !== true;
|
|
131769
|
+
if (answer2.status === 429 && code3 === "bundle_create_limit") {
|
|
131770
|
+
if (forgettable) await clearPendingCreate(home2, canonical2, bundleId);
|
|
131771
|
+
throw createRefusal(code3, hostMessage, refusalContext);
|
|
131772
|
+
}
|
|
131773
|
+
if (answer2.status === 200 && envelope.ok === false && code3 !== null) {
|
|
131774
|
+
if (code3 !== "request_conflict" && forgettable) await clearPendingCreate(home2, canonical2, bundleId);
|
|
131775
|
+
throw createRefusal(code3, hostMessage, refusalContext);
|
|
131776
|
+
}
|
|
131777
|
+
if (answer2.status !== 200 || envelope.ok !== true || typeof envelope.data !== "object" || envelope.data === null) {
|
|
131778
|
+
if (answer2.status === 400 && forgettable) await clearPendingCreate(home2, canonical2, bundleId);
|
|
131779
|
+
throw hostedFailure(new RemoteError(`hosted bundle-create answered ${answer2.status}`, code3 ?? "RUNTIME", answer2.status), target2, yesCommand);
|
|
131780
|
+
}
|
|
131781
|
+
await clearPendingCreate(home2, canonical2, bundleId);
|
|
131782
|
+
return envelope.data;
|
|
131783
|
+
}
|
|
131086
131784
|
async function publish(argv2, partial2 = {}) {
|
|
131087
131785
|
const deps = publishDeps(partial2);
|
|
131088
131786
|
const { values } = parseLeafOrUsage(
|
|
@@ -131138,13 +131836,15 @@ async function publish(argv2, partial2 = {}) {
|
|
|
131138
131836
|
const withHistory = values["with-history"] === true;
|
|
131139
131837
|
const plan = await planPublish(canonical2, withHistory ? { history: true, ...board ? { board } : {}, now: deps.auth.now() } : { history: false });
|
|
131140
131838
|
const yesCommand = commandFragment`${cliInvocation()} publish --to hosted${values.dir !== void 0 ? commandFragment` --dir ${commandToken(canonical2)}` : commandFragment``}${target2 ? commandFragment` --host ${commandToken(bindingHostArgument(target2))}` : commandFragment``}${values.workspace !== void 0 ? commandFragment` --workspace ${commandToken(values.workspace)}` : commandFragment``} --bundle-id ${commandToken(bundleId)}${values.name !== void 0 ? commandFragment` --name ${commandToken(name)}` : commandFragment``}${withHistory ? commandFragment` --with-history` : commandFragment``} --yes${values.json ? commandFragment` --json` : commandFragment``}`;
|
|
131839
|
+
const converts = plan.documents.length <= CHECKOUT_DOCUMENT_LIMIT;
|
|
131840
|
+
const uncheckable = `leave this folder as it is${board ? " (still bound to the Git board)" : ""}: a hosted checkout holds at most ${CHECKOUT_DOCUMENT_LIMIT} documents, so use the bundle in the app; from then on, edits here do not reach the hosted bundle, nor its edits here`;
|
|
131141
131841
|
const gitPlan = board ? {
|
|
131142
131842
|
branch: board.branch,
|
|
131143
131843
|
upstream: board.upstream,
|
|
131144
131844
|
head: boardHead(board),
|
|
131145
131845
|
sync: boardState?.block.state,
|
|
131146
131846
|
...boardState && (boardState.block.ahead ?? 0) + (boardState.block.uncommitted ?? 0) > 0 ? { not_on_branch: { ahead: boardState.block.ahead, uncommitted: boardState.block.uncommitted, note: "these travel to hosted but not to the board branch teammates still sync" } } : {},
|
|
131147
|
-
will: "unbind this folder from the board branch; the branch and its commits stay, locally and on origin"
|
|
131847
|
+
will: converts ? "unbind this folder from the board branch; the branch and its commits stay, locally and on origin" : "keep this folder bound to the board branch: the board and the hosted bundle then diverge, and neither's edits reach the other"
|
|
131148
131848
|
} : null;
|
|
131149
131849
|
if (!values.yes) {
|
|
131150
131850
|
const blockers = [...plan.blockers.map((b) => ({ path: b.path, reason: b.reason, message: b.message })), ...boardState?.blocker ? [boardState.blocker] : []];
|
|
@@ -131168,8 +131868,10 @@ async function publish(argv2, partial2 = {}) {
|
|
|
131168
131868
|
...gitPlan ? { git: gitPlan } : {},
|
|
131169
131869
|
then: [
|
|
131170
131870
|
"create the bundle in your workspace, reachable only by you (write) until you share it in the app",
|
|
131171
|
-
|
|
131172
|
-
|
|
131871
|
+
...converts ? [
|
|
131872
|
+
"convert this folder in place into a hosted checkout: no file rewritten, a read-only .superbee/checkout.json added",
|
|
131873
|
+
...board ? ["unbind the Git board first; teammates keep the board branch until you tell them"] : []
|
|
131874
|
+
] : [uncheckable]
|
|
131173
131875
|
],
|
|
131174
131876
|
network: "none (preview)",
|
|
131175
131877
|
help: blockers.length === 0 ? [String(yesCommand)] : !target2 ? [`${cliInvocation()} login --host <url>`] : ["fix the blocking files, then preview again"]
|
|
@@ -131206,48 +131908,34 @@ async function publish(argv2, partial2 = {}) {
|
|
|
131206
131908
|
help: otherWorkspace
|
|
131207
131909
|
});
|
|
131208
131910
|
}
|
|
131209
|
-
const
|
|
131210
|
-
|
|
131211
|
-
|
|
131212
|
-
|
|
131213
|
-
|
|
131214
|
-
|
|
131215
|
-
|
|
131216
|
-
|
|
131217
|
-
|
|
131218
|
-
|
|
131219
|
-
|
|
131220
|
-
|
|
131221
|
-
|
|
131222
|
-
|
|
131223
|
-
|
|
131224
|
-
|
|
131225
|
-
|
|
131226
|
-
|
|
131227
|
-
|
|
131228
|
-
|
|
131229
|
-
|
|
131230
|
-
|
|
131231
|
-
|
|
131232
|
-
|
|
131233
|
-
|
|
131234
|
-
|
|
131235
|
-
|
|
131236
|
-
}
|
|
131237
|
-
if (answer2.status === 429 && code3 === "bundle_create_limit") {
|
|
131238
|
-
await clearPendingCreate(home2, canonical2, bundleId);
|
|
131239
|
-
throw createRefusal(code3, hostMessage, context);
|
|
131240
|
-
}
|
|
131241
|
-
if (answer2.status === 200 && envelope.ok === false && code3 !== null) {
|
|
131242
|
-
if (code3 !== "request_conflict") await clearPendingCreate(home2, canonical2, bundleId);
|
|
131243
|
-
throw createRefusal(code3, hostMessage, context);
|
|
131244
|
-
}
|
|
131245
|
-
if (answer2.status !== 200 || envelope.ok !== true || typeof envelope.data !== "object" || envelope.data === null) {
|
|
131246
|
-
if (answer2.status === 400) await clearPendingCreate(home2, canonical2, bundleId);
|
|
131247
|
-
throw hostedFailure(new RemoteError(`hosted bundle-create answered ${answer2.status}`, code3 ?? "RUNTIME", answer2.status), target2, yesCommand);
|
|
131911
|
+
const created = await sendCreation(plan, { workspace, bundleId, name, home: home2, canonical: canonical2, target: target2, client, yesCommand, deps, json: values.json === true });
|
|
131912
|
+
if (!converts) {
|
|
131913
|
+
deps.stdout(
|
|
131914
|
+
render(
|
|
131915
|
+
{
|
|
131916
|
+
published: "created",
|
|
131917
|
+
bundle_id: bundleId,
|
|
131918
|
+
name,
|
|
131919
|
+
host: target2.origin,
|
|
131920
|
+
workspace,
|
|
131921
|
+
access: "write (only you, until you share it in the app)",
|
|
131922
|
+
sent: {
|
|
131923
|
+
documents: created.documents ?? plan.documents.length,
|
|
131924
|
+
reserved_files: created.reserved ?? plan.reserved.length,
|
|
131925
|
+
other_files: created.blobs ?? plan.blobs.length,
|
|
131926
|
+
history: created.history ?? { imported: plan.history.length, verified: false }
|
|
131927
|
+
},
|
|
131928
|
+
folder: canonical2,
|
|
131929
|
+
home: facts.home,
|
|
131930
|
+
checkout: `not converted: ${uncheckable}`,
|
|
131931
|
+
diverges: `this folder${board ? " and its Git board" : ""} and the hosted bundle '${bundleId}' are now separate copies: edits here do not reach the hosted bundle`,
|
|
131932
|
+
help: [hostedListCommand(target2)]
|
|
131933
|
+
},
|
|
131934
|
+
mode
|
|
131935
|
+
)
|
|
131936
|
+
);
|
|
131937
|
+
return;
|
|
131248
131938
|
}
|
|
131249
|
-
await clearPendingCreate(home2, canonical2, bundleId);
|
|
131250
|
-
const created = envelope.data;
|
|
131251
131939
|
await writePublishedExtras(home2, canonical2, { host: target2.origin, bundle_id: bundleId, extras: plan.extras }).catch(() => {
|
|
131252
131940
|
});
|
|
131253
131941
|
const slug = identity.workspaces.find((w2) => w2.tenantId === workspace)?.slug ?? null;
|
|
@@ -131348,6 +132036,7 @@ var init_publish = __esm({
|
|
|
131348
132036
|
init_account();
|
|
131349
132037
|
init_marker();
|
|
131350
132038
|
init_publish_plan();
|
|
132039
|
+
init_publish_staged();
|
|
131351
132040
|
init_publish_state();
|
|
131352
132041
|
init_invocation();
|
|
131353
132042
|
init_output2();
|
|
@@ -131361,9 +132050,16 @@ Usage:
|
|
|
131361
132050
|
[--bundle-id <id>] [--name <name>] [--with-history] [--yes] [--json]
|
|
131362
132051
|
|
|
131363
132052
|
Without --yes, previews only, with no network: what travels (documents, reserved files, other
|
|
131364
|
-
files), what stays (dot-files, links), every bound the host enforces
|
|
131365
|
-
|
|
131366
|
-
|
|
132053
|
+
files), what stays (dot-files, links), how it is sent, every bound the host enforces, the history
|
|
132054
|
+
plan, and what will happen to the folder.
|
|
132055
|
+
|
|
132056
|
+
A bundle within one request's bounds (at most 1,000 documents, 100 other files of 1 MB each,
|
|
132057
|
+
1,000 earlier versions, 1,500 objects in all, 3 MiB) is sent in one request. A larger one is sent
|
|
132058
|
+
staged: a list of everything, then parts of about 1 MiB, then each file, then commits until the
|
|
132059
|
+
host has created it, with progress on stderr (JSON lines with --json). Staged, a bundle holds at
|
|
132060
|
+
most 10,000 documents, 1,000 reserved files, 1,000 other files of 16 MiB each and 64 MiB of
|
|
132061
|
+
current files in all, and 5,000 earlier versions (64 MiB); the list of everything is at most
|
|
132062
|
+
3 MiB. Every way: 64 KiB per document and reserved file, 16 KiB of frontmatter.
|
|
131367
132063
|
|
|
131368
132064
|
With --yes, signs in if needed (AUTH_REQUIRED, exit 4, carries the one link to relay and the
|
|
131369
132065
|
command to re-run), creates the bundle in your workspace (only you can reach it, at write, until
|
|
@@ -131378,7 +132074,7 @@ branch, or any other folder that is its own Git working tree, is refused.
|
|
|
131378
132074
|
--with-history imports each document's earlier Git versions as labeled, unverified history
|
|
131379
132075
|
(imported:git/<commit>); without it, history starts at publish. A local bundle has only its current
|
|
131380
132076
|
versions. A retry after an unknown outcome (TRANSIENT) re-sends the same request, so the host
|
|
131381
|
-
finishes or confirms the one creation.
|
|
132077
|
+
finishes or confirms the one creation; a staged one resumes, sending only what the host lacks.
|
|
131382
132078
|
|
|
131383
132079
|
Options:
|
|
131384
132080
|
--to hosted Required: the only destination
|