@world-engines/project-host 0.1.0-alpha.0
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/LICENSE +46 -0
- package/assets/gui/WELogo-title.svg +11 -0
- package/assets/gui/app.js +1326 -0
- package/assets/gui/index.html +118 -0
- package/assets/gui/local-preview-asset-urls.js +61 -0
- package/assets/gui/local-preview-bridge.js +393 -0
- package/assets/gui/spatial-template-panel.js +233 -0
- package/assets/gui/styles.css +115 -0
- package/assets/gui/terrain-render.js +120 -0
- package/assets/gui/workspace-status.css +17 -0
- package/assets/gui/workspace-status.js +255 -0
- package/assets/gui/world-map-globe.js +187 -0
- package/assets/gui/world-map-worker.js +62 -0
- package/assets/gui/world-map.css +30 -0
- package/assets/gui/world-map.js +2466 -0
- package/dist/account-model-proxy.d.ts +21 -0
- package/dist/account-model-proxy.js +470 -0
- package/dist/authoring-browser-runtime.d.ts +7 -0
- package/dist/authoring-browser-runtime.js +110 -0
- package/dist/authoring-ingestion.d.ts +38 -0
- package/dist/authoring-ingestion.js +410 -0
- package/dist/authoring-model-connection.d.ts +166 -0
- package/dist/authoring-model-connection.js +757 -0
- package/dist/chinese-lexical.d.ts +152 -0
- package/dist/chinese-lexical.js +555 -0
- package/dist/chromium-preview-browser-driver.d.ts +43 -0
- package/dist/chromium-preview-browser-driver.js +575 -0
- package/dist/cli/download.d.ts +12 -0
- package/dist/cli/download.js +50 -0
- package/dist/cli/dump.d.ts +32 -0
- package/dist/cli/dump.js +91 -0
- package/dist/cli/ingest.d.ts +49 -0
- package/dist/cli/ingest.js +225 -0
- package/dist/cli/review.d.ts +10 -0
- package/dist/cli/review.js +44 -0
- package/dist/cli/upload.d.ts +57 -0
- package/dist/cli/upload.js +112 -0
- package/dist/cli-command.d.ts +60 -0
- package/dist/cli-command.js +613 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +182 -0
- package/dist/desktop-authoring-runtime.d.ts +88 -0
- package/dist/desktop-authoring-runtime.js +1602 -0
- package/dist/desktop-sidecar.d.ts +11 -0
- package/dist/desktop-sidecar.js +143 -0
- package/dist/document-dump.d.ts +113 -0
- package/dist/document-dump.js +431 -0
- package/dist/document-graph-extractor.d.ts +125 -0
- package/dist/document-graph-extractor.js +588 -0
- package/dist/document-ingestion-journal.d.ts +68 -0
- package/dist/document-ingestion-journal.js +471 -0
- package/dist/document-ingestion.d.ts +183 -0
- package/dist/document-ingestion.js +976 -0
- package/dist/document-query-path-proof.d.ts +30 -0
- package/dist/document-query-path-proof.js +34 -0
- package/dist/document-query-path-renderer.d.ts +50 -0
- package/dist/document-query-path-renderer.js +75 -0
- package/dist/document-query-proof-paths.d.ts +22 -0
- package/dist/document-query-proof-paths.js +71 -0
- package/dist/document-query.d.ts +97 -0
- package/dist/document-query.js +1106 -0
- package/dist/document-relation-role.d.ts +14 -0
- package/dist/document-relation-role.js +86 -0
- package/dist/document-source.d.ts +107 -0
- package/dist/document-source.js +255 -0
- package/dist/embedding-cache.d.ts +35 -0
- package/dist/embedding-cache.js +317 -0
- package/dist/frozen-review-local-pipeline.d.ts +88 -0
- package/dist/frozen-review-local-pipeline.js +609 -0
- package/dist/frozen-review-source-capture.d.ts +15 -0
- package/dist/frozen-review-source-capture.js +551 -0
- package/dist/frozen-review-wasm-encryptor.d.ts +16 -0
- package/dist/frozen-review-wasm-encryptor.js +72 -0
- package/dist/frozen-review.d.ts +131 -0
- package/dist/frozen-review.js +802 -0
- package/dist/graph-traversal.d.ts +23 -0
- package/dist/graph-traversal.js +100 -0
- package/dist/gui/graph-layout-worker.js +1 -0
- package/dist/gui/graph-layout.wasm +0 -0
- package/dist/gui/graph.css +1 -0
- package/dist/gui/graph.js +76 -0
- package/dist/gui/plane-generation.js +112 -0
- package/dist/gui/plane-generator.js +310 -0
- package/dist/gui/plane-generator_bg.wasm +0 -0
- package/dist/gui/procedural-city.js +595 -0
- package/dist/gui/road-generation.js +129 -0
- package/dist/gui/road-parcels.js +1584 -0
- package/dist/gui/score.css +1 -0
- package/dist/gui/score.js +54 -0
- package/dist/gui/spatial-runtime.js +1231 -0
- package/dist/gui/spatial-templates.js +200 -0
- package/dist/gui/trigger.css +1 -0
- package/dist/gui/trigger.js +142 -0
- package/dist/harness-callback-transport.d.ts +17 -0
- package/dist/harness-callback-transport.js +128 -0
- package/dist/harness-document-ingestion.d.ts +77 -0
- package/dist/harness-document-ingestion.js +343 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +43 -0
- package/dist/ingestion-source-reconciliation.d.ts +88 -0
- package/dist/ingestion-source-reconciliation.js +253 -0
- package/dist/ladybug-runtime.d.ts +117 -0
- package/dist/ladybug-runtime.js +886 -0
- package/dist/local-account-embedding.d.ts +34 -0
- package/dist/local-account-embedding.js +294 -0
- package/dist/local-gallery.d.ts +35 -0
- package/dist/local-gallery.js +227 -0
- package/dist/local-gui-auth.d.ts +96 -0
- package/dist/local-gui-auth.js +703 -0
- package/dist/local-gui-contract.d.ts +53 -0
- package/dist/local-gui-contract.js +5 -0
- package/dist/local-gui-image-assets.d.ts +17 -0
- package/dist/local-gui-image-assets.js +96 -0
- package/dist/local-gui-preview-client.d.ts +3 -0
- package/dist/local-gui-preview-client.js +111 -0
- package/dist/local-gui-score.d.ts +72 -0
- package/dist/local-gui-score.js +184 -0
- package/dist/local-gui-server.d.ts +10 -0
- package/dist/local-gui-server.js +987 -0
- package/dist/local-gui-version.d.ts +17 -0
- package/dist/local-gui-version.js +147 -0
- package/dist/local-gui-workspace.d.ts +14 -0
- package/dist/local-gui-workspace.js +171 -0
- package/dist/local-model-settings.d.ts +28 -0
- package/dist/local-model-settings.js +124 -0
- package/dist/local-preview-assets.d.ts +28 -0
- package/dist/local-preview-assets.js +124 -0
- package/dist/local-preview-bound-resources.d.ts +12 -0
- package/dist/local-preview-bound-resources.js +80 -0
- package/dist/local-preview-message-store.d.ts +63 -0
- package/dist/local-preview-message-store.js +214 -0
- package/dist/local-preview-player-read-models.d.ts +47 -0
- package/dist/local-preview-player-read-models.js +90 -0
- package/dist/local-preview-projections.d.ts +61 -0
- package/dist/local-preview-projections.js +117 -0
- package/dist/local-preview-runtime.d.ts +105 -0
- package/dist/local-preview-runtime.js +553 -0
- package/dist/local-preview-session.d.ts +73 -0
- package/dist/local-preview-session.js +395 -0
- package/dist/local-preview-source.d.ts +40 -0
- package/dist/local-preview-source.js +192 -0
- package/dist/local-preview-view-events.d.ts +101 -0
- package/dist/local-preview-view-events.js +179 -0
- package/dist/local-preview-view-state-sqlite.d.ts +24 -0
- package/dist/local-preview-view-state-sqlite.js +263 -0
- package/dist/local-preview-view-state.d.ts +93 -0
- package/dist/local-preview-view-state.js +108 -0
- package/dist/local-preview-vite-config.d.ts +6 -0
- package/dist/local-preview-vite-config.js +122 -0
- package/dist/local-preview-world-effects.d.ts +9 -0
- package/dist/local-preview-world-effects.js +55 -0
- package/dist/local-preview-worldline-store.d.ts +47 -0
- package/dist/local-preview-worldline-store.js +282 -0
- package/dist/local-preview-worldlines.d.ts +95 -0
- package/dist/local-preview-worldlines.js +136 -0
- package/dist/mcp-async-dispatcher.d.ts +3 -0
- package/dist/mcp-async-dispatcher.js +129 -0
- package/dist/mcp-line-queue.d.ts +14 -0
- package/dist/mcp-line-queue.js +184 -0
- package/dist/mcp-pipe-internal.d.ts +6 -0
- package/dist/mcp-pipe-internal.js +151 -0
- package/dist/mcp-pipe-response.d.ts +14 -0
- package/dist/mcp-pipe-response.js +50 -0
- package/dist/mcp-stdio-response.d.ts +12 -0
- package/dist/mcp-stdio-response.js +27 -0
- package/dist/mcp.d.ts +27 -0
- package/dist/mcp.js +194 -0
- package/dist/prepared-embedding-cache.d.ts +38 -0
- package/dist/prepared-embedding-cache.js +238 -0
- package/dist/preview-browser-adapter.d.ts +53 -0
- package/dist/preview-browser-adapter.js +171 -0
- package/dist/preview-runtime.d.ts +67 -0
- package/dist/preview-runtime.js +460 -0
- package/dist/project-auth-transport.d.ts +27 -0
- package/dist/project-auth-transport.js +121 -0
- package/dist/project-auth.d.ts +51 -0
- package/dist/project-auth.js +108 -0
- package/dist/project-git-capture-recovery.d.ts +16 -0
- package/dist/project-git-capture-recovery.js +347 -0
- package/dist/project-git-cli.d.ts +5 -0
- package/dist/project-git-cli.js +40 -0
- package/dist/project-git-scenario-source.d.ts +10 -0
- package/dist/project-git-scenario-source.js +240 -0
- package/dist/project-git-view-source.d.ts +13 -0
- package/dist/project-git-view-source.js +134 -0
- package/dist/project-git.d.ts +97 -0
- package/dist/project-git.js +656 -0
- package/dist/project-host.d.ts +59 -0
- package/dist/project-host.js +471 -0
- package/dist/project-writer-lease.d.ts +18 -0
- package/dist/project-writer-lease.js +430 -0
- package/dist/public-error.d.ts +7 -0
- package/dist/public-error.js +61 -0
- package/dist/retrieval-benchmark.d.ts +46 -0
- package/dist/retrieval-benchmark.js +58 -0
- package/dist/safe-project-path.d.ts +8 -0
- package/dist/safe-project-path.js +93 -0
- package/dist/scenario-writer.d.ts +32 -0
- package/dist/scenario-writer.js +191 -0
- package/dist/transfer/download.d.ts +159 -0
- package/dist/transfer/download.js +598 -0
- package/dist/transfer/transport.d.ts +26 -0
- package/dist/transfer/transport.js +1 -0
- package/dist/transfer/upload.d.ts +162 -0
- package/dist/transfer/upload.js +354 -0
- package/dist/transfer-types.d.ts +95 -0
- package/dist/transfer-types.js +1 -0
- package/dist/transfer.d.ts +18 -0
- package/dist/transfer.js +273 -0
- package/dist/trigger-runtime.d.ts +121 -0
- package/dist/trigger-runtime.js +153 -0
- package/dist/vendor/wasm-core/asset-hashes.json +5 -0
- package/dist/vendor/wasm-core/wasm_core.d.ts +490 -0
- package/dist/vendor/wasm-core/wasm_core.js +1 -0
- package/dist/vendor/wasm-core/wasm_core_bg.wasm +0 -0
- package/dist/view-watcher.d.ts +9 -0
- package/dist/view-watcher.js +34 -0
- package/package.json +110 -0
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { mkdir, mkdtemp, readFile, realpath, rename, rm, symlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, isAbsolute, join, normalize, relative, sep } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { gunzip } from "node:zlib";
|
|
7
|
+
import { captureNeutralSourceCompileInput, } from "@world-engines/scenario-author-source";
|
|
8
|
+
import { canonicalJson, assembleNeutralFrozenPublishInput, encodeNeutralReviewDatabaseSnapshot, materializeNeutralArtifactCompilation, } from "@world-engines/scenario-review-snapshot";
|
|
9
|
+
import { CURRENT_NEUTRAL_COMPILER_STAGE_VERSIONS, InMemoryCompilerStageCache, buildCurrentTmw3NeutralCompilerStageRegistry, compileTargetProfileDigest, parseCompileTargetProfile, compileNeutralScenarioSource, requireActiveCompilerRelease, } from "@world-engines/tmw";
|
|
10
|
+
import { AuthoringBridgeError } from "@world-engines/authoring-bridge";
|
|
11
|
+
import { ensureSafeProjectDirectory, safeProjectPath } from "./safe-project-path.js";
|
|
12
|
+
import { createFrozenReviewWasmEncryptor, } from "./frozen-review-wasm-encryptor.js";
|
|
13
|
+
const SHA256_PATTERN = /^[0-9a-f]{64}$/u;
|
|
14
|
+
const REVIEW_TARGET_PATH = ".worldengine/review-target.json";
|
|
15
|
+
const compilerCache = new InMemoryCompilerStageCache();
|
|
16
|
+
const execFileAsync = promisify(execFile);
|
|
17
|
+
const gunzipAsync = promisify(gunzip);
|
|
18
|
+
const MAX_PUBLISH_HTML_BYTES = 10 * 1024 * 1024;
|
|
19
|
+
const MAX_VIEW_ARCHIVE_BYTES = 32 * 1024 * 1024;
|
|
20
|
+
const MAX_VIEW_UNPACKED_BYTES = 96 * 1024 * 1024;
|
|
21
|
+
const MAX_VIEW_ENTRY_BYTES = 16 * 1024 * 1024;
|
|
22
|
+
const MAX_VIEW_FILES = 20_000;
|
|
23
|
+
const encodeCompactReviewDatabase = encodeNeutralReviewDatabaseSnapshot;
|
|
24
|
+
export function createFrozenReviewEmbeddingBatch(embedding) {
|
|
25
|
+
return async (request) => {
|
|
26
|
+
if (request.entities.length === 0)
|
|
27
|
+
fail("E_VALIDATION_FAILED", "review embedding entity 为空");
|
|
28
|
+
const vectors = await embedding.embed(request.entities.map((entity) => entity.source_text), request.signal === undefined ? undefined : { signal: request.signal });
|
|
29
|
+
if (vectors.length !== request.entities.length
|
|
30
|
+
|| vectors.some((vector) => vector.length !== embedding.dimensions || vector.some((value) => !Number.isFinite(value)))) {
|
|
31
|
+
fail("E_VALIDATION_FAILED", "review embedding provider result 无效");
|
|
32
|
+
}
|
|
33
|
+
const descriptorBase = Object.freeze({
|
|
34
|
+
provider: requireText(embedding.providerId, "embedding provider"),
|
|
35
|
+
model: requireText(embedding.modelId, "embedding model"),
|
|
36
|
+
dimension: requirePositive(embedding.dimensions, "embedding dimension"),
|
|
37
|
+
source_revision: request.source.source_revision,
|
|
38
|
+
source_revision_digest: request.source.revision_digest,
|
|
39
|
+
});
|
|
40
|
+
const descriptor = Object.freeze({
|
|
41
|
+
...descriptorBase,
|
|
42
|
+
descriptor_sha256: createHash("sha256").update(canonicalJson(descriptorBase)).digest("hex"),
|
|
43
|
+
});
|
|
44
|
+
return Object.freeze({
|
|
45
|
+
descriptor,
|
|
46
|
+
entries: Object.freeze(request.entities.map((entity, index) => Object.freeze({
|
|
47
|
+
entity_id: entity.entity_id,
|
|
48
|
+
source_kind: entity.source_kind,
|
|
49
|
+
source_text_sha256: entity.source_text_sha256,
|
|
50
|
+
vector: Object.freeze([...vectors[index]]),
|
|
51
|
+
}))),
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** 与 Creator 共读 Edge CompilerReleaseAuthority;transport 持有认证,CLI 不接触 cookie/token。 */
|
|
56
|
+
export function createCompilerReleaseProfileAdmission(transport, projectId) {
|
|
57
|
+
const read = async () => {
|
|
58
|
+
const response = await transport.request(Object.freeze({
|
|
59
|
+
project_id: projectId,
|
|
60
|
+
method: "GET",
|
|
61
|
+
path: "/v1/compiler-releases/current",
|
|
62
|
+
}));
|
|
63
|
+
if (response.status !== 200) {
|
|
64
|
+
fail("E_AUTH_REQUIRED", `Compiler release authority readback 失败:${response.status}`);
|
|
65
|
+
}
|
|
66
|
+
if (typeof response.body !== "object" || response.body === null || Array.isArray(response.body)) {
|
|
67
|
+
fail("E_VALIDATION_FAILED", "Compiler release authority readback 无效");
|
|
68
|
+
}
|
|
69
|
+
const envelope = response.body;
|
|
70
|
+
if (envelope.schema_version !== 1 || !Number.isSafeInteger(envelope.revision)) {
|
|
71
|
+
fail("E_VALIDATION_FAILED", "Compiler release authority revision 无效");
|
|
72
|
+
}
|
|
73
|
+
const parseIdentity = async (value) => {
|
|
74
|
+
if (value === null || value === undefined)
|
|
75
|
+
return null;
|
|
76
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
77
|
+
fail("E_VALIDATION_FAILED", "Compiler release authority identity 无效");
|
|
78
|
+
}
|
|
79
|
+
const identity = value;
|
|
80
|
+
const target_profile = parseCompileTargetProfile(identity.target_profile);
|
|
81
|
+
return Object.freeze({ ...identity, target_profile });
|
|
82
|
+
};
|
|
83
|
+
return Object.freeze({
|
|
84
|
+
active: await parseIdentity(envelope.active),
|
|
85
|
+
previous_active: await parseIdentity(envelope.previous_active),
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
return async (profile) => {
|
|
89
|
+
const snapshot = await read();
|
|
90
|
+
if (snapshot.active !== null
|
|
91
|
+
&& await compileTargetProfileDigest(snapshot.active.target_profile) !== snapshot.active.target_profile_sha256) {
|
|
92
|
+
fail("E_VALIDATION_FAILED", "Compiler release authority profile digest 不匹配");
|
|
93
|
+
}
|
|
94
|
+
await requireActiveCompilerRelease({
|
|
95
|
+
read: async () => snapshot,
|
|
96
|
+
writeOnce: async () => { throw new Error("compiler_release_readback_store_read_only"); },
|
|
97
|
+
}, profile);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function fail(code, message) {
|
|
101
|
+
throw new AuthoringBridgeError(code, message);
|
|
102
|
+
}
|
|
103
|
+
function sha256(bytes) {
|
|
104
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
105
|
+
}
|
|
106
|
+
function requireSha(value, field) {
|
|
107
|
+
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
108
|
+
fail("E_VALIDATION_FAILED", `${field} digest 无效`);
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
function requireText(value, field) {
|
|
113
|
+
if (typeof value !== "string" || value.length === 0 || value !== value.trim().normalize("NFC")) {
|
|
114
|
+
fail("E_VALIDATION_FAILED", `${field} identity 无效`);
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
function requirePositive(value, field) {
|
|
119
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
120
|
+
fail("E_VALIDATION_FAILED", `${field} 无效`);
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
function object(value, field) {
|
|
125
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
126
|
+
fail("E_VALIDATION_FAILED", `${field} response 无效`);
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
export async function readFrozenReviewTargetBinding(root, expectedProjectId) {
|
|
131
|
+
const path = await safeProjectPath(root, REVIEW_TARGET_PATH);
|
|
132
|
+
let value;
|
|
133
|
+
try {
|
|
134
|
+
value = JSON.parse(await readFile(path, "utf8"));
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return fail("E_VALIDATION_FAILED", `${REVIEW_TARGET_PATH} 缺失或无效;先完成 Neutral source upload binding`);
|
|
138
|
+
}
|
|
139
|
+
const record = object(value, "review target");
|
|
140
|
+
const keys = Object.keys(record).sort();
|
|
141
|
+
if (keys.join("\0") !== ["project_id", "scenario_id", "schema_version", "source_digest", "source_head_digest"].sort().join("\0")
|
|
142
|
+
|| record.schema_version !== 1
|
|
143
|
+
|| record.project_id !== expectedProjectId) {
|
|
144
|
+
fail("E_OPERATION_ID_CONFLICT", "review target 未绑定当前 project");
|
|
145
|
+
}
|
|
146
|
+
return Object.freeze({
|
|
147
|
+
schema_version: 1,
|
|
148
|
+
project_id: expectedProjectId,
|
|
149
|
+
scenario_id: requireText(record.scenario_id, "review target scenario"),
|
|
150
|
+
source_digest: requireSha(record.source_digest, "review target source"),
|
|
151
|
+
source_head_digest: requireSha(record.source_head_digest, "review target head"),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function tarText(bytes) {
|
|
155
|
+
const end = bytes.indexOf(0);
|
|
156
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(end < 0 ? bytes : bytes.subarray(0, end));
|
|
157
|
+
}
|
|
158
|
+
function tarOctal(bytes, field) {
|
|
159
|
+
const value = tarText(bytes).trim().replace(/^0+/u, "");
|
|
160
|
+
if (value === "")
|
|
161
|
+
return 0;
|
|
162
|
+
if (!/^[0-7]+$/u.test(value))
|
|
163
|
+
fail("E_VALIDATION_FAILED", `view tar ${field} 无效`);
|
|
164
|
+
const parsed = Number.parseInt(value, 8);
|
|
165
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0)
|
|
166
|
+
fail("E_VALIDATION_FAILED", `view tar ${field} 越界`);
|
|
167
|
+
return parsed;
|
|
168
|
+
}
|
|
169
|
+
function canonicalTarPath(raw) {
|
|
170
|
+
if (raw.length === 0 || raw.includes("\\") || raw.includes("\0") || isAbsolute(raw)) {
|
|
171
|
+
return fail("E_VALIDATION_FAILED", "view tar path 无效");
|
|
172
|
+
}
|
|
173
|
+
const path = normalize(raw).split(sep).join("/");
|
|
174
|
+
if (path === "." || path.startsWith("../") || path.includes("/../") || path.startsWith("/")) {
|
|
175
|
+
return fail("E_VALIDATION_FAILED", "view tar path 越界");
|
|
176
|
+
}
|
|
177
|
+
return path.replace(/^\.\//u, "");
|
|
178
|
+
}
|
|
179
|
+
async function unpackCapturedViewProject(canonicalGzipTar) {
|
|
180
|
+
if (canonicalGzipTar.byteLength === 0 || canonicalGzipTar.byteLength > MAX_VIEW_ARCHIVE_BYTES) {
|
|
181
|
+
fail("E_VALIDATION_FAILED", "view archive size 无效");
|
|
182
|
+
}
|
|
183
|
+
let tar = canonicalGzipTar;
|
|
184
|
+
if (canonicalGzipTar[0] === 0x1f && canonicalGzipTar[1] === 0x8b) {
|
|
185
|
+
try {
|
|
186
|
+
tar = new Uint8Array(await gunzipAsync(canonicalGzipTar, { maxOutputLength: MAX_VIEW_UNPACKED_BYTES }));
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return fail("E_VALIDATION_FAILED", "view gzip 无法解压");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (tar.byteLength === 0 || tar.byteLength > MAX_VIEW_UNPACKED_BYTES) {
|
|
193
|
+
fail("E_VALIDATION_FAILED", "view tar size 无效");
|
|
194
|
+
}
|
|
195
|
+
const files = new Map();
|
|
196
|
+
let totalBytes = 0;
|
|
197
|
+
let offset = 0;
|
|
198
|
+
while (offset + 512 <= tar.byteLength) {
|
|
199
|
+
const header = tar.subarray(offset, offset + 512);
|
|
200
|
+
if (header.every((byte) => byte === 0))
|
|
201
|
+
break;
|
|
202
|
+
const storedChecksum = tarOctal(header.subarray(148, 156), "checksum");
|
|
203
|
+
let checksum = 0;
|
|
204
|
+
for (let index = 0; index < header.length; index += 1) {
|
|
205
|
+
checksum += index >= 148 && index < 156 ? 0x20 : header[index];
|
|
206
|
+
}
|
|
207
|
+
if (checksum !== storedChecksum)
|
|
208
|
+
fail("E_VALIDATION_FAILED", "view tar checksum 不匹配");
|
|
209
|
+
const name = tarText(header.subarray(0, 100));
|
|
210
|
+
const prefix = tarText(header.subarray(345, 500));
|
|
211
|
+
const path = canonicalTarPath(prefix.length === 0 ? name : `${prefix}/${name}`);
|
|
212
|
+
const size = tarOctal(header.subarray(124, 136), "size");
|
|
213
|
+
if (size > MAX_VIEW_ENTRY_BYTES)
|
|
214
|
+
fail("E_VALIDATION_FAILED", "view tar entry 过大");
|
|
215
|
+
const type = header[156] ?? 0;
|
|
216
|
+
const contentStart = offset + 512;
|
|
217
|
+
const contentEnd = contentStart + size;
|
|
218
|
+
if (contentEnd > tar.byteLength)
|
|
219
|
+
fail("E_VALIDATION_FAILED", "view tar 被截断");
|
|
220
|
+
if (type === 0 || type === 0x30) {
|
|
221
|
+
if (files.has(path))
|
|
222
|
+
fail("E_VALIDATION_FAILED", `view tar path 重复:${path}`);
|
|
223
|
+
if (files.size >= MAX_VIEW_FILES)
|
|
224
|
+
fail("E_VALIDATION_FAILED", "view tar files 过多");
|
|
225
|
+
totalBytes += size;
|
|
226
|
+
if (!Number.isSafeInteger(totalBytes) || totalBytes > MAX_VIEW_UNPACKED_BYTES) {
|
|
227
|
+
fail("E_VALIDATION_FAILED", "view tar total size 过大");
|
|
228
|
+
}
|
|
229
|
+
files.set(path, tar.slice(contentStart, contentEnd));
|
|
230
|
+
}
|
|
231
|
+
else if (type !== 0x35) {
|
|
232
|
+
fail("E_VALIDATION_FAILED", "view tar 禁止 link/device entry");
|
|
233
|
+
}
|
|
234
|
+
offset = contentStart + Math.ceil(size / 512) * 512;
|
|
235
|
+
}
|
|
236
|
+
if (files.size === 0)
|
|
237
|
+
fail("E_VALIDATION_FAILED", "view project 为空");
|
|
238
|
+
return files;
|
|
239
|
+
}
|
|
240
|
+
async function materializeCapturedViewProject(root, project) {
|
|
241
|
+
for (const [path, bytes] of project) {
|
|
242
|
+
const target = join(root, ...canonicalTarPath(path).split("/"));
|
|
243
|
+
const local = relative(root, target);
|
|
244
|
+
if (local === "" || local === ".." || local.startsWith(`..${sep}`) || isAbsolute(local)) {
|
|
245
|
+
fail("E_VALIDATION_FAILED", "view project path 越界");
|
|
246
|
+
}
|
|
247
|
+
await mkdir(dirname(target), { recursive: true });
|
|
248
|
+
await writeFile(target, bytes, { flag: "wx" });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function npmInvocation() {
|
|
252
|
+
if (process.platform !== "win32")
|
|
253
|
+
return { command: "npm", args: [] };
|
|
254
|
+
return {
|
|
255
|
+
command: process.execPath,
|
|
256
|
+
args: [join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")],
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
async function buildCapturedView(projectRoot, project, revisionDigest) {
|
|
260
|
+
await ensureSafeProjectDirectory(projectRoot, ".worldengine/review-build");
|
|
261
|
+
const buildParent = await safeProjectPath(projectRoot, ".worldengine/review-build");
|
|
262
|
+
const work = await mkdtemp(join(buildParent, `${revisionDigest.slice(0, 16)}-`));
|
|
263
|
+
const canonicalParent = await realpath(buildParent);
|
|
264
|
+
const canonicalWork = await realpath(work);
|
|
265
|
+
if (!canonicalWork.startsWith(`${canonicalParent}${sep}`))
|
|
266
|
+
fail("E_VALIDATION_FAILED", "review build path 越界");
|
|
267
|
+
try {
|
|
268
|
+
await materializeCapturedViewProject(canonicalWork, project);
|
|
269
|
+
const nodeModules = await realpath(join(projectRoot, "view", "node_modules"));
|
|
270
|
+
await symlink(nodeModules, join(canonicalWork, "node_modules"), process.platform === "win32" ? "junction" : "dir");
|
|
271
|
+
const npm = npmInvocation();
|
|
272
|
+
const buildOne = async (target) => {
|
|
273
|
+
await writeFile(join(canonicalWork, ".env"), `VITE_PUBLISH_TARGET=${target}\n`, "utf8");
|
|
274
|
+
const outDir = join(canonicalWork, `dist-${target}`);
|
|
275
|
+
await execFileAsync(npm.command, [...npm.args, "run", "build", "--", "--outDir", outDir, "--emptyOutDir"], {
|
|
276
|
+
cwd: canonicalWork,
|
|
277
|
+
env: { ...process.env, VITE_PUBLISH_TARGET: target },
|
|
278
|
+
timeout: 5 * 60_000,
|
|
279
|
+
windowsHide: true,
|
|
280
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
281
|
+
});
|
|
282
|
+
const html = new Uint8Array(await readFile(join(outDir, "index.html")));
|
|
283
|
+
if (html.byteLength === 0 || html.byteLength > MAX_PUBLISH_HTML_BYTES) {
|
|
284
|
+
fail("E_VALIDATION_FAILED", `view ${target} build size 无效`);
|
|
285
|
+
}
|
|
286
|
+
return html;
|
|
287
|
+
};
|
|
288
|
+
return Object.freeze({ mobile: await buildOne("mobile"), pc: await buildOne("pc") });
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
if (canonicalWork.startsWith(`${canonicalParent}${sep}`))
|
|
292
|
+
await rm(canonicalWork, { recursive: true, force: true });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function successBody(response, field) {
|
|
296
|
+
if (response.status === 401 || response.status === 403)
|
|
297
|
+
fail("E_AUTH_REQUIRED", `${field} 未获授权`);
|
|
298
|
+
if (response.status < 200 || response.status >= 300)
|
|
299
|
+
fail("E_VALIDATION_FAILED", `${field} 失败:HTTP ${response.status}`);
|
|
300
|
+
return object(response.body, field);
|
|
301
|
+
}
|
|
302
|
+
async function readDatabaseKey(options, scenarioId, requiredBytes) {
|
|
303
|
+
const response = await options.transport.request({
|
|
304
|
+
project_id: options.project_id,
|
|
305
|
+
method: "GET",
|
|
306
|
+
path: `/v1/scenarios/${encodeURIComponent(scenarioId)}/modules/database/key`,
|
|
307
|
+
});
|
|
308
|
+
const body = successBody(response, "database key");
|
|
309
|
+
const derivation = object(body.derivation, "database key derivation");
|
|
310
|
+
const key = requireText(body.key_b64, "database key");
|
|
311
|
+
if (derivation.algo !== "HKDF-SHA256" || derivation.info_v !== 1
|
|
312
|
+
|| requirePositive(body.ttl_seconds, "database key ttl") < 1
|
|
313
|
+
|| requirePositive(body.max_bytes, "database key max bytes") < requiredBytes) {
|
|
314
|
+
fail("E_VALIDATION_FAILED", "database key contract 不匹配");
|
|
315
|
+
}
|
|
316
|
+
return key;
|
|
317
|
+
}
|
|
318
|
+
async function encryptDatabaseDelivery(options, scenarioId, plaintext) {
|
|
319
|
+
const key = await readDatabaseKey(options, scenarioId, plaintext.byteLength);
|
|
320
|
+
const ciphertext = await (options.crypto ?? createFrozenReviewWasmEncryptor()).encrypt(key, plaintext);
|
|
321
|
+
if (ciphertext.byteLength === 0 || ciphertext[0] !== 0x10)
|
|
322
|
+
fail("E_VALIDATION_FAILED", "database SCNB v2 delivery 无效");
|
|
323
|
+
return ciphertext;
|
|
324
|
+
}
|
|
325
|
+
/** Coordinator 的随机密文恢复 port;每次仅短暂消费同 scenario database key。 */
|
|
326
|
+
export function createFrozenReviewRecoveryPort(options) {
|
|
327
|
+
const crypto = options.crypto ?? createFrozenReviewWasmEncryptor();
|
|
328
|
+
const scenario = async () => {
|
|
329
|
+
const target = await readFrozenReviewTargetBinding(options.project_root, options.project_id);
|
|
330
|
+
return target.scenario_id;
|
|
331
|
+
};
|
|
332
|
+
return Object.freeze({
|
|
333
|
+
async seal(projectId, plaintext) {
|
|
334
|
+
if (projectId !== options.project_id)
|
|
335
|
+
fail("E_OPERATION_ID_CONFLICT", "review recovery project 不匹配");
|
|
336
|
+
const key = await readDatabaseKey(options, await scenario(), plaintext.byteLength);
|
|
337
|
+
const ciphertext = await crypto.encrypt(key, plaintext);
|
|
338
|
+
if (ciphertext.byteLength === 0 || ciphertext[0] !== 0x10)
|
|
339
|
+
fail("E_VALIDATION_FAILED", "review recovery SCNB v2 无效");
|
|
340
|
+
return ciphertext;
|
|
341
|
+
},
|
|
342
|
+
async open(projectId, ciphertext) {
|
|
343
|
+
if (projectId !== options.project_id || ciphertext.byteLength === 0 || ciphertext[0] !== 0x10) {
|
|
344
|
+
fail("E_OPERATION_ID_CONFLICT", "review recovery identity 不匹配");
|
|
345
|
+
}
|
|
346
|
+
const key = await readDatabaseKey(options, await scenario(), 1);
|
|
347
|
+
return await crypto.decrypt(key, ciphertext);
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Standalone Node 的真实发布装配器:remote capture 的同一 compiler outcome 在共享模块物化,
|
|
353
|
+
* 随后构建双端 View、用 broker key + Rust/WASM 生成 SCNB,再返回 FrozenPublishInput。
|
|
354
|
+
* prepare 不写 mutable module;唯一远端写由 Coordinator 的 attempt POST 执行。
|
|
355
|
+
*/
|
|
356
|
+
export function createFrozenReviewLocalArtifactAssembler(options) {
|
|
357
|
+
const compilations = new Map();
|
|
358
|
+
const materialize = async (input) => {
|
|
359
|
+
const cached = compilations.get(input.capture.revision_digest);
|
|
360
|
+
if (cached !== undefined)
|
|
361
|
+
return cached;
|
|
362
|
+
const compiled = await materializeNeutralArtifactCompilation(input.source, input.profile, input.compilation, unpackCapturedViewProject);
|
|
363
|
+
compilations.set(input.capture.revision_digest, compiled);
|
|
364
|
+
return compiled;
|
|
365
|
+
};
|
|
366
|
+
return Object.freeze({
|
|
367
|
+
async embeddingGraph(input) {
|
|
368
|
+
return (await materialize(input)).model.graph;
|
|
369
|
+
},
|
|
370
|
+
async assemble(input) {
|
|
371
|
+
if (input.signal?.aborted === true)
|
|
372
|
+
fail("E_VALIDATION_FAILED", "review assemble 已取消");
|
|
373
|
+
const compilation = await materialize(input);
|
|
374
|
+
const crypto = options.crypto ?? createFrozenReviewWasmEncryptor();
|
|
375
|
+
if (crypto.pack === undefined)
|
|
376
|
+
fail("E_VALIDATION_FAILED", "review database WASM pack 未配置");
|
|
377
|
+
const database = await encodeCompactReviewDatabase({
|
|
378
|
+
source: input.source,
|
|
379
|
+
content_epoch: input.capture.content_epoch,
|
|
380
|
+
compilation,
|
|
381
|
+
embedding: input.embedding_snapshot.materialization,
|
|
382
|
+
packFrozenDatabase: (packInput) => crypto.pack(packInput),
|
|
383
|
+
});
|
|
384
|
+
const views = await buildCapturedView(options.project_root, compilation.model.view_project, input.capture.revision_digest);
|
|
385
|
+
const runtimeResponse = await options.transport.request({
|
|
386
|
+
project_id: options.project_id,
|
|
387
|
+
method: "GET",
|
|
388
|
+
path: "/v1/runtime/current",
|
|
389
|
+
});
|
|
390
|
+
const runtime = successBody(runtimeResponse, "runtime current");
|
|
391
|
+
const runtimeMajor = requirePositive(runtime.runtime_major, "runtime major");
|
|
392
|
+
if (typeof runtime.runtime_patch !== "number" || !Number.isSafeInteger(runtime.runtime_patch) || runtime.runtime_patch < 0) {
|
|
393
|
+
fail("E_VALIDATION_FAILED", "runtime patch 无效");
|
|
394
|
+
}
|
|
395
|
+
const protocolVersion = requirePositive(runtime.protocol_version, "runtime protocol");
|
|
396
|
+
const databaseDelivery = await encryptDatabaseDelivery(options, input.capture.scenario_id, database);
|
|
397
|
+
return await assembleNeutralFrozenPublishInput({
|
|
398
|
+
source: input.source,
|
|
399
|
+
content_epoch: input.capture.content_epoch,
|
|
400
|
+
compilation,
|
|
401
|
+
embedding: input.embedding_snapshot.materialization,
|
|
402
|
+
database_bytes: database,
|
|
403
|
+
database_delivery_bytes: databaseDelivery,
|
|
404
|
+
view_publish_mobile_bytes: views.mobile,
|
|
405
|
+
view_publish_pc_bytes: views.pc,
|
|
406
|
+
runtime_identity: {
|
|
407
|
+
runtime_generation: 3,
|
|
408
|
+
runtime_contract_hash: compilation.model.trigger_document.runtime_contract_hash,
|
|
409
|
+
runtime_major: runtimeMajor,
|
|
410
|
+
runtime_patch: runtime.runtime_patch,
|
|
411
|
+
runtime_release_id: `runtime3-${runtimeMajor}.${runtime.runtime_patch}`,
|
|
412
|
+
protocol_version: protocolVersion,
|
|
413
|
+
sdk_schema_version: 3,
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
/** neutral-transfer 在远端 readback 成功后调用;只持久化公开 identity/digest。 */
|
|
420
|
+
export async function writeFrozenReviewTargetBinding(root, binding) {
|
|
421
|
+
if (binding.schema_version !== 1)
|
|
422
|
+
fail("E_VALIDATION_FAILED", "review target schema 无效");
|
|
423
|
+
requireText(binding.project_id, "review target project");
|
|
424
|
+
requireText(binding.scenario_id, "review target scenario");
|
|
425
|
+
requireSha(binding.source_digest, "review target source");
|
|
426
|
+
requireSha(binding.source_head_digest, "review target head");
|
|
427
|
+
await ensureSafeProjectDirectory(root, ".worldengine");
|
|
428
|
+
const path = await safeProjectPath(root, REVIEW_TARGET_PATH);
|
|
429
|
+
const temporary = await safeProjectPath(root, `${REVIEW_TARGET_PATH}.tmp`);
|
|
430
|
+
await writeFile(temporary, `${JSON.stringify(binding)}\n`, "utf8");
|
|
431
|
+
await rename(temporary, path);
|
|
432
|
+
return await readFrozenReviewTargetBinding(root, binding.project_id);
|
|
433
|
+
}
|
|
434
|
+
function currentProfile() {
|
|
435
|
+
return Object.freeze({
|
|
436
|
+
schema_version: 1,
|
|
437
|
+
compiler_release: "tmw3-neutral-compiler-1",
|
|
438
|
+
runtime_semantics: "tmw3-runtime-contract-v3",
|
|
439
|
+
sdk_schema: "chatplay-sdk-v3",
|
|
440
|
+
artifact_format: "artifact-v5-package-2",
|
|
441
|
+
stage_versions: CURRENT_NEUTRAL_COMPILER_STAGE_VERSIONS,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
async function captureLocalSource(options) {
|
|
445
|
+
const sourceReady = await options.read_source_ready();
|
|
446
|
+
const target = sourceReady.target;
|
|
447
|
+
if (target.project_id !== options.project_id)
|
|
448
|
+
fail("E_OPERATION_ID_CONFLICT", "review target project 不匹配");
|
|
449
|
+
if (!(sourceReady.revision_bytes instanceof Uint8Array)) {
|
|
450
|
+
fail("E_VALIDATION_FAILED", "source-ready 缺少 canonical Neutral revision bytes");
|
|
451
|
+
}
|
|
452
|
+
const sourceBytes = sourceReady.revision_bytes.slice();
|
|
453
|
+
const sourceDigest = sha256(sourceBytes);
|
|
454
|
+
if (sourceDigest !== target.source_digest) {
|
|
455
|
+
fail("E_OPERATION_ID_CONFLICT", "canonical Neutral source 与 upload binding 不匹配");
|
|
456
|
+
}
|
|
457
|
+
const epoch = requirePositive(sourceReady.content_epoch, "content epoch");
|
|
458
|
+
const head = sourceReady.head;
|
|
459
|
+
const ownerId = requireText(head.owner_id, "author source owner");
|
|
460
|
+
const scenarioId = requireText(head.scenario_id, "author source scenario");
|
|
461
|
+
const sourceRevision = requirePositive(head.source_revision, "author source revision");
|
|
462
|
+
const revisionDigest = requireSha(head.revision_digest, "author source revision");
|
|
463
|
+
const headDigest = requireSha(head.head_digest, "author source head");
|
|
464
|
+
requireSha(head.manifest_digest, "author source manifest");
|
|
465
|
+
if (scenarioId !== target.scenario_id
|
|
466
|
+
|| headDigest !== target.source_head_digest
|
|
467
|
+
|| requireSha(head.source_digest, "author source") !== sourceDigest) {
|
|
468
|
+
fail("E_OPERATION_ID_CONFLICT", "远端 author-source head 与本地 upload binding 不匹配");
|
|
469
|
+
}
|
|
470
|
+
const witness = Object.freeze({ ...head });
|
|
471
|
+
const source = await captureNeutralSourceCompileInput({ witness, source_bytes: sourceBytes });
|
|
472
|
+
return Object.freeze({
|
|
473
|
+
public: Object.freeze({
|
|
474
|
+
project_id: options.project_id,
|
|
475
|
+
owner_id: ownerId,
|
|
476
|
+
scenario_id: scenarioId,
|
|
477
|
+
content_epoch: epoch,
|
|
478
|
+
source_revision: sourceRevision,
|
|
479
|
+
source_sha256: sourceDigest,
|
|
480
|
+
revision_digest: revisionDigest,
|
|
481
|
+
revision_id: headDigest,
|
|
482
|
+
}),
|
|
483
|
+
source,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
export function createFrozenReviewLocalPipeline(options) {
|
|
487
|
+
const captures = new Map();
|
|
488
|
+
const compilations = new Map();
|
|
489
|
+
const embeddings = new Map();
|
|
490
|
+
return Object.freeze({
|
|
491
|
+
getPreparedEmbeddingSnapshot(capture) {
|
|
492
|
+
const prepared = embeddings.get(capture.revision_digest)?.prepared ?? null;
|
|
493
|
+
if (prepared === null)
|
|
494
|
+
return null;
|
|
495
|
+
return prepared.source.scenario_id === capture.scenario_id
|
|
496
|
+
&& prepared.source.source_revision === capture.source_revision
|
|
497
|
+
&& prepared.source.revision_digest === capture.revision_digest
|
|
498
|
+
? prepared
|
|
499
|
+
: null;
|
|
500
|
+
},
|
|
501
|
+
async capture() {
|
|
502
|
+
const captured = await captureLocalSource(options);
|
|
503
|
+
captures.set(captured.public.revision_digest, captured);
|
|
504
|
+
return captured.public;
|
|
505
|
+
},
|
|
506
|
+
async compile(capture) {
|
|
507
|
+
const captured = captures.get(capture.revision_digest);
|
|
508
|
+
if (captured === undefined || captured.public.revision_id !== capture.revision_id) {
|
|
509
|
+
fail("E_OPERATION_ID_CONFLICT", "compile 未消费本次冻结 capture");
|
|
510
|
+
}
|
|
511
|
+
const profile = currentProfile();
|
|
512
|
+
const admitProfile = options.admit_profile;
|
|
513
|
+
if (admitProfile === undefined) {
|
|
514
|
+
fail("E_AUTH_REQUIRED", "Compiler release authority adapter 未配置");
|
|
515
|
+
}
|
|
516
|
+
await admitProfile(profile);
|
|
517
|
+
const outcome = await compileNeutralScenarioSource(captured.source, profile, buildCurrentTmw3NeutralCompilerStageRegistry(profile), compilerCache);
|
|
518
|
+
if (!outcome.ok) {
|
|
519
|
+
fail("E_VALIDATION_FAILED", `Neutral source 编译失败:${outcome.diagnostics.map((item) => item.code).join(",")}`);
|
|
520
|
+
}
|
|
521
|
+
const value = Object.freeze({
|
|
522
|
+
public: Object.freeze({
|
|
523
|
+
source_revision_digest: capture.revision_digest,
|
|
524
|
+
compiler_receipt_sha256: requireSha(outcome.receipt.receipt_sha256, "compiler receipt"),
|
|
525
|
+
}),
|
|
526
|
+
profile,
|
|
527
|
+
outcome,
|
|
528
|
+
});
|
|
529
|
+
compilations.set(capture.revision_digest, value);
|
|
530
|
+
return value.public;
|
|
531
|
+
},
|
|
532
|
+
async embed(capture, compilation, signal) {
|
|
533
|
+
const captured = captures.get(capture.revision_digest);
|
|
534
|
+
const compiled = compilations.get(capture.revision_digest);
|
|
535
|
+
if (captured === undefined || compiled === undefined
|
|
536
|
+
|| compiled.public.compiler_receipt_sha256 !== compilation.compiler_receipt_sha256) {
|
|
537
|
+
fail("E_OPERATION_ID_CONFLICT", "embedding 未消费本次冻结 compilation");
|
|
538
|
+
}
|
|
539
|
+
const graph = await options.assembler.embeddingGraph({
|
|
540
|
+
capture,
|
|
541
|
+
source: captured.source,
|
|
542
|
+
profile: compiled.profile,
|
|
543
|
+
compilation: compiled.outcome,
|
|
544
|
+
});
|
|
545
|
+
const recalled = await options.read_prepared_embedding_snapshot?.(capture) ?? null;
|
|
546
|
+
if (recalled !== null) {
|
|
547
|
+
if (recalled.source.owner_id !== capture.owner_id
|
|
548
|
+
|| recalled.source.scenario_id !== capture.scenario_id
|
|
549
|
+
|| recalled.source.source_revision !== capture.source_revision
|
|
550
|
+
|| recalled.source.revision_digest !== capture.revision_digest
|
|
551
|
+
|| recalled.batch.entries.length !== graph.entities.length
|
|
552
|
+
|| recalled.materialization.vector_by_entity_id.size !== graph.entities.length) {
|
|
553
|
+
fail("E_OPERATION_ID_CONFLICT", "Host prepared embedding snapshot 与 captured source 不匹配");
|
|
554
|
+
}
|
|
555
|
+
const value = Object.freeze({
|
|
556
|
+
public: Object.freeze({
|
|
557
|
+
source_revision_digest: capture.revision_digest,
|
|
558
|
+
descriptor_sha256: recalled.batch.descriptor.descriptor_sha256,
|
|
559
|
+
vector_count: recalled.batch.entries.length,
|
|
560
|
+
}),
|
|
561
|
+
vectors: recalled.materialization.vector_by_entity_id,
|
|
562
|
+
prepared: recalled,
|
|
563
|
+
});
|
|
564
|
+
embeddings.set(capture.revision_digest, value);
|
|
565
|
+
return value.public;
|
|
566
|
+
}
|
|
567
|
+
const { prepareNeutralArtifactEmbeddingSnapshot } = await import("@world-engines/scenario-review-snapshot");
|
|
568
|
+
const prepared = await prepareNeutralArtifactEmbeddingSnapshot(graph, {
|
|
569
|
+
owner_id: capture.owner_id,
|
|
570
|
+
scenario_id: capture.scenario_id,
|
|
571
|
+
source_revision: capture.source_revision,
|
|
572
|
+
revision_digest: capture.revision_digest,
|
|
573
|
+
}, options.run_embedding_batch, signal);
|
|
574
|
+
const vectorMap = prepared.materialization.vector_by_entity_id;
|
|
575
|
+
const value = Object.freeze({
|
|
576
|
+
public: Object.freeze({
|
|
577
|
+
source_revision_digest: capture.revision_digest,
|
|
578
|
+
descriptor_sha256: prepared.batch.descriptor.descriptor_sha256,
|
|
579
|
+
vector_count: vectorMap.size,
|
|
580
|
+
}),
|
|
581
|
+
vectors: vectorMap,
|
|
582
|
+
prepared,
|
|
583
|
+
});
|
|
584
|
+
embeddings.set(capture.revision_digest, value);
|
|
585
|
+
return value.public;
|
|
586
|
+
},
|
|
587
|
+
async freeze(capture, compilation, embedding, signal) {
|
|
588
|
+
const captured = captures.get(capture.revision_digest);
|
|
589
|
+
const compiled = compilations.get(capture.revision_digest);
|
|
590
|
+
const embedded = embeddings.get(capture.revision_digest);
|
|
591
|
+
if (captured === undefined || compiled === undefined || embedded === undefined
|
|
592
|
+
|| compiled.public.compiler_receipt_sha256 !== compilation.compiler_receipt_sha256
|
|
593
|
+
|| embedded.public.descriptor_sha256 !== embedding.descriptor_sha256
|
|
594
|
+
|| embedded.public.vector_count !== embedding.vector_count) {
|
|
595
|
+
fail("E_OPERATION_ID_CONFLICT", "freeze 未消费本次冻结 pipeline identity");
|
|
596
|
+
}
|
|
597
|
+
return await options.assembler.assemble({
|
|
598
|
+
capture,
|
|
599
|
+
source: captured.source,
|
|
600
|
+
profile: compiled.profile,
|
|
601
|
+
compilation: compiled.outcome,
|
|
602
|
+
embedding,
|
|
603
|
+
vectors: embedded.vectors,
|
|
604
|
+
embedding_snapshot: embedded.prepared,
|
|
605
|
+
...(signal === undefined ? {} : { signal }),
|
|
606
|
+
});
|
|
607
|
+
},
|
|
608
|
+
});
|
|
609
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type CreateFrozenReviewLocalPipelineOptions } from "./frozen-review-local-pipeline.js";
|
|
2
|
+
import { type FrozenReviewWasmEncryptor } from "./frozen-review-wasm-encryptor.js";
|
|
3
|
+
import type { ProjectTransferTransport } from "./transfer/transport.js";
|
|
4
|
+
export interface CreateFrozenReviewSourceCaptureOptions {
|
|
5
|
+
readonly project_root: string;
|
|
6
|
+
readonly project_id: string;
|
|
7
|
+
readonly transport: ProjectTransferTransport | undefined;
|
|
8
|
+
/** 测试 seam;production 默认使用与 Browser 相同的 Rust/WASM scenario-key 算法。 */
|
|
9
|
+
readonly crypto?: Pick<FrozenReviewWasmEncryptor, "decrypt">;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 为 standalone Node review 提供真实 read_source_ready。所有远端读取都经过 project-bound
|
|
13
|
+
* ProjectAuth transport;返回值只含已解密并逐 digest 校验的 canonical Neutral revision。
|
|
14
|
+
*/
|
|
15
|
+
export declare function createFrozenReviewSourceCapture(options: CreateFrozenReviewSourceCaptureOptions): CreateFrozenReviewLocalPipelineOptions["read_source_ready"];
|