@world-engines/scenario-review-snapshot 0.0.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.
@@ -0,0 +1,1050 @@
1
+ import { canonicalScenarioSourceManifestDigestInputBytes, } from "@world-engines/scenario-author-source";
2
+ import { CURRENT_NEUTRAL_COMPILER_STAGE_VERSIONS, NEUTRAL_SPATIAL_WORLD_STAGE_ID, NEUTRAL_SPATIAL_WORLD_STAGE_VERSION, canonicalCompileTargetProfileBytes, canonicalNeutralSourceCompilerReceiptBytes, compileTargetProfileDigest, digestViewInteractionManifestV3, materializeTmw3TriggerDocument, initialTmwSceneFromMetadata, parseMusicCandidateSourceV3, parseViewInteractionManifestV3, parseViewInteractionSourceV3, } from "@world-engines/tmw";
3
+ import { canonicalLadybugRelationId, createViewAccessPolicySubject, sealViewAccessPolicy, VIEW_ACCESS_POLICY_DENY_ROOT, VIEW_ACCESS_POLICY_ROOT_SUBJECT_ID, } from "@world-engines/ladybug-bridge";
4
+ import { validateRoadGeneration } from "@world-engines/spatial-authoring/road-generation";
5
+ import { validateSpatialTemplateConfiguration } from "@world-engines/spatial-authoring/spatial-templates";
6
+ import { canonicalJson, sha256Hex, toJsonValue } from "./canonical.js";
7
+ import { ARTIFACT_COMPILER_WITNESS_PATHS, ARTIFACT_COMPILER_WITNESS_RESERVED_PREFIX, FROZEN_PUBLISH_INPUT_SCHEMA_VERSION, } from "./artifact.js";
8
+ import { buildArtifactV5 } from "./artifact-digest.js";
9
+ export const NEUTRAL_ARTIFACT_EMBEDDING_INDEX_PATH = "__derived__/embedding-index-v1.json";
10
+ export const FRESH_RUNTIME3_PLAYER_TURN_POLICY_SCHEMA_VERSION = 1;
11
+ export const FROZEN_DATABASE_VIEW_ACCESS_POLICY_SCHEMA_VERSION = 3;
12
+ export const NEUTRAL_ARTIFACT_DERIVED_RESOURCE_PREFIX = "__derived__/";
13
+ const SHA256 = /^[0-9a-f]{64}$/u;
14
+ const encoder = new TextEncoder();
15
+ const MAX_EMBEDDING_SOURCE_TEXT_CHARACTERS = 32_000;
16
+ const canonicalBytes = (value) => encoder.encode(canonicalJson(value));
17
+ /** 既有 embedding index 的 source vector hash:JSON number array,不是 packed bytes hash。 */
18
+ const vectorSourceJsonSha256 = (vector) => sha256Hex(encoder.encode(JSON.stringify([...vector])));
19
+ function record(value, code) {
20
+ if (value === null || Array.isArray(value) || typeof value !== "object")
21
+ throw new Error(code);
22
+ return value;
23
+ }
24
+ function text(value, code) {
25
+ if (typeof value !== "string" || value.length === 0 || value !== value.trim().normalize("NFC")) {
26
+ throw new Error(code);
27
+ }
28
+ return value;
29
+ }
30
+ function names(value) {
31
+ const values = typeof value === "string" ? [value] : Array.isArray(value) ? value : [];
32
+ return values.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
33
+ }
34
+ function stableText(value) {
35
+ if (value === undefined || value === null)
36
+ return "";
37
+ if (typeof value === "string")
38
+ return value;
39
+ if (typeof value === "number" || typeof value === "boolean")
40
+ return String(value);
41
+ return new TextDecoder().decode(canonicalBytes(value));
42
+ }
43
+ function effectivePropText(value) {
44
+ if (value === null || Array.isArray(value) || typeof value !== "object")
45
+ return stableText(value);
46
+ const prop = value;
47
+ if (Array.isArray(prop.states) && prop.states.length > 0) {
48
+ const active = prop.states.find((state) => {
49
+ if (state === null || Array.isArray(state) || typeof state !== "object")
50
+ return false;
51
+ const candidate = state;
52
+ return candidate.active === true || candidate.is_active === true;
53
+ });
54
+ if (active !== undefined && active !== null && !Array.isArray(active) && typeof active === "object") {
55
+ return stableText(active.value);
56
+ }
57
+ return "";
58
+ }
59
+ return stableText(prop.value);
60
+ }
61
+ function disabledEntity(entity) {
62
+ if (entity.disabled === true)
63
+ return true;
64
+ const props = record(entity.props ?? {}, "neutral_artifact_embedding_entity_props_invalid");
65
+ const values = Object.values(props);
66
+ return values.length > 0 && values.every((value) => {
67
+ if (value === null || Array.isArray(value) || typeof value !== "object")
68
+ return false;
69
+ const prop = value;
70
+ return prop.dead === true || prop.deleted === true;
71
+ });
72
+ }
73
+ function retrievalExcludedEntity(entity) {
74
+ const props = record(entity.props ?? {}, "neutral_artifact_embedding_entity_props_invalid");
75
+ const value = props.retrieval_excluded;
76
+ if (value === true)
77
+ return true;
78
+ return value !== null && value !== undefined && !Array.isArray(value) && typeof value === "object"
79
+ && value.value === true;
80
+ }
81
+ function retrievalSearchableEntity(entity) {
82
+ const kind = entity.kind;
83
+ return !disabledEntity(entity) && !retrievalExcludedEntity(entity)
84
+ && kind !== "world_source_document" && kind !== "source_document"
85
+ && kind !== "world_ingestion_batch" && kind !== "world_ingestion_source_control"
86
+ && kind !== "world_document_ingestion_source_control";
87
+ }
88
+ function embeddingSafeField(key) {
89
+ const compact = key.toLocaleLowerCase("en-US").replaceAll(/[^a-z0-9]/gu, "");
90
+ return !/(?:raw|original|archive|source|binary|blob).*(?:base64|bytes)|(?:base64|bytes).*(?:raw|original|archive|source|binary|blob)/u.test(compact);
91
+ }
92
+ function relationSourceText(relation) {
93
+ const props = record(relation.props ?? {}, "neutral_artifact_embedding_relation_props_invalid");
94
+ const fields = relation.fields === undefined
95
+ ? {}
96
+ : record(relation.fields, "neutral_artifact_embedding_relation_fields_invalid");
97
+ const from = text(relation.from_id, "neutral_artifact_embedding_relation_from_invalid");
98
+ const to = text(relation.to_id, "neutral_artifact_embedding_relation_to_invalid");
99
+ const kind = text(relation.kind, "neutral_artifact_embedding_relation_kind_invalid");
100
+ const propText = Object.keys(props).filter(embeddingSafeField).sort().map((key) => {
101
+ const value = props[key];
102
+ const rendered = value === undefined ? "" : stableText(value);
103
+ return rendered.length === 0 ? "" : `${key}=${rendered}`;
104
+ }).filter(Boolean).join(" ");
105
+ const fieldText = Object.keys(fields).sort().map((key) => {
106
+ const value = fields[key];
107
+ const rendered = value === undefined ? "" : effectivePropText(value);
108
+ return rendered.length === 0 ? "" : `${key}=${rendered}`;
109
+ }).filter(Boolean).join(" ");
110
+ return [`${kind} ${from} -> ${to} ${propText}`.trim(), fieldText].filter(Boolean).join(" ");
111
+ }
112
+ function buildNeutralArtifactEmbeddingSourceTextIndex(graph) {
113
+ const entityById = new Map();
114
+ for (const value of graph.entities) {
115
+ const entity = record(value, "neutral_artifact_embedding_entity_invalid");
116
+ if (entity.entity_id === undefined && disabledEntity(entity))
117
+ continue;
118
+ const entityId = text(entity.entity_id, "neutral_artifact_embedding_entity_id_invalid");
119
+ if (entityById.has(entityId))
120
+ throw new Error(`neutral_artifact_embedding_entity_id_duplicate:${entityId}`);
121
+ entityById.set(entityId, entity);
122
+ }
123
+ const mutableIncident = new Map();
124
+ const append = (entityId, relation) => {
125
+ const values = mutableIncident.get(entityId);
126
+ if (values === undefined)
127
+ mutableIncident.set(entityId, [relation]);
128
+ else
129
+ values.push(relation);
130
+ };
131
+ for (const value of graph.relations) {
132
+ const relation = record(value, "neutral_artifact_embedding_relation_invalid");
133
+ if (relation.disabled === true)
134
+ continue;
135
+ const from = relation.from_id;
136
+ const to = relation.to_id;
137
+ if (typeof from === "string")
138
+ append(from, relation);
139
+ if (typeof to === "string" && to !== from)
140
+ append(to, relation);
141
+ }
142
+ const incidentByEntityId = new Map();
143
+ for (const [entityId, relations] of mutableIncident) {
144
+ incidentByEntityId.set(entityId, Object.freeze(relations.sort((left, right) => String(left.relation_id).localeCompare(String(right.relation_id)))));
145
+ }
146
+ return Object.freeze({ entityById, incidentByEntityId });
147
+ }
148
+ function deriveNeutralArtifactEntityEmbeddingSourceTextFromIndex(entityValue, index) {
149
+ const entity = record(entityValue, "neutral_artifact_embedding_entity_invalid");
150
+ const entityId = text(entity.entity_id, "neutral_artifact_embedding_entity_id_invalid");
151
+ const kind = text(entity.kind, "neutral_artifact_embedding_entity_kind_invalid");
152
+ const props = record(entity.props ?? {}, "neutral_artifact_embedding_entity_props_invalid");
153
+ const baseProps = Object.keys(props).filter(embeddingSafeField).sort().map((key) => {
154
+ const value = props[key];
155
+ const rendered = value === undefined ? "" : effectivePropText(value);
156
+ return rendered.length === 0 ? "" : `${key}=${rendered}`;
157
+ }).filter(Boolean).join(" ");
158
+ const base = `names=${names(entity.name).join("|")} ${kind} ${entityId} ${baseProps}`.trim();
159
+ const incident = (index.incidentByEntityId.get(entityId) ?? []).map((relation) => {
160
+ const outgoing = relation.from_id === entityId;
161
+ const counterpartId = text(outgoing ? relation.to_id : relation.from_id, "neutral_artifact_embedding_counterpart_invalid");
162
+ const counterpart = index.entityById.get(counterpartId);
163
+ return [
164
+ `relation=${text(relation.relation_id, "neutral_artifact_embedding_relation_id_invalid")}`,
165
+ `direction=${outgoing ? "outgoing" : "incoming"}`,
166
+ `counterpart=${counterpartId}`,
167
+ `counterpart_names=${counterpart === undefined ? counterpartId : names(counterpart.name).join("|")}`,
168
+ `title=${typeof relation.title === "string" ? relation.title : ""}`,
169
+ `content=${typeof relation.content === "string" ? relation.content : ""}`,
170
+ relationSourceText(relation),
171
+ ].filter((segment) => segment.length > 0).join(" ");
172
+ });
173
+ return [base, ...incident].join("\n");
174
+ }
175
+ /** ADR-0148 canonical source text: enabled incident relations enrich both endpoint entities. */
176
+ export function deriveNeutralArtifactEntityEmbeddingSourceText(entityValue, graph) {
177
+ return deriveNeutralArtifactEntityEmbeddingSourceTextFromIndex(entityValue, buildNeutralArtifactEmbeddingSourceTextIndex(graph));
178
+ }
179
+ export async function compileCapturedNeutralSource(source, profile, compiler) {
180
+ return Object.freeze({ source, compilation: await compiler.compile(source, profile) });
181
+ }
182
+ const strictDecoder = new TextDecoder("utf-8", { fatal: true });
183
+ function bytesEqual(left, right) {
184
+ if (left.byteLength !== right.byteLength)
185
+ return false;
186
+ let difference = 0;
187
+ for (let index = 0; index < left.byteLength; index += 1) {
188
+ difference |= (left[index] ?? 0) ^ (right[index] ?? 0);
189
+ }
190
+ return difference === 0;
191
+ }
192
+ function parseCanonicalJson(bytes, field) {
193
+ let parsed;
194
+ try {
195
+ parsed = JSON.parse(strictDecoder.decode(bytes));
196
+ }
197
+ catch {
198
+ throw new Error(`neutral_artifact_${field}_json_invalid`);
199
+ }
200
+ const value = toJsonValue(parsed);
201
+ if (!bytesEqual(canonicalBytes(value), bytes))
202
+ throw new Error(`neutral_artifact_${field}_not_canonical`);
203
+ return value;
204
+ }
205
+ function exactKeys(value, expected, field) {
206
+ const actual = Object.keys(value).sort();
207
+ const canonical = [...expected].sort();
208
+ if (actual.length !== canonical.length || actual.some((key, index) => key !== canonical[index])) {
209
+ throw new Error(`neutral_artifact_${field}_fields_mismatch`);
210
+ }
211
+ }
212
+ function array(value, field) {
213
+ if (!Array.isArray(value))
214
+ throw new Error(`neutral_artifact_${field}_array_required`);
215
+ return value;
216
+ }
217
+ function base64Bytes(value, field) {
218
+ const encoded = text(value, field);
219
+ let decoded;
220
+ try {
221
+ decoded = atob(encoded);
222
+ }
223
+ catch {
224
+ throw new Error(`neutral_artifact_${field}_base64_invalid`);
225
+ }
226
+ if (btoa(decoded) !== encoded)
227
+ throw new Error(`neutral_artifact_${field}_base64_noncanonical`);
228
+ return Uint8Array.from(decoded, (character) => character.charCodeAt(0));
229
+ }
230
+ function derivedObjectMap(objects) {
231
+ const byStage = new Map(objects.map((object) => [object.stage_id, object]));
232
+ const spatial = byStage.get(NEUTRAL_SPATIAL_WORLD_STAGE_ID);
233
+ const expectedSize = CURRENT_NEUTRAL_COMPILER_STAGE_VERSIONS.length + (spatial === undefined ? 0 : 1);
234
+ if (byStage.size !== expectedSize) {
235
+ throw new Error("neutral_artifact_derived_object_set_mismatch");
236
+ }
237
+ for (const stage of CURRENT_NEUTRAL_COMPILER_STAGE_VERSIONS) {
238
+ const object = byStage.get(stage.stage_id);
239
+ if (object === undefined || object.stage_version !== stage.stage_version) {
240
+ throw new Error(`neutral_artifact_derived_object_missing:${stage.stage_id}`);
241
+ }
242
+ }
243
+ if (spatial !== undefined && spatial.stage_version !== NEUTRAL_SPATIAL_WORLD_STAGE_VERSION) {
244
+ throw new Error(`neutral_artifact_derived_object_missing:${NEUTRAL_SPATIAL_WORLD_STAGE_ID}`);
245
+ }
246
+ return byStage;
247
+ }
248
+ function requiredDerivedJson(byStage, stageId) {
249
+ const object = byStage.get(stageId);
250
+ if (object === undefined)
251
+ throw new Error(`neutral_artifact_derived_object_missing:${stageId}`);
252
+ return parseCanonicalJson(object.bytes, stageId.replaceAll(".", "_"));
253
+ }
254
+ function optionalSpatialBase(byStage) {
255
+ const object = byStage.get(NEUTRAL_SPATIAL_WORLD_STAGE_ID);
256
+ if (object === undefined)
257
+ return null;
258
+ const root = record(parseCanonicalJson(object.bytes, "neutral_spatial_world"), "neutral_artifact_spatial_world_object_required");
259
+ if (root.player_programmatic_supported === false) {
260
+ exactKeys(root, ["player_programmatic_supported", "reason"], "spatial_world_unsupported");
261
+ if (root.reason !== "topology_missing"
262
+ && root.reason !== "topology_not_plane"
263
+ && root.reason !== "generation_missing"
264
+ && root.reason !== "procedural_missing") {
265
+ throw new Error("neutral_artifact_spatial_world_unsupported_reason_invalid");
266
+ }
267
+ return null;
268
+ }
269
+ exactKeys(root, [
270
+ "generation",
271
+ "procedural",
272
+ "baseDigest",
273
+ ...(root.roadGeneration === undefined ? [] : ["roadGeneration"]),
274
+ ...(root.templates === undefined ? [] : ["templates"]),
275
+ ...(root.controlledActorId === undefined ? [] : ["controlledActorId"]),
276
+ ], "spatial_world");
277
+ const baseDigest = text(root.baseDigest, "neutral_artifact_spatial_world_base_digest_invalid");
278
+ if (!SHA256.test(baseDigest))
279
+ throw new Error("neutral_artifact_spatial_world_base_digest_invalid");
280
+ const digestInput = toJsonValue({
281
+ generation: root.generation,
282
+ procedural: root.procedural,
283
+ ...(root.roadGeneration === undefined ? {} : { roadGeneration: root.roadGeneration }),
284
+ ...(root.templates === undefined ? {} : { templates: root.templates }),
285
+ ...(root.controlledActorId === undefined ? {} : { controlledActorId: root.controlledActorId }),
286
+ });
287
+ if (sha256Hex(canonicalBytes(digestInput)) !== baseDigest) {
288
+ throw new Error("neutral_artifact_spatial_world_base_digest_mismatch");
289
+ }
290
+ const roadGeneration = root.roadGeneration === undefined
291
+ ? undefined
292
+ : validateRoadGeneration(root.roadGeneration);
293
+ const templates = root.templates === undefined
294
+ ? undefined
295
+ : validateSpatialTemplateConfiguration(root.templates);
296
+ return Object.freeze({
297
+ generation: root.generation,
298
+ procedural: root.procedural,
299
+ ...(roadGeneration === undefined ? {} : {
300
+ roadGeneration,
301
+ }),
302
+ ...(templates === undefined ? {} : { templates }),
303
+ ...(root.controlledActorId === undefined ? {} : { controlledActorId: text(root.controlledActorId, "neutral_artifact_controlled_actor_invalid") }),
304
+ baseDigest,
305
+ });
306
+ }
307
+ function parseMetadataModel(value) {
308
+ const root = record(value, "neutral_artifact_metadata_object_required");
309
+ exactKeys(root, ["metadata", "protagonist", "system_prompts", "source_metadata"], "metadata");
310
+ const systemPrompts = array(root.system_prompts, "system_prompts").map((value, index) => {
311
+ const prompt = record(value, `neutral_artifact_system_prompt_${index}_object_required`);
312
+ exactKeys(prompt, ["prompt_id", "order", "content"], `system_prompt_${index}`);
313
+ if (typeof prompt.order !== "number" || !Number.isSafeInteger(prompt.order) || prompt.order < 0) {
314
+ throw new Error(`neutral_artifact_system_prompt_order_invalid:${index}`);
315
+ }
316
+ return Object.freeze({
317
+ prompt_id: text(prompt.prompt_id, `neutral_artifact_system_prompt_id_invalid:${index}`),
318
+ order: prompt.order,
319
+ content: prompt.content ?? null,
320
+ });
321
+ });
322
+ return Object.freeze({
323
+ metadata: root.metadata ?? null,
324
+ protagonist: root.protagonist ?? null,
325
+ system_prompts: Object.freeze(systemPrompts),
326
+ source_metadata: record(root.source_metadata ?? null, "neutral_artifact_source_metadata_object_required"),
327
+ });
328
+ }
329
+ /**
330
+ * Runtime3 的 player turn 只消费已编译计划中的 policy 段;不回读 ArtifactV5 中
331
+ * 已退役的 metadata/system_prompts identity pair。policy digest 只覆盖可执行作者语义
332
+ * 与 runtime contract,Artifact/head/five-object identity 由 Edge verified reader 再绑定。
333
+ */
334
+ export function attachFreshRuntime3PlayerTurnPolicy(compiledPlan, prompts, runtimeContractHash) {
335
+ if (!SHA256.test(runtimeContractHash))
336
+ throw new Error("fresh_runtime3_policy_runtime_contract_invalid");
337
+ const plan = record(compiledPlan, "fresh_runtime3_policy_compiled_plan_object_required");
338
+ if (plan.runtime_contract_hash !== runtimeContractHash) {
339
+ throw new Error("fresh_runtime3_policy_compiled_plan_contract_mismatch");
340
+ }
341
+ if (plan.player_turn_policy !== undefined)
342
+ throw new Error("fresh_runtime3_policy_duplicate");
343
+ const author_prompts = prompts.map((prompt, index) => {
344
+ if (typeof prompt.content !== "string") {
345
+ throw new Error(`fresh_runtime3_policy_prompt_content_string_required:${index}`);
346
+ }
347
+ return Object.freeze({ prompt_id: prompt.prompt_id, order: prompt.order, content: prompt.content });
348
+ }).sort((left, right) => left.order - right.order || left.prompt_id.localeCompare(right.prompt_id));
349
+ const core = Object.freeze({
350
+ schema_version: FRESH_RUNTIME3_PLAYER_TURN_POLICY_SCHEMA_VERSION,
351
+ runtime_contract_hash: runtimeContractHash,
352
+ author_prompts: Object.freeze(author_prompts),
353
+ });
354
+ const player_turn_policy = Object.freeze({
355
+ ...core,
356
+ policy_sha256: sha256Hex(canonicalBytes(core)),
357
+ });
358
+ return toJsonValue(Object.freeze({ ...plan, player_turn_policy }));
359
+ }
360
+ function parseGraphModel(value) {
361
+ const root = record(value, "neutral_artifact_logical_graph_object_required");
362
+ exactKeys(root, ["entities", "relations"], "logical_graph");
363
+ return Object.freeze({
364
+ entities: array(root.entities, "graph_entities"),
365
+ relations: array(root.relations, "graph_relations"),
366
+ });
367
+ }
368
+ function parseLayoutModel(value) {
369
+ const root = record(value, "neutral_artifact_layout_object_required");
370
+ exactKeys(root, ["view_interaction_mobile", "view_interaction_pc"], "layout");
371
+ const mobile = root.view_interaction_mobile === null ? null : parseViewInteractionSourceV3(root.view_interaction_mobile);
372
+ const pc = root.view_interaction_pc === null ? null : parseViewInteractionSourceV3(root.view_interaction_pc);
373
+ if ((root.view_interaction_mobile !== null && mobile === null)
374
+ || (root.view_interaction_pc !== null && pc === null)) {
375
+ throw new Error("neutral_artifact_view_interaction_source_invalid");
376
+ }
377
+ return Object.freeze({ view_interaction_mobile: mobile, view_interaction_pc: pc });
378
+ }
379
+ function parsePromptRetrievalModel(value) {
380
+ const root = record(value, "neutral_artifact_prompt_retrieval_object_required");
381
+ exactKeys(root, ["retrieval", "music_candidates", "runtime_definitions"], "prompt_retrieval");
382
+ const retrieval = record(root.retrieval ?? null, "neutral_artifact_retrieval_object_required");
383
+ exactKeys(retrieval, ["world_time", "play_config", "retrieval_config", "style_guide_selections", "attr_mutex_keys"], "retrieval");
384
+ const runtime = record(root.runtime_definitions ?? null, "neutral_artifact_runtime_definitions_object_required");
385
+ exactKeys(runtime, ["triggers", "trigger_variables_schema", "cg_defs", "achievement_defs", "transaction_defs"], "runtime_definitions");
386
+ const music = parseMusicCandidateSourceV3(Object.freeze({
387
+ schema: "music-candidate-source.v3",
388
+ generation: 3,
389
+ candidates: array(root.music_candidates, "music_candidates"),
390
+ }));
391
+ if (music === null)
392
+ throw new Error("neutral_artifact_music_candidate_source_invalid");
393
+ return Object.freeze({
394
+ retrieval: retrieval.retrieval_config ?? null,
395
+ music_candidate_source: music,
396
+ runtime_definitions: Object.freeze({
397
+ triggers: array(runtime.triggers, "runtime_triggers"),
398
+ trigger_variables_schema: array(runtime.trigger_variables_schema, "runtime_trigger_variables_schema"),
399
+ cg_defs: array(runtime.cg_defs, "runtime_cg_defs"),
400
+ achievement_defs: array(runtime.achievement_defs, "runtime_achievement_defs"),
401
+ transaction_defs: array(runtime.transaction_defs, "runtime_transaction_defs"),
402
+ }),
403
+ });
404
+ }
405
+ function parseResourcesModel(value) {
406
+ return Object.freeze(array(value, "resource_refs").map((value, index) => {
407
+ const entry = record(value, `neutral_artifact_resource_${index}_object_required`);
408
+ exactKeys(entry, ["path", "media_type", "bytes_base64"], `resource_${index}`);
409
+ const path = text(entry.path, `neutral_artifact_resource_path_${index}_invalid`);
410
+ if (path.startsWith(ARTIFACT_COMPILER_WITNESS_RESERVED_PREFIX)
411
+ || path.startsWith(NEUTRAL_ARTIFACT_DERIVED_RESOURCE_PREFIX)) {
412
+ throw new Error("neutral_artifact_reserved_resource_prefix");
413
+ }
414
+ return Object.freeze({
415
+ path,
416
+ media_type: text(entry.media_type, `neutral_artifact_resource_media_type_${index}_invalid`),
417
+ bytes: base64Bytes(entry.bytes_base64, `resource_bytes_${index}`),
418
+ });
419
+ }));
420
+ }
421
+ /**
422
+ * 把一次成功 compiler outcome 物化为 Browser/Node 共用的发布模型。
423
+ * gzip-tar 解包是唯一外部 seam;调用方不能重写 compiler derived-object 解释规则。
424
+ */
425
+ export async function materializeNeutralArtifactCompilation(source, profile, outcome, unpackViewProject) {
426
+ const byStage = derivedObjectMap(outcome.derived_objects);
427
+ const metadata = parseMetadataModel(requiredDerivedJson(byStage, "neutral.metadata"));
428
+ const graph = parseGraphModel(requiredDerivedJson(byStage, "neutral.logical-graph"));
429
+ const layout = parseLayoutModel(requiredDerivedJson(byStage, "neutral.layout"));
430
+ const promptRetrieval = parsePromptRetrievalModel(requiredDerivedJson(byStage, "neutral.prompt-retrieval"));
431
+ const spatialWorld = optionalSpatialBase(byStage);
432
+ const triggerSection = source.sections.find((section) => section.section_key === "trigger_semantics");
433
+ if (triggerSection === undefined)
434
+ throw new Error("neutral_artifact_trigger_semantics_missing");
435
+ const viewSection = source.sections.find((section) => section.section_key === "view_project");
436
+ if (viewSection === undefined
437
+ || viewSection.media_type !== "application/vnd.chat.view-project+gzip"
438
+ || viewSection.canonical_encoding !== "gzip-tar-v1") {
439
+ throw new Error("neutral_artifact_view_project_encoding_invalid");
440
+ }
441
+ const viewSourceBytes = viewSection.payload.slice();
442
+ const profileBytes = canonicalCompileTargetProfileBytes(profile);
443
+ const profileDigest = await compileTargetProfileDigest(profile);
444
+ const receiptBytes = canonicalNeutralSourceCompilerReceiptBytes(outcome.receipt);
445
+ const witnessResources = await buildNeutralArtifactWitnessResources({
446
+ source,
447
+ target_profile_bytes: profileBytes,
448
+ target_profile_digest: profileDigest,
449
+ compiler_receipt_bytes: receiptBytes,
450
+ compiler_receipt_digest: outcome.receipt.receipt_sha256,
451
+ });
452
+ return Object.freeze({
453
+ profile,
454
+ profile_digest: profileDigest,
455
+ receipt: outcome.receipt,
456
+ model: Object.freeze({
457
+ ...metadata,
458
+ graph,
459
+ trigger_document: await materializeTmw3TriggerDocument(parseCanonicalJson(triggerSection.payload, "trigger_semantics")),
460
+ compiled_plan: requiredDerivedJson(byStage, "tmw.trigger-semantics"),
461
+ view_project: await unpackViewProject(viewSourceBytes.slice()),
462
+ view_source_bytes: viewSourceBytes,
463
+ ...layout,
464
+ ...promptRetrieval,
465
+ resources: parseResourcesModel(requiredDerivedJson(byStage, "neutral.resource-refs")),
466
+ spatial_world: spatialWorld,
467
+ }),
468
+ witness_resources: witnessResources,
469
+ });
470
+ }
471
+ export async function buildNeutralArtifactWitnessResources(input) {
472
+ const manifestBytes = canonicalScenarioSourceManifestDigestInputBytes(input.source.manifest);
473
+ if (sha256Hex(manifestBytes) !== input.source.witness.manifest_digest) {
474
+ throw new Error("neutral_artifact_source_manifest_digest_mismatch");
475
+ }
476
+ if (sha256Hex(input.target_profile_bytes) !== input.target_profile_digest) {
477
+ throw new Error("neutral_artifact_target_profile_digest_mismatch");
478
+ }
479
+ if (sha256Hex(input.compiler_receipt_bytes) !== input.compiler_receipt_digest) {
480
+ throw new Error("neutral_artifact_compiler_receipt_digest_mismatch");
481
+ }
482
+ return Object.freeze([
483
+ Object.freeze({ path: ARTIFACT_COMPILER_WITNESS_PATHS.source_manifest, media_type: "application/x-protobuf", bytes: manifestBytes }),
484
+ Object.freeze({ path: ARTIFACT_COMPILER_WITNESS_PATHS.target_profile, media_type: "application/json", bytes: input.target_profile_bytes.slice() }),
485
+ Object.freeze({ path: ARTIFACT_COMPILER_WITNESS_PATHS.compiler_receipt, media_type: "application/json", bytes: input.compiler_receipt_bytes.slice() }),
486
+ ]);
487
+ }
488
+ export function mergeNeutralArtifactResources(authorResources, witnessResources, derivedResources = []) {
489
+ if (authorResources.some((resource) => resource.path.startsWith(ARTIFACT_COMPILER_WITNESS_RESERVED_PREFIX)
490
+ || resource.path.startsWith(NEUTRAL_ARTIFACT_DERIVED_RESOURCE_PREFIX))) {
491
+ throw new Error("neutral_artifact_reserved_resource_prefix");
492
+ }
493
+ if (derivedResources.length > 1 || derivedResources.some((resource) => resource.path !== NEUTRAL_ARTIFACT_EMBEDDING_INDEX_PATH
494
+ || resource.media_type !== "application/vnd.chat.embedding-index.v1+json")) {
495
+ throw new Error("neutral_artifact_derived_resource_invalid");
496
+ }
497
+ const paths = new Set(witnessResources.map((resource) => resource.path));
498
+ const required = new Set(Object.values(ARTIFACT_COMPILER_WITNESS_PATHS));
499
+ if (paths.size !== required.size || [...required].some((path) => !paths.has(path))) {
500
+ throw new Error("neutral_artifact_compiler_witness_incomplete");
501
+ }
502
+ const clone = (resource) => Object.freeze({
503
+ ...resource,
504
+ bytes: resource.bytes.slice(),
505
+ });
506
+ return Object.freeze([...authorResources.map(clone), ...derivedResources.map(clone), ...witnessResources.map(clone)]);
507
+ }
508
+ /**
509
+ * 编译产物取得最终 epoch/artifact identity 时,保留 source 中的完整下行声明。
510
+ * source digest 先验证,避免 compiler 以未签名声明扩展 View SDK 面。
511
+ */
512
+ export async function bindViewInteractionManifestForFrozenArtifact(source, epoch, runtimeContractHash, artifactBytes) {
513
+ if (source === null)
514
+ return null;
515
+ if (await digestViewInteractionManifestV3(source.manifest) !== source.manifest_digest) {
516
+ throw new Error("neutral_artifact_view_interaction_source_digest_mismatch");
517
+ }
518
+ const bound = parseViewInteractionManifestV3({
519
+ ...source.manifest,
520
+ epoch,
521
+ runtime_contract_hash: runtimeContractHash,
522
+ artifact_digest: sha256Hex(artifactBytes),
523
+ });
524
+ if (bound === null)
525
+ throw new Error("neutral_artifact_view_interaction_manifest_invalid");
526
+ return bound;
527
+ }
528
+ const PROP_ENVELOPE_MARKER = "_system_prop_envelope_v1";
529
+ const PROP_METADATA_KEYS = ["trigger", "states", "title", "initial_available", "source_refs", "view_access"];
530
+ function prop(value, field) {
531
+ return record(value, field);
532
+ }
533
+ function serializePropValue(value, field) {
534
+ if (Array.isArray(value))
535
+ return value.map((entry, index) => serializeProp(entry, `${field}_${index}`));
536
+ if (value !== null && typeof value === "object") {
537
+ const output = {};
538
+ for (const [key, entry] of Object.entries(value))
539
+ output[key] = serializeProp(entry, `${field}_${key}`);
540
+ return output;
541
+ }
542
+ return value;
543
+ }
544
+ function serializeProp(value, field) {
545
+ const input = prop(value, field);
546
+ if (!("value" in input))
547
+ throw new Error(`neutral_artifact_${field}_value_missing`);
548
+ const serializedValue = serializePropValue(input.value ?? null, `${field}_value`);
549
+ const hasMetadata = PROP_METADATA_KEYS.some((key) => input[key] !== undefined)
550
+ && !(input.states !== undefined && Array.isArray(input.states) && input.states.length === 0
551
+ && PROP_METADATA_KEYS.every((key) => key === "states" || input[key] === undefined));
552
+ if (!hasMetadata)
553
+ return serializedValue;
554
+ const output = { [PROP_ENVELOPE_MARKER]: true, value: serializedValue };
555
+ for (const key of PROP_METADATA_KEYS) {
556
+ const metadata = input[key];
557
+ if (metadata !== undefined && !(key === "states" && Array.isArray(metadata) && metadata.length === 0)
558
+ && !(key === "source_refs" && Array.isArray(metadata) && metadata.length === 0)) {
559
+ output[key] = metadata;
560
+ }
561
+ }
562
+ return output;
563
+ }
564
+ function serializeProps(value, field) {
565
+ const input = record(value ?? {}, field);
566
+ const output = {};
567
+ for (const [key, entry] of Object.entries(input))
568
+ output[key] = serializeProp(entry, `${field}_${key}`);
569
+ return output;
570
+ }
571
+ function viewAccess(value, field) {
572
+ if (value === undefined)
573
+ return undefined;
574
+ if (value !== "inherit" && value !== "deny" && value !== "read" && value !== "read_write") {
575
+ throw new Error(`neutral_artifact_${field}_invalid`);
576
+ }
577
+ return value;
578
+ }
579
+ const DELIVERY_RESERVED_DERIVED_PROP_KEYS = new Set([
580
+ "embedding",
581
+ "embedding_provider_id",
582
+ "embedding_model_id",
583
+ "embedding_dimensions",
584
+ ]);
585
+ function propPayload(value) {
586
+ if (value === undefined || value === null || Array.isArray(value) || typeof value !== "object")
587
+ return value;
588
+ return "value" in value ? value.value : value;
589
+ }
590
+ function derivedEmbeddingDisposition(props) {
591
+ const vector = propPayload(props.embedding);
592
+ const provider = propPayload(props.embedding_provider_id);
593
+ const model = propPayload(props.embedding_model_id);
594
+ const dimension = propPayload(props.embedding_dimensions);
595
+ const metadata = typeof provider === "string" && provider.length > 0
596
+ && typeof model === "string" && model.length > 0
597
+ && Number.isSafeInteger(dimension) && Number(dimension) > 0;
598
+ const derivedVector = metadata && Array.isArray(vector)
599
+ && vector.length === dimension
600
+ && vector.every((value) => {
601
+ const scalar = propPayload(value);
602
+ return typeof scalar === "number" && Number.isFinite(scalar);
603
+ });
604
+ return { metadata, vector: derivedVector };
605
+ }
606
+ /**
607
+ * 发布 database 的图只保留作者业务属性。旧 WESP 会把检索向量及其 identity
608
+ * 放进 props;这些值已由 embedding index/vector section 单独承载,不是玩家可见
609
+ * 属性,也不得扩张 view-access policy。始终复制,不能改写同一次 capture 的 source。
610
+ */
611
+ function deliveryGraphEntities(values) {
612
+ return Object.freeze(values.map((value, index) => {
613
+ const entity = record(value, `neutral_artifact_delivery_entity_${index}_invalid`);
614
+ const props = record(entity.props ?? {}, `neutral_artifact_delivery_entity_props_${index}_invalid`);
615
+ const derived = derivedEmbeddingDisposition(props);
616
+ const deliveryProps = {};
617
+ for (const [key, entry] of Object.entries(props)) {
618
+ const strip = key === "embedding"
619
+ ? derived.vector
620
+ : derived.metadata && DELIVERY_RESERVED_DERIVED_PROP_KEYS.has(key);
621
+ if (!strip)
622
+ deliveryProps[key] = entry;
623
+ }
624
+ if (!retrievalSearchableEntity(entity) && !retrievalExcludedEntity(entity)) {
625
+ deliveryProps.retrieval_excluded = Object.freeze({ value: true });
626
+ }
627
+ return Object.freeze({ ...entity, props: Object.freeze(deliveryProps) });
628
+ }));
629
+ }
630
+ function snapshotEntities(values) {
631
+ const ids = new Set();
632
+ return Object.freeze(values.map((value, index) => {
633
+ const entity = record(value, `neutral_artifact_entity_${index}_invalid`);
634
+ if (entity.disabled === true)
635
+ return null;
636
+ const id = text(entity.entity_id, `neutral_artifact_entity_id_${index}_invalid`);
637
+ if (ids.has(id))
638
+ throw new Error(`neutral_artifact_entity_id_duplicate:${id}`);
639
+ ids.add(id);
640
+ const rawNames = Array.isArray(entity.name) ? entity.name : [];
641
+ if (rawNames.length === 0 || rawNames.some((name) => typeof name !== "string"
642
+ || name.length === 0 || name !== name.trim().normalize("NFC"))) {
643
+ throw new Error(`neutral_artifact_entity_name_invalid:${id}`);
644
+ }
645
+ const mode = viewAccess(entity.view_access, `entity_view_access_${id}`);
646
+ return Object.freeze({
647
+ id,
648
+ kind: text(entity.kind, `neutral_artifact_entity_kind_${index}_invalid`),
649
+ name: Object.freeze([...rawNames]),
650
+ props: serializeProps(entity.props, `entity_props_${id}`),
651
+ ...(mode === undefined ? {} : { view_access: mode }),
652
+ ...(entity.trigger === undefined ? {} : { trigger: entity.trigger }),
653
+ ...(entity.source_refs === undefined ? {} : { source_refs: entity.source_refs }),
654
+ ...(entity.position === undefined ? {} : { position: entity.position }),
655
+ });
656
+ }).filter((entity) => entity !== null));
657
+ }
658
+ function snapshotRelations(values) {
659
+ const ids = new Set();
660
+ return Object.freeze(values.map((value, index) => {
661
+ const relation = record(value, `neutral_artifact_relation_${index}_invalid`);
662
+ if (relation.disabled === true)
663
+ return null;
664
+ const src = text(relation.from_id, `neutral_artifact_relation_from_${index}_invalid`);
665
+ const dst = text(relation.to_id, `neutral_artifact_relation_to_${index}_invalid`);
666
+ const kind = text(relation.kind, `neutral_artifact_relation_kind_${index}_invalid`);
667
+ const relationId = canonicalLadybugRelationId(src, dst, kind);
668
+ if (relation.relation_id !== relationId || ids.has(relationId)) {
669
+ throw new Error(`neutral_artifact_relation_id_invalid:${index}`);
670
+ }
671
+ ids.add(relationId);
672
+ const mode = viewAccess(relation.view_access, `relation_view_access_${relationId}`);
673
+ return Object.freeze({
674
+ relation_id: relationId,
675
+ src,
676
+ dst,
677
+ kind,
678
+ props: record(relation.props ?? {}, `neutral_artifact_relation_props_${index}_invalid`),
679
+ ...(mode === undefined ? {} : { view_access: mode }),
680
+ ...(relation.title === undefined ? {} : { title: text(relation.title, `relation_title_${index}`) }),
681
+ ...(relation.content === undefined ? {} : { content: text(relation.content, `relation_content_${index}`) }),
682
+ ...(relation.fields === undefined ? {} : { fields: serializeProps(relation.fields, `relation_fields_${relationId}`) }),
683
+ ...(relation.initial_available === undefined ? {} : { initial_available: relation.initial_available === true }),
684
+ ...(relation.trigger === undefined ? {} : { trigger: relation.trigger }),
685
+ ...(relation.source_refs === undefined ? {} : { source_refs: relation.source_refs }),
686
+ });
687
+ }).filter((relation) => relation !== null));
688
+ }
689
+ function appendPropPolicy(subjects, owner, path, value, parent) {
690
+ const input = prop(value, `policy_prop_${path.join("_")}`);
691
+ const subject = createViewAccessPolicySubject({ kind: "attr", owner, path: [...path] }, parent, viewAccess(input.view_access, "prop_view_access") ?? "inherit");
692
+ subjects.push(subject);
693
+ const child = input.value;
694
+ if (Array.isArray(child)) {
695
+ child.forEach((entry, index) => appendPropPolicy(subjects, owner, [...path, String(index)], entry, subject.id));
696
+ }
697
+ else if (child !== null && typeof child === "object") {
698
+ for (const [key, entry] of Object.entries(child))
699
+ appendPropPolicy(subjects, owner, [...path, key], entry, subject.id);
700
+ }
701
+ }
702
+ function buildViewAccessPolicy(graphEntities, entities, graphRelations, relations) {
703
+ const subjects = [];
704
+ const entityById = new Map(entities.map((entity) => [entity.id, entity]));
705
+ for (const value of graphEntities) {
706
+ const input = record(value, "neutral_artifact_policy_entity_invalid");
707
+ if (input.disabled === true)
708
+ continue;
709
+ const id = text(input.entity_id, "neutral_artifact_policy_entity_id_invalid");
710
+ const entity = entityById.get(id);
711
+ if (entity === undefined)
712
+ throw new Error(`neutral_artifact_policy_entity_missing:${id}`);
713
+ const subject = createViewAccessPolicySubject({ kind: "node", nodeId: id }, VIEW_ACCESS_POLICY_ROOT_SUBJECT_ID, entity.view_access ?? "inherit");
714
+ subjects.push(subject);
715
+ const props = record(input.props ?? {}, "neutral_artifact_policy_entity_props_invalid");
716
+ for (const [key, entry] of Object.entries(props))
717
+ appendPropPolicy(subjects, { kind: "node", nodeId: id }, [key], entry, subject.id);
718
+ }
719
+ const relationById = new Map(relations.map((relation) => [relation.relation_id, relation]));
720
+ for (const value of graphRelations) {
721
+ const input = record(value, "neutral_artifact_policy_relation_invalid");
722
+ if (input.disabled === true)
723
+ continue;
724
+ const src = text(input.from_id, "neutral_artifact_policy_relation_from_invalid");
725
+ const dst = text(input.to_id, "neutral_artifact_policy_relation_to_invalid");
726
+ const kind = text(input.kind, "neutral_artifact_policy_relation_kind_invalid");
727
+ if (!entityById.has(src) || !entityById.has(dst))
728
+ throw new Error("neutral_artifact_policy_relation_endpoint_missing");
729
+ const id = canonicalLadybugRelationId(src, dst, kind);
730
+ const relation = relationById.get(id);
731
+ if (relation === undefined)
732
+ throw new Error(`neutral_artifact_policy_relation_missing:${id}`);
733
+ const owner = { kind: "edge", sourceNodeId: src, destinationNodeId: dst, relationKind: kind };
734
+ const subject = createViewAccessPolicySubject(owner, VIEW_ACCESS_POLICY_ROOT_SUBJECT_ID, relation.view_access ?? "inherit");
735
+ subjects.push(subject);
736
+ if (input.fields !== undefined) {
737
+ const fields = record(input.fields, "neutral_artifact_policy_relation_fields_invalid");
738
+ for (const [key, entry] of Object.entries(fields))
739
+ appendPropPolicy(subjects, owner, [key], entry, subject.id);
740
+ }
741
+ }
742
+ return sealViewAccessPolicy([VIEW_ACCESS_POLICY_DENY_ROOT, ...subjects]);
743
+ }
744
+ function buildDerivedViewAccessPolicyV3(graphEntities, entities, graphRelations, relations) {
745
+ const policy = buildViewAccessPolicy(graphEntities, entities, graphRelations, relations);
746
+ return Object.freeze({
747
+ schemaVersion: FROZEN_DATABASE_VIEW_ACCESS_POLICY_SCHEMA_VERSION,
748
+ derivation: "graph-v5",
749
+ policySha256: Object.freeze([...policy.policySha256]),
750
+ });
751
+ }
752
+ function databaseVectorValues(graphEntities, entities, embedding) {
753
+ const resource = embedding.index_resource;
754
+ if (resource.path !== NEUTRAL_ARTIFACT_EMBEDDING_INDEX_PATH
755
+ || resource.media_type !== "application/vnd.chat.embedding-index.v1+json") {
756
+ throw new Error("neutral_artifact_database_embedding_index_invalid");
757
+ }
758
+ const root = record(parseCanonicalJson(resource.bytes, "database_embedding_index"), "neutral_artifact_database_embedding_index_object_required");
759
+ const descriptor = embedding.descriptor;
760
+ if (root.schema_version !== 1
761
+ || root.provider !== descriptor.provider
762
+ || root.model !== descriptor.model
763
+ || root.dimension !== descriptor.dimension
764
+ || root.descriptor_sha256 !== descriptor.descriptor_sha256
765
+ || root.source_revision !== descriptor.source_revision
766
+ || root.source_revision_digest !== descriptor.source_revision_digest) {
767
+ throw new Error("neutral_artifact_database_embedding_descriptor_mismatch");
768
+ }
769
+ const entries = array(root.entries, "database_embedding_index_entries");
770
+ const databaseEntityIds = new Set(entities.map((entity) => entity.id));
771
+ const searchableIds = new Set(graphEntities
772
+ .map((value) => record(value, "neutral_artifact_database_embedding_entity_invalid"))
773
+ .filter(retrievalSearchableEntity)
774
+ .map((entity) => text(entity.entity_id, "neutral_artifact_database_embedding_entity_id_invalid")));
775
+ if ([...searchableIds].some((entityId) => !databaseEntityIds.has(entityId))) {
776
+ throw new Error("neutral_artifact_database_embedding_graph_binding_mismatch");
777
+ }
778
+ if (entries.length !== searchableIds.size
779
+ || embedding.vector_by_entity_id.size !== searchableIds.size) {
780
+ throw new Error("neutral_artifact_database_embedding_entity_count_mismatch");
781
+ }
782
+ const seen = new Set();
783
+ const scalarCount = entries.length * descriptor.dimension;
784
+ if (!Number.isSafeInteger(scalarCount) || scalarCount <= 0) {
785
+ throw new Error("neutral_artifact_database_embedding_scalar_count_invalid");
786
+ }
787
+ const values = new Float64Array(scalarCount);
788
+ let offset = 0;
789
+ for (const [index, value] of entries.entries()) {
790
+ const entry = record(value, `neutral_artifact_database_embedding_index_entry_${index}_invalid`);
791
+ const entityId = text(entry.entity_id, `neutral_artifact_database_embedding_index_entity_${index}_invalid`);
792
+ const vectorSha256 = text(entry.vector_sha256, `neutral_artifact_database_embedding_index_vector_${index}_invalid`);
793
+ if (!searchableIds.has(entityId) || seen.has(entityId) || !SHA256.test(vectorSha256)) {
794
+ throw new Error(`neutral_artifact_database_embedding_index_binding_mismatch:${entityId}`);
795
+ }
796
+ const vector = embedding.vector_by_entity_id.get(entityId);
797
+ if (vector === undefined || vector.length !== descriptor.dimension
798
+ || vector.some((number) => !Number.isFinite(number))) {
799
+ throw new Error(`neutral_artifact_database_embedding_vector_invalid:${entityId}`);
800
+ }
801
+ if (vectorSourceJsonSha256(vector) !== vectorSha256) {
802
+ throw new Error(`neutral_artifact_database_embedding_vector_hash_mismatch:${entityId}`);
803
+ }
804
+ for (const number of vector)
805
+ values[offset++] = number;
806
+ seen.add(entityId);
807
+ }
808
+ for (const entityId of embedding.vector_by_entity_id.keys()) {
809
+ if (!searchableIds.has(entityId)) {
810
+ throw new Error(`neutral_artifact_database_embedding_vector_unexpected:${entityId}`);
811
+ }
812
+ }
813
+ if (offset !== values.length || seen.size !== searchableIds.size) {
814
+ throw new Error("neutral_artifact_database_embedding_vector_order_incomplete");
815
+ }
816
+ return values;
817
+ }
818
+ /**
819
+ * 由 compiler materialization 直接生成 fresh-only compact database v1;Browser
820
+ * 与 Node 必须注入同一 Rust/WASM adapter,禁止在 JS 内先拼出含向量的完整 JSON。
821
+ */
822
+ export async function encodeNeutralReviewDatabaseSnapshot(input) {
823
+ if (!Number.isSafeInteger(input.content_epoch) || input.content_epoch <= 0) {
824
+ throw new Error("neutral_artifact_database_epoch_invalid");
825
+ }
826
+ const model = input.compilation.model;
827
+ const graphEntities = deliveryGraphEntities(model.graph.entities);
828
+ const entities = snapshotEntities(graphEntities);
829
+ const relations = snapshotRelations(model.graph.relations);
830
+ const entityIds = new Set(entities.map((entity) => entity.id));
831
+ for (const relation of relations) {
832
+ if (!entityIds.has(relation.src) || !entityIds.has(relation.dst)) {
833
+ throw new Error(`neutral_artifact_relation_endpoint_missing:${relation.relation_id}`);
834
+ }
835
+ }
836
+ const witness = input.source.witness;
837
+ const graph = toJsonValue({
838
+ v: 5,
839
+ entities,
840
+ relations,
841
+ });
842
+ const sidecar = toJsonValue({
843
+ initial_scene: initialTmwSceneFromMetadata(model.metadata == null ? {} : record(model.metadata, "neutral_artifact_metadata_object_required")),
844
+ retrieval_config: model.retrieval,
845
+ triggers: model.runtime_definitions.triggers,
846
+ trigger_variables_schema: model.runtime_definitions.trigger_variables_schema,
847
+ cg_defs: model.runtime_definitions.cg_defs,
848
+ achievement_defs: model.runtime_definitions.achievement_defs,
849
+ transaction_defs: model.runtime_definitions.transaction_defs,
850
+ ...(model.spatial_world == null ? {} : { spatial_world: model.spatial_world }),
851
+ tmw_source: {
852
+ schema_version: 3,
853
+ runtime_generation: 3,
854
+ owner_id: witness.owner_id,
855
+ scenario_id: witness.scenario_id,
856
+ epoch: input.content_epoch,
857
+ source_revision: witness.source_revision,
858
+ previous_revision_digest: null,
859
+ trigger_document: model.trigger_document,
860
+ view_interaction_mobile: model.view_interaction_mobile,
861
+ view_interaction_pc: model.view_interaction_pc,
862
+ music_candidate_source: model.music_candidate_source,
863
+ source_metadata: model.source_metadata,
864
+ },
865
+ view_access_policy: buildDerivedViewAccessPolicyV3(graphEntities, entities, model.graph.relations, relations),
866
+ });
867
+ const result = await input.packFrozenDatabase(Object.freeze({
868
+ graph_v5_json: canonicalBytes(graph),
869
+ embedding_index_v1_json: input.embedding.index_resource.bytes.slice(),
870
+ vector_values: databaseVectorValues(graphEntities, entities, input.embedding),
871
+ sidecar_v5_json: canonicalBytes(sidecar),
872
+ }));
873
+ if (result.codec !== "frozen-database-v1"
874
+ || (result.vector_dtype !== "f32-le" && result.vector_dtype !== "f64-le")
875
+ || !(result.bytes instanceof Uint8Array)
876
+ || result.bytes.byteLength === 0) {
877
+ throw new Error("neutral_artifact_database_pack_result_invalid");
878
+ }
879
+ if (result.vector_dtype === "f64-le") {
880
+ throw new Error("neutral_artifact_database_vector_dtype_runtime_unsupported:f64-le");
881
+ }
882
+ return result.bytes.slice();
883
+ }
884
+ /** 最终 FrozenPublishInput 唯一共享装配点;返回前执行 ArtifactV5 全量 normalization。 */
885
+ export async function assembleNeutralFrozenPublishInput(input) {
886
+ const { source, compilation, embedding } = input;
887
+ const witness = source.witness;
888
+ if (!Number.isSafeInteger(input.content_epoch) || input.content_epoch <= 0
889
+ || embedding.descriptor.source_revision !== witness.source_revision
890
+ || embedding.descriptor.source_revision_digest !== witness.revision_digest
891
+ || input.runtime_identity.runtime_contract_hash !== compilation.model.trigger_document.runtime_contract_hash) {
892
+ throw new Error("neutral_artifact_frozen_binding_mismatch");
893
+ }
894
+ const [mobile, pc] = await Promise.all([
895
+ bindViewInteractionManifestForFrozenArtifact(compilation.model.view_interaction_mobile, input.content_epoch, input.runtime_identity.runtime_contract_hash, input.view_publish_mobile_bytes),
896
+ bindViewInteractionManifestForFrozenArtifact(compilation.model.view_interaction_pc, input.content_epoch, input.runtime_identity.runtime_contract_hash, input.view_publish_pc_bytes),
897
+ ]);
898
+ const frozen = Object.freeze({
899
+ input_schema_version: FROZEN_PUBLISH_INPUT_SCHEMA_VERSION,
900
+ captured_revision: Object.freeze({
901
+ schema_version: 3,
902
+ owner_id: witness.owner_id,
903
+ scenario_id: witness.scenario_id,
904
+ epoch: input.content_epoch,
905
+ revision_number: witness.source_revision,
906
+ revision_id: witness.head_digest,
907
+ source_sha256: witness.source_digest,
908
+ revision_digest: witness.revision_digest,
909
+ }),
910
+ runtime_contract_hash: input.runtime_identity.runtime_contract_hash,
911
+ metadata: compilation.model.metadata,
912
+ protagonist: compilation.model.protagonist,
913
+ system_prompts: compilation.model.system_prompts,
914
+ database_bytes: input.database_bytes.slice(),
915
+ database_delivery: Object.freeze({ codec: "scnb-v2", bytes: input.database_delivery_bytes.slice() }),
916
+ trigger_document: compilation.model.trigger_document,
917
+ compiled_plan: attachFreshRuntime3PlayerTurnPolicy(compilation.model.compiled_plan, compilation.model.system_prompts, input.runtime_identity.runtime_contract_hash),
918
+ view_source_bytes: compilation.model.view_source_bytes.slice(),
919
+ view_publish_mobile_bytes: input.view_publish_mobile_bytes.slice(),
920
+ view_publish_pc_bytes: input.view_publish_pc_bytes.slice(),
921
+ view_interaction_mobile: mobile,
922
+ view_interaction_pc: pc,
923
+ music_candidate_source: compilation.model.music_candidate_source,
924
+ retrieval: compilation.model.retrieval,
925
+ resources: mergeNeutralArtifactResources(compilation.model.resources, compilation.witness_resources, [embedding.index_resource]),
926
+ runtime_identity: Object.freeze({ ...input.runtime_identity }),
927
+ archive_bytes: source.source_bytes.slice(),
928
+ });
929
+ buildArtifactV5(frozen);
930
+ return frozen;
931
+ }
932
+ function sourceOf(entity, sourceTextIndex) {
933
+ if (entity.source_kind === undefined && entity.source_text === undefined) {
934
+ const kind = text(entity.kind, "neutral_artifact_embedding_entity_kind_invalid");
935
+ const props = record(entity.props ?? {}, "neutral_artifact_embedding_entity_props_invalid");
936
+ const documentText = (value, code) => {
937
+ if (typeof value !== "string")
938
+ throw new Error(code);
939
+ const normalized = value.normalize("NFC").trim();
940
+ if (normalized.length === 0)
941
+ throw new Error(code);
942
+ return normalized;
943
+ };
944
+ if (kind === "world_source_chunk")
945
+ return {
946
+ source_kind: "chunk",
947
+ source_text: documentText(props.index_text, "neutral_artifact_embedding_chunk_index_text_invalid"),
948
+ };
949
+ if (kind === "world_document_statement")
950
+ return {
951
+ source_kind: "statement",
952
+ source_text: documentText(props.assertion, "neutral_artifact_embedding_statement_assertion_invalid"),
953
+ };
954
+ if (kind === "world_document_edge_assertion")
955
+ return {
956
+ source_kind: "edge_assertion",
957
+ source_text: documentText(props.assertion, "neutral_artifact_embedding_edge_assertion_invalid"),
958
+ };
959
+ return {
960
+ source_kind: "entity",
961
+ source_text: deriveNeutralArtifactEntityEmbeddingSourceTextFromIndex(entity, sourceTextIndex),
962
+ };
963
+ }
964
+ const kind = entity.source_kind;
965
+ if (kind !== "entity" && kind !== "chunk" && kind !== "statement" && kind !== "edge_assertion") {
966
+ throw new Error("neutral_artifact_embedding_source_kind_invalid");
967
+ }
968
+ return { source_kind: kind, source_text: text(entity.source_text, "neutral_artifact_embedding_source_text_invalid") };
969
+ }
970
+ /** Formal freeze 与本地 authoring recall 共用的唯一 corpus 文本投影;关系索引每批只构建一次。 */
971
+ export function projectNeutralArtifactEmbeddingSources(graph) {
972
+ const sourceTextIndex = buildNeutralArtifactEmbeddingSourceTextIndex(graph);
973
+ const entities = graph.entities.map((value) => record(value, "neutral_artifact_embedding_entity_invalid"))
974
+ .filter(retrievalSearchableEntity)
975
+ .sort((left, right) => text(left.entity_id, "neutral_artifact_embedding_entity_id_invalid")
976
+ .localeCompare(text(right.entity_id, "neutral_artifact_embedding_entity_id_invalid")));
977
+ return Object.freeze(entities.map((entity) => {
978
+ const derived = sourceOf(entity, sourceTextIndex);
979
+ if (derived.source_text.length > MAX_EMBEDDING_SOURCE_TEXT_CHARACTERS) {
980
+ throw new Error(`neutral_artifact_embedding_source_text_too_long:${String(entity.entity_id)}`);
981
+ }
982
+ return Object.freeze({
983
+ entity_id: text(entity.entity_id, "neutral_artifact_embedding_entity_id_invalid"),
984
+ ...derived,
985
+ });
986
+ }));
987
+ }
988
+ export async function prepareNeutralArtifactEmbeddingSnapshot(graph, source, runBatch, signal) {
989
+ if (!source.owner_id || !source.scenario_id || !Number.isSafeInteger(source.source_revision)
990
+ || source.source_revision <= 0 || !SHA256.test(source.revision_digest)) {
991
+ throw new Error("neutral_artifact_embedding_source_identity_invalid");
992
+ }
993
+ const projected = projectNeutralArtifactEmbeddingSources(graph);
994
+ if (projected.length === 0)
995
+ throw new Error("neutral_artifact_embedding_searchable_entity_required");
996
+ const requests = await Promise.all(projected.map(async (entity) => {
997
+ return Object.freeze({
998
+ ...entity,
999
+ source_text_sha256: sha256Hex(new TextEncoder().encode(entity.source_text)),
1000
+ });
1001
+ }));
1002
+ const result = await runBatch(Object.freeze({ source, entities: Object.freeze(requests), ...(signal === undefined ? {} : { signal }) }));
1003
+ const descriptor = result.descriptor;
1004
+ if (!descriptor.provider || !descriptor.model || !Number.isSafeInteger(descriptor.dimension) || descriptor.dimension <= 0
1005
+ || !SHA256.test(descriptor.descriptor_sha256) || descriptor.source_revision !== source.source_revision
1006
+ || descriptor.source_revision_digest !== source.revision_digest || result.entries.length !== requests.length) {
1007
+ throw new Error("neutral_artifact_embedding_descriptor_mismatch");
1008
+ }
1009
+ const expected = new Map(requests.map((request) => [request.entity_id, request]));
1010
+ const vectors = new Map();
1011
+ for (const entry of result.entries) {
1012
+ const request = expected.get(entry.entity_id);
1013
+ if (request === undefined || vectors.has(entry.entity_id) || entry.source_kind !== request.source_kind
1014
+ || entry.source_text_sha256 !== request.source_text_sha256 || entry.vector.length !== descriptor.dimension
1015
+ || entry.vector.some((value) => !Number.isFinite(value))) {
1016
+ throw new Error(`neutral_artifact_embedding_entry_mismatch:${entry.entity_id}`);
1017
+ }
1018
+ vectors.set(entry.entity_id, Object.freeze([...entry.vector]));
1019
+ }
1020
+ const entries = requests.map((request) => {
1021
+ const vector = vectors.get(request.entity_id);
1022
+ if (vector === undefined)
1023
+ throw new Error(`neutral_artifact_embedding_entry_missing:${request.entity_id}`);
1024
+ return { entity_id: request.entity_id, source_kind: request.source_kind, source_text_sha256: request.source_text_sha256,
1025
+ vector_sha256: vectorSourceJsonSha256(vector) };
1026
+ });
1027
+ const materialization = Object.freeze({
1028
+ descriptor: Object.freeze({ ...descriptor }),
1029
+ vector_by_entity_id: vectors,
1030
+ index_resource: Object.freeze({
1031
+ path: NEUTRAL_ARTIFACT_EMBEDDING_INDEX_PATH,
1032
+ media_type: "application/vnd.chat.embedding-index.v1+json",
1033
+ bytes: canonicalBytes(toJsonValue({ schema_version: 1, ...descriptor, entries })),
1034
+ }),
1035
+ });
1036
+ return Object.freeze({
1037
+ source: Object.freeze({ ...source }),
1038
+ batch: Object.freeze({
1039
+ descriptor: Object.freeze({ ...descriptor }),
1040
+ entries: Object.freeze(result.entries.map((entry) => Object.freeze({
1041
+ ...entry,
1042
+ vector: Object.freeze([...entry.vector]),
1043
+ }))),
1044
+ }),
1045
+ materialization,
1046
+ });
1047
+ }
1048
+ export async function materializeNeutralArtifactEmbeddings(graph, source, runBatch, signal) {
1049
+ return (await prepareNeutralArtifactEmbeddingSnapshot(graph, source, runBatch, signal)).materialization;
1050
+ }