@world-engines/scenario-review-snapshot 0.1.4 → 1.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,21 @@
1
+ import { type CompileTargetProfile } from "@world-engines/tmw/compile-target-profile";
2
+ import { type NeutralSourceCompilerReceiptCore } from "@world-engines/tmw/neutral-source-compiler";
3
+ export interface ArtifactCompilerWitnessBlob {
4
+ readonly bytes: Uint8Array;
5
+ readonly sha256: string;
6
+ }
7
+ export interface ArtifactCompilerWitnessVerificationInput {
8
+ readonly scenario_id: string;
9
+ readonly source_manifest_sha256: string;
10
+ readonly target_profile: ArtifactCompilerWitnessBlob;
11
+ readonly compiler_receipt: ArtifactCompilerWitnessBlob;
12
+ }
13
+ export interface VerifiedArtifactCompilerWitness {
14
+ readonly target_profile: CompileTargetProfile;
15
+ readonly compiler_receipt: NeutralSourceCompilerReceiptCore;
16
+ }
17
+ /**
18
+ * 验证 ArtifactV5 中两份 compiler witness 的共同语义,而不信任生产者
19
+ * 曾经走过哪个 compile path。三个 Player reader 与 bundle parser 共用此门。
20
+ */
21
+ export declare function verifyArtifactCompilerWitness(input: ArtifactCompilerWitnessVerificationInput): VerifiedArtifactCompilerWitness;
@@ -0,0 +1,75 @@
1
+ import { canonicalCompileTargetProfileBytes, parseCompileTargetProfile, } from "@world-engines/tmw/compile-target-profile";
2
+ import { canonicalNeutralSourceCompilerReceiptCoreBytes, parseNeutralSourceCompilerReceiptCore, } from "@world-engines/tmw/neutral-source-compiler";
3
+ import { sha256Hex } from "./canonical.js";
4
+ const strictDecoder = new TextDecoder("utf-8", { fatal: true });
5
+ const SHA256_PATTERN = /^[0-9a-f]{64}$/u;
6
+ function bytesEqual(left, right) {
7
+ if (left.byteLength !== right.byteLength)
8
+ return false;
9
+ let difference = 0;
10
+ for (let index = 0; index < left.byteLength; index += 1) {
11
+ difference |= (left[index] ?? 0) ^ (right[index] ?? 0);
12
+ }
13
+ return difference === 0;
14
+ }
15
+ function parseJson(bytes, kind) {
16
+ try {
17
+ return JSON.parse(strictDecoder.decode(bytes));
18
+ }
19
+ catch (cause) {
20
+ throw new Error(`artifact_compiler_witness_json_invalid:${kind}`, { cause });
21
+ }
22
+ }
23
+ function requireBlobIdentity(blob, kind) {
24
+ if (!(blob.bytes instanceof Uint8Array)
25
+ || !SHA256_PATTERN.test(blob.sha256)
26
+ || sha256Hex(blob.bytes) !== blob.sha256) {
27
+ throw new Error(`artifact_compiler_witness_identity_invalid:${kind}`);
28
+ }
29
+ }
30
+ /**
31
+ * 验证 ArtifactV5 中两份 compiler witness 的共同语义,而不信任生产者
32
+ * 曾经走过哪个 compile path。三个 Player reader 与 bundle parser 共用此门。
33
+ */
34
+ export function verifyArtifactCompilerWitness(input) {
35
+ if (input.scenario_id.length === 0
36
+ || input.scenario_id !== input.scenario_id.trim().normalize("NFC")
37
+ || !SHA256_PATTERN.test(input.source_manifest_sha256)) {
38
+ throw new Error("artifact_compiler_witness_scope_invalid");
39
+ }
40
+ requireBlobIdentity(input.target_profile, "compile_target_profile");
41
+ requireBlobIdentity(input.compiler_receipt, "compiler_receipt");
42
+ const targetProfile = parseCompileTargetProfile(parseJson(input.target_profile.bytes, "compile_target_profile"));
43
+ if (!bytesEqual(input.target_profile.bytes, canonicalCompileTargetProfileBytes(targetProfile))) {
44
+ throw new Error("artifact_compiler_witness_target_profile_noncanonical");
45
+ }
46
+ const compilerReceipt = parseNeutralSourceCompilerReceiptCore(parseJson(input.compiler_receipt.bytes, "compiler_receipt"));
47
+ if (!bytesEqual(input.compiler_receipt.bytes, canonicalNeutralSourceCompilerReceiptCoreBytes(compilerReceipt))) {
48
+ throw new Error("artifact_compiler_witness_compiler_receipt_noncanonical");
49
+ }
50
+ if (compilerReceipt.scenario_id !== input.scenario_id) {
51
+ throw new Error("artifact_compiler_witness_scenario_mismatch");
52
+ }
53
+ if (compilerReceipt.source_manifest_sha256 !== input.source_manifest_sha256) {
54
+ throw new Error("artifact_compiler_witness_source_manifest_mismatch");
55
+ }
56
+ if (compilerReceipt.compiler_release !== targetProfile.compiler_release) {
57
+ throw new Error("artifact_compiler_witness_compiler_release_mismatch");
58
+ }
59
+ if (compilerReceipt.target_profile_sha256 !== input.target_profile.sha256) {
60
+ throw new Error("artifact_compiler_witness_target_profile_digest_mismatch");
61
+ }
62
+ const receiptVersions = new Map(compilerReceipt.stages.map((stage) => [stage.stage_id, stage.stage_version]));
63
+ if (receiptVersions.size !== targetProfile.stage_versions.length
64
+ || compilerReceipt.stages.length !== targetProfile.stage_versions.length
65
+ || targetProfile.stage_versions.some((stage) => receiptVersions.get(stage.stage_id) !== stage.stage_version)) {
66
+ throw new Error("artifact_compiler_witness_stage_set_mismatch");
67
+ }
68
+ if (compilerReceipt.stages.some((stage) => stage.dependency_outputs.some((dependency) => !receiptVersions.has(dependency.stage_id)))) {
69
+ throw new Error("artifact_compiler_witness_stage_dependency_unknown");
70
+ }
71
+ return Object.freeze({
72
+ target_profile: targetProfile,
73
+ compiler_receipt: compilerReceipt,
74
+ });
75
+ }
@@ -1,10 +1,18 @@
1
1
  import type { JsonValue } from "./types.js";
2
- import { type ArtifactV5, type FrozenPublishInput, type FrozenArtifactBundle, type FrozenRuntimeIdentity, type LegacyArtifactV5Package2Read, type PublishedArtifactHead, type PublishedArtifactHeadCore } from "./artifact.js";
2
+ import { type ArtifactV5, type BinaryIdentity, type FrozenPublishInput, type FrozenArtifactBundle, type FrozenViewPublicationCapture, type FrozenRuntimeIdentity, type LegacyArtifactV5Package2Read, type PublishedArtifactHead, type PublishedArtifactHeadCore } from "./artifact.js";
3
3
  export declare function canonicalBytes(value: JsonValue): Uint8Array;
4
4
  export declare function canonicalSha256(value: JsonValue): string;
5
5
  export declare function buildArtifactV5(input: FrozenPublishInput): ArtifactV5;
6
6
  export declare function buildFrozenArtifactBundle(input: FrozenPublishInput): FrozenArtifactBundle;
7
7
  export declare function parseFrozenArtifactBundle(value: unknown): FrozenArtifactBundle;
8
+ /**
9
+ * 解码后 submit、恢复 ledger 与 bundle parser 共用的唯一 HTML capture 校验器。
10
+ * 未携带旧字段只兼容两端均为空的历史 bundle;非空 manifest 必须 fail closed。
11
+ */
12
+ export declare function parseFrozenViewPublications(value: unknown, views: Readonly<{
13
+ publish_mobile: BinaryIdentity;
14
+ publish_pc: BinaryIdentity;
15
+ }>): readonly FrozenViewPublicationCapture[];
8
16
  export declare function canonicalArtifactV5Bytes(artifact: ArtifactV5): Uint8Array;
9
17
  export declare function artifactV5ManifestSha256(artifact: ArtifactV5): string;
10
18
  export declare function parseArtifactV5(value: unknown): ArtifactV5;
@@ -2,6 +2,7 @@ import { canonicalJson, compareUtf8, sha256Hex, toJsonValue } from "./canonical.
2
2
  import { parsePublishedDeviceViewReceipt, assertDeviceViewsMatchArtifact } from "./device-view-receipt.js";
3
3
  import { parseViewInteractionManifestV3, } from "@world-engines/tmw/view-interaction-manifest-v3";
4
4
  import { parseMusicCandidateSourceV3, } from "@world-engines/tmw/music-candidate-set";
5
+ import { verifyArtifactCompilerWitness } from "./artifact-compiler-witness.js";
5
6
  import { ARTIFACT_COMPILER_WITNESS_OBJECTS, ARTIFACT_COMPILER_WITNESS_PATHS, ARTIFACT_COMPILER_WITNESS_RESERVED_PREFIX, ARTIFACT_MANIFEST_VERSION_V5, ARTIFACT_PACKAGE_FORMAT_VERSION_V5, CAPTURED_REVISION_SCHEMA_VERSION, FROZEN_PUBLISH_INPUT_SCHEMA_VERSION, FROZEN_ARTIFACT_OBJECT_COUNT, FROZEN_DATABASE_DELIVERY_CODEC, FROZEN_DATABASE_DELIVERY_MEDIA_TYPE, FROZEN_DATABASE_DELIVERY_MAGIC, FROZEN_DATABASE_DELIVERY_RELATIVE_PATH, EARLIER_LEGACY_ARTIFACT_PACKAGE_FORMAT_VERSION_V5, LEGACY_ARTIFACT_PACKAGE_FORMAT_VERSION_V5, PUBLISHED_ARTIFACT_HEAD_SCHEMA_VERSION, } from "./artifact.js";
6
7
  const encoder = new TextEncoder();
7
8
  const strictDecoder = new TextDecoder("utf-8", { fatal: true });
@@ -111,11 +112,18 @@ export function buildFrozenArtifactBundle(input) {
111
112
  ].sort((left, right) => compareUtf8(left.relative_path, right.relative_path));
112
113
  assertStrictlySortedUnique(objects.map((object) => object.relative_path), "frozen_artifact_object_path_duplicate");
113
114
  assertBundleMatchesArtifact(artifact, objects);
114
- return parseFrozenArtifactBundle({ artifact, objects });
115
+ const viewPublications = parseFrozenViewPublications([
116
+ ...(normalized.view_publish_mobile_bytes.byteLength === 0 ? [] : [frozenViewPublication("mobile", normalized.view_publish_mobile_bytes)]),
117
+ ...(normalized.view_publish_pc_bytes.byteLength === 0 ? [] : [frozenViewPublication("pc", normalized.view_publish_pc_bytes)]),
118
+ ], artifact.views);
119
+ return parseFrozenArtifactBundle({ artifact, objects, view_publications: viewPublications });
115
120
  }
116
121
  export function parseFrozenArtifactBundle(value) {
117
122
  const root = plainObject(value, "frozen_artifact_bundle_object_required");
118
- exactKeys(root, ["artifact", "objects"], "frozen_artifact_bundle");
123
+ const bundleKeys = Object.hasOwn(root, "view_publications")
124
+ ? ["artifact", "objects", "view_publications"]
125
+ : ["artifact", "objects"];
126
+ exactKeys(root, bundleKeys, "frozen_artifact_bundle");
119
127
  const artifact = parseArtifactV5(root.artifact);
120
128
  if (!Array.isArray(root.objects))
121
129
  throw new Error("frozen_artifact_bundle_objects_invalid");
@@ -123,7 +131,102 @@ export function parseFrozenArtifactBundle(value) {
123
131
  assertStrictlySortedUnique(objects.map((object) => object.relative_path), "frozen_artifact_object_order_invalid");
124
132
  assertBundleMatchesArtifact(artifact, objects);
125
133
  assertCanonicalBundlePayloads(artifact, objects);
126
- return Object.freeze({ artifact, objects: Object.freeze(objects) });
134
+ const viewPublications = parseFrozenViewPublications(root.view_publications, artifact.views);
135
+ return Object.freeze({
136
+ artifact,
137
+ objects: Object.freeze(objects),
138
+ view_publications: viewPublications,
139
+ });
140
+ }
141
+ const FROZEN_VIEW_PUBLICATION_SPECS = Object.freeze({
142
+ mobile: Object.freeze({ relative_path: "view-publish-mobile.html" }),
143
+ pc: Object.freeze({ relative_path: "view-publish-pc.html" }),
144
+ });
145
+ const FROZEN_VIEW_PUBLICATION_MEDIA_TYPE = "text/html; charset=utf-8";
146
+ function frozenViewPublication(device, value) {
147
+ const bytes = requireBytes(value, `frozen_view_publication_${device}_bytes_invalid`);
148
+ return Object.freeze({
149
+ device,
150
+ relative_path: FROZEN_VIEW_PUBLICATION_SPECS[device].relative_path,
151
+ media_type: FROZEN_VIEW_PUBLICATION_MEDIA_TYPE,
152
+ ...binaryIdentity(bytes),
153
+ bytes,
154
+ });
155
+ }
156
+ /**
157
+ * 解码后 submit、恢复 ledger 与 bundle parser 共用的唯一 HTML capture 校验器。
158
+ * 未携带旧字段只兼容两端均为空的历史 bundle;非空 manifest 必须 fail closed。
159
+ */
160
+ export function parseFrozenViewPublications(value, views) {
161
+ const expected = Object.freeze({
162
+ mobile: parseBinaryIdentity(views.publish_mobile),
163
+ pc: parseBinaryIdentity(views.publish_pc),
164
+ });
165
+ const emptySha256 = sha256Hex(new Uint8Array());
166
+ for (const device of ["mobile", "pc"]) {
167
+ if (expected[device].size_bytes === 0 && expected[device].sha256 !== emptySha256) {
168
+ throw new Error(`frozen_view_publication_empty_identity_mismatch:${device}`);
169
+ }
170
+ }
171
+ if (value === undefined) {
172
+ if (expected.mobile.size_bytes !== 0 || expected.pc.size_bytes !== 0) {
173
+ throw new Error("frozen_view_publications_missing");
174
+ }
175
+ return Object.freeze([]);
176
+ }
177
+ if (!Array.isArray(value))
178
+ throw new Error("frozen_view_publications_invalid");
179
+ const captures = [];
180
+ const seen = new Set();
181
+ for (const entry of value) {
182
+ const object = plainObject(entry, "frozen_view_publication_invalid");
183
+ exactKeys(object, [
184
+ "device", "relative_path", "media_type", "encoding", "size_bytes", "sha256", "bytes",
185
+ ], "frozen_view_publication");
186
+ if (object.device !== "mobile" && object.device !== "pc") {
187
+ throw new Error("frozen_view_publication_device_invalid");
188
+ }
189
+ const device = object.device;
190
+ if (seen.has(device))
191
+ throw new Error(`frozen_view_publication_device_duplicate:${device}`);
192
+ seen.add(device);
193
+ if (object.relative_path !== FROZEN_VIEW_PUBLICATION_SPECS[device].relative_path) {
194
+ throw new Error(`frozen_view_publication_path_mismatch:${device}`);
195
+ }
196
+ if (object.media_type !== FROZEN_VIEW_PUBLICATION_MEDIA_TYPE) {
197
+ throw new Error(`frozen_view_publication_media_type_mismatch:${device}`);
198
+ }
199
+ if (object.encoding !== "bytes") {
200
+ throw new Error(`frozen_view_publication_encoding_invalid:${device}`);
201
+ }
202
+ const capture = frozenViewPublication(device, requireBytes(object.bytes, `frozen_view_publication_${device}_bytes_invalid`));
203
+ try {
204
+ strictDecoder.decode(capture.bytes);
205
+ }
206
+ catch {
207
+ throw new Error(`frozen_view_publication_utf8_invalid:${device}`);
208
+ }
209
+ if (object.size_bytes !== capture.size_bytes || object.sha256 !== capture.sha256) {
210
+ throw new Error(`frozen_view_publication_readback_mismatch:${device}`);
211
+ }
212
+ const identity = expected[device];
213
+ if (capture.size_bytes !== identity.size_bytes || capture.sha256 !== identity.sha256) {
214
+ throw new Error(`frozen_view_publication_artifact_mismatch:${device}`);
215
+ }
216
+ if (capture.size_bytes === 0) {
217
+ throw new Error(`frozen_view_publication_empty_must_be_omitted:${device}`);
218
+ }
219
+ captures.push(capture);
220
+ }
221
+ for (const device of ["mobile", "pc"]) {
222
+ const hasCapture = seen.has(device);
223
+ const isRequired = expected[device].size_bytes > 0;
224
+ if (hasCapture !== isRequired) {
225
+ throw new Error(`frozen_view_publication_capture_count_mismatch:${device}`);
226
+ }
227
+ }
228
+ captures.sort((left, right) => left.device === right.device ? 0 : left.device === "mobile" ? -1 : 1);
229
+ return Object.freeze(captures);
127
230
  }
128
231
  function canonicalArtifactV5CoreBytes(core) {
129
232
  if (core.package_format_version === LEGACY_ARTIFACT_PACKAGE_FORMAT_VERSION_V5) {
@@ -805,8 +908,17 @@ function assertCanonicalBundlePayloads(artifact, objects) {
805
908
  const viewInteractionMobile = assertCanonicalJsonObject(one("view_interaction_mobile"));
806
909
  const viewInteractionPc = assertCanonicalJsonObject(one("view_interaction_pc"));
807
910
  const musicCandidateSource = assertCanonicalJsonObject(one("music_candidate_source"));
808
- assertCanonicalJsonObject(one("compile_target_profile"));
809
- assertCanonicalJsonObject(one("compiler_receipt"));
911
+ const sourceManifest = one("source_manifest");
912
+ const targetProfile = one("compile_target_profile");
913
+ const compilerReceipt = one("compiler_receipt");
914
+ assertCanonicalJsonObject(targetProfile);
915
+ assertCanonicalJsonObject(compilerReceipt);
916
+ verifyArtifactCompilerWitness({
917
+ scenario_id: artifact.scenario_id,
918
+ source_manifest_sha256: sourceManifest.sha256,
919
+ target_profile: { bytes: targetProfile.bytes, sha256: targetProfile.sha256 },
920
+ compiler_receipt: { bytes: compilerReceipt.bytes, sha256: compilerReceipt.sha256 },
921
+ });
810
922
  normalizeViewInteractionManifest(viewInteractionMobile, artifact.epoch, artifact.runtime_contract_hash, artifact.views.publish_mobile.sha256, "mobile");
811
923
  normalizeViewInteractionManifest(viewInteractionPc, artifact.epoch, artifact.runtime_contract_hash, artifact.views.publish_pc.sha256, "pc");
812
924
  if (parseMusicCandidateSourceV3(musicCandidateSource) === null) {
@@ -215,6 +215,17 @@ export interface FrozenArtifactObjectIdentity extends BinaryIdentity {
215
215
  export interface FrozenArtifactObject extends FrozenArtifactObjectIdentity {
216
216
  readonly bytes: Uint8Array;
217
217
  }
218
+ export type FrozenViewPublicationDevice = "mobile" | "pc";
219
+ /**
220
+ * 审核提交随 Artifact 一次性携带的发布 HTML 原始字节。该对象不参与
221
+ * ArtifactV5 digest;其身份已由 `artifact.views.publish_*` 固定。
222
+ */
223
+ export interface FrozenViewPublicationCapture extends BinaryIdentity {
224
+ readonly device: FrozenViewPublicationDevice;
225
+ readonly relative_path: "view-publish-mobile.html" | "view-publish-pc.html";
226
+ readonly media_type: "text/html; charset=utf-8";
227
+ readonly bytes: Uint8Array;
228
+ }
218
229
  export interface ArtifactCompilerWitnessIdentity {
219
230
  readonly source_manifest: FrozenArtifactObjectIdentity & {
220
231
  readonly kind: "source_manifest";
@@ -236,6 +247,7 @@ export interface ArtifactCompilerWitnessIdentity {
236
247
  export interface FrozenArtifactBundle {
237
248
  readonly artifact: ArtifactV5;
238
249
  readonly objects: readonly FrozenArtifactObject[];
250
+ readonly view_publications: readonly FrozenViewPublicationCapture[];
239
251
  }
240
252
  export interface PublishedArtifactHeadCore {
241
253
  readonly head_schema_version: typeof PUBLISHED_ARTIFACT_HEAD_SCHEMA_VERSION;
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  export { appendJsonPointer, canonicalJson, compareUtf8, escapeJsonPointerToken, parseJsonPointer, sha256Hex, toJsonValue, unescapeJsonPointerToken, utf8ByteLength, } from "./canonical.js";
2
2
  export { buildScenarioReviewSnapshot, DEFAULT_SCENARIO_REVIEW_SNAPSHOT_LIMITS, reconstructRecordPayload, stableRelationId, } from "./snapshot.js";
3
- export { ARTIFACT_MANIFEST_VERSION_V5, ARTIFACT_PACKAGE_FORMAT_VERSION_V5, LEGACY_ARTIFACT_PACKAGE_FORMAT_VERSION_V5, EARLIER_LEGACY_ARTIFACT_PACKAGE_FORMAT_VERSION_V5, CAPTURED_REVISION_SCHEMA_VERSION, FROZEN_PUBLISH_INPUT_SCHEMA_VERSION, FROZEN_DATABASE_DELIVERY_CODEC, FROZEN_DATABASE_DELIVERY_MEDIA_TYPE, FROZEN_DATABASE_DELIVERY_MAGIC, FROZEN_DATABASE_DELIVERY_RELATIVE_PATH, PUBLISHED_ARTIFACT_HEAD_SCHEMA_VERSION, } from "./artifact.js";
4
- export { artifactV5ManifestSha256, buildArtifactV5, buildFrozenArtifactBundle, buildPublishedArtifactHead, canonicalArtifactV5Bytes, canonicalBytes, canonicalPublishedArtifactHeadCoreBytes, canonicalSha256, digestPublishedArtifactHeadCore, parseArtifactV5, parseFrozenArtifactBundle, parseLegacyArtifactV5Package2, parsePublishedArtifactHead, } from "./artifact-digest.js";
3
+ export { ARTIFACT_MANIFEST_VERSION_V5, ARTIFACT_COMPILER_WITNESS_OBJECTS, ARTIFACT_PACKAGE_FORMAT_VERSION_V5, LEGACY_ARTIFACT_PACKAGE_FORMAT_VERSION_V5, EARLIER_LEGACY_ARTIFACT_PACKAGE_FORMAT_VERSION_V5, CAPTURED_REVISION_SCHEMA_VERSION, FROZEN_PUBLISH_INPUT_SCHEMA_VERSION, FROZEN_DATABASE_DELIVERY_CODEC, FROZEN_DATABASE_DELIVERY_MEDIA_TYPE, FROZEN_DATABASE_DELIVERY_MAGIC, FROZEN_DATABASE_DELIVERY_RELATIVE_PATH, PUBLISHED_ARTIFACT_HEAD_SCHEMA_VERSION, } from "./artifact.js";
4
+ export { artifactV5ManifestSha256, buildArtifactV5, buildFrozenArtifactBundle, buildPublishedArtifactHead, canonicalArtifactV5Bytes, canonicalBytes, canonicalPublishedArtifactHeadCoreBytes, canonicalSha256, digestPublishedArtifactHeadCore, parseArtifactV5, parseFrozenArtifactBundle, parseFrozenViewPublications, parseLegacyArtifactV5Package2, parsePublishedArtifactHead, } from "./artifact-digest.js";
5
+ export { verifyArtifactCompilerWitness } from "./artifact-compiler-witness.js";
6
+ export type { ArtifactCompilerWitnessBlob, ArtifactCompilerWitnessVerificationInput, VerifiedArtifactCompilerWitness, } from "./artifact-compiler-witness.js";
5
7
  export type { CoverageManifest, CoverageManifestCore, CoverageRecordEntry, CoverageSectionEntry, JsonPrimitive, JsonValue, ReviewFragmentValueKind, ReviewRecordKind, ReviewSection, ScenarioReviewChunk, ScenarioReviewFragment, ScenarioReviewRecord, ScenarioReviewSnapshot, ScenarioReviewSnapshotCore, ScenarioReviewSnapshotInput, ScenarioReviewSnapshotLimits, ScenarioReviewSupplementalRecordInput, } from "./types.js";
6
- export type { ArtifactV5, ArtifactV5Core, BinaryIdentity, CanonicalJsonIdentity, CapturedRevision, FrozenPublishInput, FrozenDatabaseDelivery, FrozenDatabaseDeliveryIdentity, FrozenArtifactBundle, FrozenArtifactObject, FrozenArtifactObjectIdentity, FrozenArtifactObjectKind, FrozenPublishResource, FrozenResourceIdentity, FrozenRuntimeIdentity, PublishedViewBuildReceipt, PublishedViewBuildHtmlReceipt, FrozenSystemPrompt, LegacyArtifactV5Package2Read, PublishedArtifactHead, PublishedArtifactHeadCore, Sha256Hex, } from "./artifact.js";
8
+ export type { ArtifactV5, ArtifactV5Core, BinaryIdentity, CanonicalJsonIdentity, CapturedRevision, FrozenPublishInput, FrozenDatabaseDelivery, FrozenDatabaseDeliveryIdentity, FrozenArtifactBundle, FrozenArtifactObject, FrozenArtifactObjectIdentity, FrozenArtifactObjectKind, FrozenViewPublicationCapture, FrozenViewPublicationDevice, FrozenPublishResource, FrozenResourceIdentity, FrozenRuntimeIdentity, PublishedViewBuildReceipt, PublishedViewBuildHtmlReceipt, FrozenSystemPrompt, LegacyArtifactV5Package2Read, PublishedArtifactHead, PublishedArtifactHeadCore, Sha256Hex, } from "./artifact.js";
7
9
  export type { ArtifactObjectReadbackReceipt, FinalizeApprovedArtifactInput, FinalizeApprovedArtifactResult, PublishOperationKey, PublishAttemptState, PublishAttemptCoordinator, PublishedArtifactObjectReader, ResolvePublishedArtifactInput, ResolvedPublishedArtifact, SubmitFrozenArtifactInput, SubmittedPublishAttempt, WriteOnceArtifactObject, WriteOnceArtifactStore, WriteOncePutResult, } from "./publish-attempt.js";
8
10
  export { buildFrozenArtifactObjectKey, publishOperationKey, } from "./publish-attempt.js";
9
11
  export * from "./neutral-artifact-assembler.js";
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  export { appendJsonPointer, canonicalJson, compareUtf8, escapeJsonPointerToken, parseJsonPointer, sha256Hex, toJsonValue, unescapeJsonPointerToken, utf8ByteLength, } from "./canonical.js";
2
2
  export { buildScenarioReviewSnapshot, DEFAULT_SCENARIO_REVIEW_SNAPSHOT_LIMITS, reconstructRecordPayload, stableRelationId, } from "./snapshot.js";
3
- export { ARTIFACT_MANIFEST_VERSION_V5, ARTIFACT_PACKAGE_FORMAT_VERSION_V5, LEGACY_ARTIFACT_PACKAGE_FORMAT_VERSION_V5, EARLIER_LEGACY_ARTIFACT_PACKAGE_FORMAT_VERSION_V5, CAPTURED_REVISION_SCHEMA_VERSION, FROZEN_PUBLISH_INPUT_SCHEMA_VERSION, FROZEN_DATABASE_DELIVERY_CODEC, FROZEN_DATABASE_DELIVERY_MEDIA_TYPE, FROZEN_DATABASE_DELIVERY_MAGIC, FROZEN_DATABASE_DELIVERY_RELATIVE_PATH, PUBLISHED_ARTIFACT_HEAD_SCHEMA_VERSION, } from "./artifact.js";
4
- export { artifactV5ManifestSha256, buildArtifactV5, buildFrozenArtifactBundle, buildPublishedArtifactHead, canonicalArtifactV5Bytes, canonicalBytes, canonicalPublishedArtifactHeadCoreBytes, canonicalSha256, digestPublishedArtifactHeadCore, parseArtifactV5, parseFrozenArtifactBundle, parseLegacyArtifactV5Package2, parsePublishedArtifactHead, } from "./artifact-digest.js";
3
+ export { ARTIFACT_MANIFEST_VERSION_V5, ARTIFACT_COMPILER_WITNESS_OBJECTS, ARTIFACT_PACKAGE_FORMAT_VERSION_V5, LEGACY_ARTIFACT_PACKAGE_FORMAT_VERSION_V5, EARLIER_LEGACY_ARTIFACT_PACKAGE_FORMAT_VERSION_V5, CAPTURED_REVISION_SCHEMA_VERSION, FROZEN_PUBLISH_INPUT_SCHEMA_VERSION, FROZEN_DATABASE_DELIVERY_CODEC, FROZEN_DATABASE_DELIVERY_MEDIA_TYPE, FROZEN_DATABASE_DELIVERY_MAGIC, FROZEN_DATABASE_DELIVERY_RELATIVE_PATH, PUBLISHED_ARTIFACT_HEAD_SCHEMA_VERSION, } from "./artifact.js";
4
+ export { artifactV5ManifestSha256, buildArtifactV5, buildFrozenArtifactBundle, buildPublishedArtifactHead, canonicalArtifactV5Bytes, canonicalBytes, canonicalPublishedArtifactHeadCoreBytes, canonicalSha256, digestPublishedArtifactHeadCore, parseArtifactV5, parseFrozenArtifactBundle, parseFrozenViewPublications, parseLegacyArtifactV5Package2, parsePublishedArtifactHead, } from "./artifact-digest.js";
5
+ export { verifyArtifactCompilerWitness } from "./artifact-compiler-witness.js";
5
6
  export { buildFrozenArtifactObjectKey, publishOperationKey, } from "./publish-attempt.js";
6
7
  export * from "./neutral-artifact-assembler.js";
7
8
  export * from "./bound-view-freeze.js";
@@ -93,6 +93,13 @@ export interface NeutralArtifactRuntimeDefinitions {
93
93
  readonly achievement_defs: readonly JsonValue[];
94
94
  readonly transaction_defs: readonly JsonValue[];
95
95
  }
96
+ /** 发布时由 compiler 从 prompt_retrieval.retrieval.play_config 固化的生成合同。 */
97
+ export interface FrozenGenerationPolicyV1 {
98
+ readonly schema_version: 1;
99
+ readonly model: string;
100
+ readonly temperature: number;
101
+ readonly policy_sha256: string;
102
+ }
96
103
  export interface NeutralArtifactSourceModel<TViewProject = unknown> {
97
104
  readonly metadata: JsonValue;
98
105
  readonly protagonist: JsonValue;
@@ -111,6 +118,7 @@ export interface NeutralArtifactSourceModel<TViewProject = unknown> {
111
118
  readonly view_interaction_pc: ViewInteractionSourceV3 | null;
112
119
  readonly music_candidate_source: MusicCandidateSourceV3;
113
120
  readonly retrieval: JsonValue;
121
+ readonly generation_policy: FrozenGenerationPolicyV1 | null;
114
122
  readonly runtime_definitions: NeutralArtifactRuntimeDefinitions;
115
123
  readonly source_metadata: Readonly<Record<string, JsonValue>>;
116
124
  readonly resources: readonly FrozenPublishResource[];
@@ -1,13 +1,14 @@
1
1
  import { canonicalScenarioSourceManifestDigestInputBytes, canonicalScenarioSourceSectionBytes, } 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, canonicalTmwBytes, digestViewInteractionManifestV3, materializeTmw3TriggerDocument, initialTmwSceneFromMetadata, parseMusicCandidateSourceV3, parseTmwBlueprintProgramV1, parseViewInteractionManifestV3, parseViewInteractionSourceV3, runtimeContractHashV3, } from "@world-engines/tmw";
2
+ import { NEUTRAL_SPATIAL_WORLD_STAGE_ID, canonicalCompileTargetProfileBytes, canonicalNeutralSourceCompilerReceiptBytes, compileTargetProfileDigest, canonicalTmwBytes, digestViewInteractionManifestV3, materializeTmw3TriggerDocument, initialTmwSceneFromMetadata, parseMusicCandidateSourceV3, parseTmwBlueprintProgramV1, parseViewInteractionManifestV3, parseViewInteractionSourceV3, runtimeContractHashV3, } from "@world-engines/tmw";
3
3
  import { decodeTrustedCompiledBlueprintArtifactV1 } from "@world-engines/tmw/compiled-blueprint-artifact";
4
+ import { TMW_RELATION_ID_ALIASES_METADATA_KEY, createTmwRelationIdAliasesMetadataValue, createTmwRelationIdAliasResolver } from "@world-engines/tmw/relation-id-aliases";
4
5
  import { canonicalLadybugRelationId, createViewAccessPolicySubject, sealViewAccessPolicy, VIEW_ACCESS_POLICY_DENY_ROOT, VIEW_ACCESS_POLICY_ROOT_SUBJECT_ID, } from "@world-engines/ladybug-bridge";
5
6
  import { validateRoadGeneration } from "@world-engines/spatial-authoring/road-generation";
6
7
  import { validateSpatialTemplateConfiguration } from "@world-engines/spatial-authoring/spatial-templates";
7
8
  import { validateGpsPosition, validateSpatialCoordinateSystem } from "@world-engines/spatial-authoring";
8
9
  import { canonicalJson, sha256Hex, toJsonValue } from "./canonical.js";
9
10
  import { ARTIFACT_COMPILER_WITNESS_PATHS, ARTIFACT_COMPILER_WITNESS_RESERVED_PREFIX, FROZEN_PUBLISH_INPUT_SCHEMA_VERSION, } from "./artifact.js";
10
- import { buildArtifactV5 } from "./artifact-digest.js";
11
+ import { buildArtifactV5, canonicalSha256 } from "./artifact-digest.js";
11
12
  export const NEUTRAL_ARTIFACT_EMBEDDING_INDEX_PATH = "__derived__/embedding-index-v1.json";
12
13
  export const FRESH_RUNTIME3_PLAYER_TURN_POLICY_SCHEMA_VERSION = 1;
13
14
  export const FROZEN_DATABASE_VIEW_ACCESS_POLICY_SCHEMA_VERSION = 3;
@@ -100,14 +101,37 @@ function embeddingSafeField(key) {
100
101
  const compact = key.toLocaleLowerCase("en-US").replaceAll(/[^a-z0-9]/gu, "");
101
102
  return !/(?:raw|original|archive|source|binary|blob).*(?:base64|bytes)|(?:base64|bytes).*(?:raw|original|archive|source|binary|blob)/u.test(compact);
102
103
  }
104
+ /**
105
+ * Review input historically used `kind`; the locked r14b source uses
106
+ * `relation_kind`. Source endpoints remain `from_id`/`to_id`; no caller may
107
+ * silently choose between conflicting kind fields.
108
+ */
109
+ function normalizeAuthorRelation(relation) {
110
+ const from = text(relation.from_id, "neutral_artifact_embedding_relation_from_invalid");
111
+ const to = text(relation.to_id, "neutral_artifact_embedding_relation_to_invalid");
112
+ const sourceKind = relation.kind;
113
+ const r14bKind = relation.relation_kind;
114
+ const kind = sourceKind !== undefined && r14bKind !== undefined
115
+ ? (() => {
116
+ const current = text(sourceKind, "neutral_artifact_embedding_relation_kind_invalid");
117
+ const blocked = text(r14bKind, "neutral_artifact_embedding_relation_kind_invalid");
118
+ if (current !== blocked)
119
+ throw new Error("neutral_artifact_embedding_relation_kind_conflict");
120
+ return current;
121
+ })()
122
+ : text(sourceKind ?? r14bKind, "neutral_artifact_embedding_relation_kind_invalid");
123
+ return Object.freeze({
124
+ from,
125
+ to,
126
+ kind,
127
+ });
128
+ }
103
129
  function relationSourceText(relation) {
104
130
  const props = record(relation.props ?? {}, "neutral_artifact_embedding_relation_props_invalid");
105
131
  const fields = relation.fields === undefined
106
132
  ? {}
107
133
  : record(relation.fields, "neutral_artifact_embedding_relation_fields_invalid");
108
- const from = text(relation.from_id, "neutral_artifact_embedding_relation_from_invalid");
109
- const to = text(relation.to_id, "neutral_artifact_embedding_relation_to_invalid");
110
- const kind = text(relation.kind, "neutral_artifact_embedding_relation_kind_invalid");
134
+ const { from, to, kind } = normalizeAuthorRelation(relation);
111
135
  const propText = Object.keys(props).filter(embeddingSafeField).sort().map((key) => {
112
136
  const value = props[key];
113
137
  const rendered = value === undefined ? "" : stableText(value);
@@ -143,11 +167,9 @@ function buildNeutralArtifactEmbeddingSourceTextIndex(graph) {
143
167
  const relation = record(value, "neutral_artifact_embedding_relation_invalid");
144
168
  if (relation.disabled === true)
145
169
  continue;
146
- const from = relation.from_id;
147
- const to = relation.to_id;
148
- if (typeof from === "string")
149
- append(from, relation);
150
- if (typeof to === "string" && to !== from)
170
+ const { from, to } = normalizeAuthorRelation(relation);
171
+ append(from, relation);
172
+ if (to !== from)
151
173
  append(to, relation);
152
174
  }
153
175
  const incidentByEntityId = new Map();
@@ -168,8 +190,9 @@ function deriveNeutralArtifactEntityEmbeddingSourceTextFromIndex(entityValue, in
168
190
  }).filter(Boolean).join(" ");
169
191
  const base = `names=${names(entity.name).join("|")} ${kind} ${entityId} ${baseProps}`.trim();
170
192
  const incident = (index.incidentByEntityId.get(entityId) ?? []).map((relation) => {
171
- const outgoing = relation.from_id === entityId;
172
- const counterpartId = text(outgoing ? relation.to_id : relation.from_id, "neutral_artifact_embedding_counterpart_invalid");
193
+ const normalized = normalizeAuthorRelation(relation);
194
+ const outgoing = normalized.from === entityId;
195
+ const counterpartId = outgoing ? normalized.to : normalized.from;
173
196
  const counterpart = index.entityById.get(counterpartId);
174
197
  return [
175
198
  `relation=${text(relation.relation_id, "neutral_artifact_embedding_relation_id_invalid")}`,
@@ -278,22 +301,36 @@ function base64Bytes(value, field) {
278
301
  throw new Error(`neutral_artifact_${field}_base64_noncanonical`);
279
302
  return Uint8Array.from(decoded, (character) => character.charCodeAt(0));
280
303
  }
281
- function derivedObjectMap(objects) {
282
- const byStage = new Map(objects.map((object) => [object.stage_id, object]));
283
- const spatial = byStage.get(NEUTRAL_SPATIAL_WORLD_STAGE_ID);
284
- const expectedSize = CURRENT_NEUTRAL_COMPILER_STAGE_VERSIONS.length + (spatial === undefined ? 0 : 1);
285
- if (byStage.size !== expectedSize) {
304
+ function derivedObjectMap(objects, profile) {
305
+ const expectedByStage = new Map();
306
+ for (const stage of profile.stage_versions) {
307
+ if (expectedByStage.has(stage.stage_id)) {
308
+ throw new Error(`neutral_artifact_target_profile_stage_duplicate:${stage.stage_id}`);
309
+ }
310
+ expectedByStage.set(stage.stage_id, stage.stage_version);
311
+ }
312
+ const byStage = new Map();
313
+ for (const object of objects) {
314
+ if (byStage.has(object.stage_id)) {
315
+ throw new Error(`neutral_artifact_derived_object_duplicate:${object.stage_id}`);
316
+ }
317
+ const expectedVersion = expectedByStage.get(object.stage_id);
318
+ if (expectedVersion === undefined) {
319
+ throw new Error(`neutral_artifact_derived_object_unexpected:${object.stage_id}`);
320
+ }
321
+ if (object.stage_version !== expectedVersion) {
322
+ throw new Error(`neutral_artifact_derived_object_version_mismatch:${object.stage_id}`);
323
+ }
324
+ byStage.set(object.stage_id, object);
325
+ }
326
+ if (objects.length !== profile.stage_versions.length) {
286
327
  throw new Error("neutral_artifact_derived_object_set_mismatch");
287
328
  }
288
- for (const stage of CURRENT_NEUTRAL_COMPILER_STAGE_VERSIONS) {
289
- const object = byStage.get(stage.stage_id);
290
- if (object === undefined || object.stage_version !== stage.stage_version) {
329
+ for (const stage of profile.stage_versions) {
330
+ if (!byStage.has(stage.stage_id)) {
291
331
  throw new Error(`neutral_artifact_derived_object_missing:${stage.stage_id}`);
292
332
  }
293
333
  }
294
- if (spatial !== undefined && spatial.stage_version !== NEUTRAL_SPATIAL_WORLD_STAGE_VERSION) {
295
- throw new Error(`neutral_artifact_derived_object_missing:${NEUTRAL_SPATIAL_WORLD_STAGE_ID}`);
296
- }
297
334
  return byStage;
298
335
  }
299
336
  function requiredDerivedJson(byStage, stageId) {
@@ -307,6 +344,14 @@ function optionalSpatialBase(byStage) {
307
344
  if (object === undefined)
308
345
  return null;
309
346
  const root = record(parseCanonicalJson(object.bytes, "neutral_spatial_world"), "neutral_artifact_spatial_world_object_required");
347
+ if (root.spatial_world_present !== undefined) {
348
+ exactKeys(root, ["spatial_world_present"], "spatial_world_absent");
349
+ if (object.stage_version !== "2"
350
+ || root.spatial_world_present !== false) {
351
+ throw new Error("neutral_artifact_spatial_world_absence_invalid");
352
+ }
353
+ return null;
354
+ }
310
355
  if (root.player_programmatic_supported === false) {
311
356
  exactKeys(root, ["player_programmatic_supported", "reason", ...(root.playerMap === undefined ? [] : ["playerMap"]), ...(root.spatialAuthority === undefined ? [] : ["spatialAuthority"])], "spatial_world_unsupported");
312
357
  if (root.reason !== "topology_missing"
@@ -327,6 +372,7 @@ function optionalSpatialBase(byStage) {
327
372
  ...(root.templates === undefined ? [] : ["templates"]),
328
373
  ...(root.controlledActorId === undefined ? [] : ["controlledActorId"]),
329
374
  ...(root.playerMap === undefined ? [] : ["playerMap"]),
375
+ ...(root.spatialAuthority === undefined ? [] : ["spatialAuthority"]),
330
376
  ], "spatial_world");
331
377
  const baseDigest = text(root.baseDigest, "neutral_artifact_spatial_world_base_digest_invalid");
332
378
  if (!SHA256.test(baseDigest))
@@ -657,6 +703,27 @@ function parseLayoutModel(value) {
657
703
  }
658
704
  return Object.freeze({ view_interaction_mobile: mobile, view_interaction_pc: pc });
659
705
  }
706
+ function parseGenerationPolicy(playConfig) {
707
+ if (playConfig === null)
708
+ return null;
709
+ const root = record(playConfig, "neutral_artifact_play_config_object_required");
710
+ const raw = root.generation_policy;
711
+ if (raw === undefined || raw === null)
712
+ return null;
713
+ const policy = record(raw, "neutral_artifact_generation_policy_object_required");
714
+ exactKeys(policy, ["schema_version", "model", "temperature"], "generation_policy");
715
+ const model = text(policy.model, "neutral_artifact_generation_policy_model_invalid");
716
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/u.test(model)) {
717
+ throw new Error("neutral_artifact_generation_policy_model_invalid");
718
+ }
719
+ const temperature = policy.temperature;
720
+ if (typeof temperature !== "number" || !Number.isFinite(temperature)
721
+ || temperature < 0 || temperature > 2) {
722
+ throw new Error("neutral_artifact_generation_policy_temperature_invalid");
723
+ }
724
+ const core = toJsonValue({ schema_version: 1, model, temperature });
725
+ return Object.freeze({ schema_version: 1, model, temperature, policy_sha256: canonicalSha256(core) });
726
+ }
660
727
  function parsePromptRetrievalModel(value) {
661
728
  const root = record(value, "neutral_artifact_prompt_retrieval_object_required");
662
729
  exactKeys(root, ["retrieval", "music_candidates", "runtime_definitions"], "prompt_retrieval");
@@ -673,6 +740,7 @@ function parsePromptRetrievalModel(value) {
673
740
  throw new Error("neutral_artifact_music_candidate_source_invalid");
674
741
  return Object.freeze({
675
742
  retrieval: retrieval.retrieval_config ?? null,
743
+ generation_policy: parseGenerationPolicy(retrieval.play_config ?? null),
676
744
  music_candidate_source: music,
677
745
  runtime_definitions: Object.freeze({
678
746
  triggers: array(runtime.triggers, "runtime_triggers"),
@@ -704,7 +772,7 @@ function parseResourcesModel(value) {
704
772
  * gzip-tar 解包是唯一外部 seam;调用方不能重写 compiler derived-object 解释规则。
705
773
  */
706
774
  export async function materializeNeutralArtifactCompilation(source, profile, outcome, unpackViewProject) {
707
- const byStage = derivedObjectMap(outcome.derived_objects);
775
+ const byStage = derivedObjectMap(outcome.derived_objects, profile);
708
776
  const metadata = parseMetadataModel(requiredDerivedJson(byStage, "neutral.metadata"));
709
777
  const graph = parseGraphModel(requiredDerivedJson(byStage, "neutral.logical-graph"));
710
778
  const layout = parseLayoutModel(requiredDerivedJson(byStage, "neutral.layout"));
@@ -934,7 +1002,8 @@ function snapshotEntities(values) {
934
1002
  if (ids.has(id))
935
1003
  throw new Error(`neutral_artifact_entity_id_duplicate:${id}`);
936
1004
  ids.add(id);
937
- const rawNames = Array.isArray(entity.name) ? entity.name : [];
1005
+ // 中性作者源允许单名称字符串;仅在数据库投影时转成名称数组,原源不变。
1006
+ const rawNames = typeof entity.name === "string" ? [entity.name] : Array.isArray(entity.name) ? entity.name : [];
938
1007
  if (rawNames.length === 0 || rawNames.some((name) => typeof name !== "string"
939
1008
  || name.length === 0 || name !== name.trim().normalize("NFC"))) {
940
1009
  throw new Error(`neutral_artifact_entity_name_invalid:${id}`);
@@ -954,17 +1023,22 @@ function snapshotEntities(values) {
954
1023
  }
955
1024
  function snapshotRelations(values) {
956
1025
  const ids = new Set();
1026
+ const sourceIds = new Set();
957
1027
  return Object.freeze(values.map((value, index) => {
958
1028
  const relation = record(value, `neutral_artifact_relation_${index}_invalid`);
959
1029
  if (relation.disabled === true)
960
1030
  return null;
961
- const src = text(relation.from_id, `neutral_artifact_relation_from_${index}_invalid`);
962
- const dst = text(relation.to_id, `neutral_artifact_relation_to_${index}_invalid`);
963
- const kind = text(relation.kind, `neutral_artifact_relation_kind_${index}_invalid`);
1031
+ const normalized = normalizeAuthorRelation(relation);
1032
+ const src = normalized.from;
1033
+ const dst = normalized.to;
1034
+ const kind = normalized.kind;
1035
+ const sourceId = text(relation.relation_id, `neutral_artifact_relation_id_invalid:${index}`);
1036
+ // 作者 ID 属于中性源;数据库关系按两端与类型生成 identity,不能要求作者预先使用物理 ID。
964
1037
  const relationId = canonicalLadybugRelationId(src, dst, kind);
965
- if (relation.relation_id !== relationId || ids.has(relationId)) {
1038
+ if (sourceIds.has(sourceId) || ids.has(relationId)) {
966
1039
  throw new Error(`neutral_artifact_relation_id_invalid:${index}`);
967
1040
  }
1041
+ sourceIds.add(sourceId);
968
1042
  ids.add(relationId);
969
1043
  const mode = viewAccess(relation.view_access, `relation_view_access_${relationId}`);
970
1044
  return Object.freeze({
@@ -1018,9 +1092,10 @@ function buildViewAccessPolicy(graphEntities, entities, graphRelations, relation
1018
1092
  const input = record(value, "neutral_artifact_policy_relation_invalid");
1019
1093
  if (input.disabled === true)
1020
1094
  continue;
1021
- const src = text(input.from_id, "neutral_artifact_policy_relation_from_invalid");
1022
- const dst = text(input.to_id, "neutral_artifact_policy_relation_to_invalid");
1023
- const kind = text(input.kind, "neutral_artifact_policy_relation_kind_invalid");
1095
+ const normalized = normalizeAuthorRelation(input);
1096
+ const src = normalized.from;
1097
+ const dst = normalized.to;
1098
+ const kind = normalized.kind;
1024
1099
  if (!entityById.has(src) || !entityById.has(dst))
1025
1100
  throw new Error("neutral_artifact_policy_relation_endpoint_missing");
1026
1101
  const id = canonicalLadybugRelationId(src, dst, kind);
@@ -1169,6 +1244,22 @@ export async function encodeNeutralReviewDatabaseSnapshot(input) {
1169
1244
  const graphEntities = deliveryGraphEntities(model.graph.entities);
1170
1245
  const entities = snapshotEntities(graphEntities);
1171
1246
  const relations = snapshotRelations(model.graph.relations);
1247
+ if (Object.hasOwn(model.source_metadata, TMW_RELATION_ID_ALIASES_METADATA_KEY)) {
1248
+ throw new Error("neutral_artifact_relation_alias_metadata_reserved");
1249
+ }
1250
+ const relationAliases = createTmwRelationIdAliasesMetadataValue(model.graph.relations.flatMap(value => {
1251
+ const relation = record(value, "neutral_artifact_relation_alias_source_invalid");
1252
+ if (relation.disabled === true)
1253
+ return [];
1254
+ const normalized = normalizeAuthorRelation(relation);
1255
+ const sourceId = text(relation.relation_id, "neutral_artifact_relation_alias_source_id_invalid");
1256
+ const canonicalId = canonicalLadybugRelationId(normalized.from, normalized.to, normalized.kind);
1257
+ return sourceId === canonicalId ? [] : [{ source_relation_id: sourceId, canonical_relation_id: canonicalId }];
1258
+ }));
1259
+ const sourceMetadata = relationAliases.length === 0 ? model.source_metadata : {
1260
+ ...model.source_metadata, [TMW_RELATION_ID_ALIASES_METADATA_KEY]: relationAliases,
1261
+ };
1262
+ createTmwRelationIdAliasResolver({ sourceMetadata, canonicalRelationIds: new Set(relations.map(relation => relation.relation_id)) });
1172
1263
  const entityIds = new Set(entities.map((entity) => entity.id));
1173
1264
  for (const relation of relations) {
1174
1265
  if (!entityIds.has(relation.src) || !entityIds.has(relation.dst)) {
@@ -1184,6 +1275,7 @@ export async function encodeNeutralReviewDatabaseSnapshot(input) {
1184
1275
  const sidecar = toJsonValue({
1185
1276
  initial_scene: initialTmwSceneFromMetadata(model.metadata == null ? {} : record(model.metadata, "neutral_artifact_metadata_object_required")),
1186
1277
  retrieval_config: model.retrieval,
1278
+ generation_policy: model.generation_policy,
1187
1279
  triggers: model.runtime_definitions.triggers,
1188
1280
  trigger_variables_schema: model.runtime_definitions.trigger_variables_schema,
1189
1281
  cg_defs: model.runtime_definitions.cg_defs,
@@ -1218,7 +1310,7 @@ export async function encodeNeutralReviewDatabaseSnapshot(input) {
1218
1310
  view_interaction_mobile: model.view_interaction_mobile,
1219
1311
  view_interaction_pc: model.view_interaction_pc,
1220
1312
  music_candidate_source: model.music_candidate_source,
1221
- source_metadata: model.source_metadata,
1313
+ source_metadata: sourceMetadata,
1222
1314
  },
1223
1315
  view_access_policy: buildDerivedViewAccessPolicyV3(graphEntities, entities, model.graph.relations, relations),
1224
1316
  });
@@ -1286,16 +1378,17 @@ export async function assembleNeutralFrozenPublishInput(input) {
1286
1378
  database_bytes: input.database_bytes.slice(),
1287
1379
  database_delivery: Object.freeze({ codec: "scnb-v2", bytes: input.database_delivery_bytes.slice() }),
1288
1380
  trigger_document: toJsonValue(compilation.model.trigger_document),
1289
- compiled_plan: compilation.model.trigger_semantics_kind === "blueprint-program"
1290
- ? compilation.model.compiled_plan
1291
- : attachFreshRuntime3PlayerTurnPolicy(compilation.model.compiled_plan, compilation.model.system_prompts, input.runtime_identity.runtime_contract_hash),
1381
+ compiled_plan: attachFreshRuntime3PlayerTurnPolicy(compilation.model.compiled_plan, compilation.model.system_prompts, input.runtime_identity.runtime_contract_hash),
1292
1382
  view_source_bytes: compilation.model.view_source_bytes.slice(),
1293
1383
  view_publish_mobile_bytes: input.view_publish_mobile_bytes.slice(),
1294
1384
  view_publish_pc_bytes: input.view_publish_pc_bytes.slice(),
1295
1385
  view_interaction_mobile: mobile,
1296
1386
  view_interaction_pc: pc,
1297
1387
  music_candidate_source: compilation.model.music_candidate_source,
1298
- retrieval: compilation.model.retrieval,
1388
+ retrieval: toJsonValue({
1389
+ retrieval_config: compilation.model.retrieval,
1390
+ generation_policy: compilation.model.generation_policy,
1391
+ }),
1299
1392
  resources: mergeNeutralArtifactResources(compilation.model.resources, compilation.witness_resources, [embedding.index_resource]),
1300
1393
  runtime_identity: Object.freeze({ ...input.runtime_identity }),
1301
1394
  ...(input.published_view === undefined ? {} : { published_view: input.published_view }),
@@ -7,7 +7,7 @@ import { materializeNeutralArtifactCompilation, } from "./neutral-artifact-assem
7
7
  * 只能运行在 sandbox Node build worker,不能进入 Cloudflare Worker isolate。
8
8
  */
9
9
  export function createSandboxNodeBoundViewCompiler(options) {
10
- const stages = buildCurrentTmw3NeutralCompilerStageRegistry();
10
+ const stages = buildCurrentTmw3NeutralCompilerStageRegistry(options.target_profile);
11
11
  const cache = new InMemoryCompilerStageCache();
12
12
  const codec = Object.freeze({
13
13
  decode_svp(bytes) {
@@ -36,6 +36,15 @@ export declare function buildFrozenArtifactLegacySubmitWire(frozen: FrozenPublis
36
36
  sha256: string;
37
37
  bytes_base64: string;
38
38
  }>[];
39
+ view_publications: readonly Readonly<{
40
+ device: import("./artifact.js").FrozenViewPublicationDevice;
41
+ relative_path: "view-publish-mobile.html" | "view-publish-pc.html";
42
+ media_type: "text/html; charset=utf-8";
43
+ encoding: "bytes";
44
+ size_bytes: number;
45
+ sha256: string;
46
+ bytes_base64: string;
47
+ }>[];
39
48
  resources: readonly Readonly<{
40
49
  path: string;
41
50
  media_type: string;
@@ -38,6 +38,15 @@ export function buildFrozenArtifactLegacySubmitWire(frozen, operation_key, revie
38
38
  encoding: object.encoding, size_bytes: object.size_bytes, sha256: object.sha256,
39
39
  bytes_base64: base64(object.bytes),
40
40
  }))),
41
+ view_publications: Object.freeze(artifactBundle.view_publications.map((publication) => Object.freeze({
42
+ device: publication.device,
43
+ relative_path: publication.relative_path,
44
+ media_type: publication.media_type,
45
+ encoding: publication.encoding,
46
+ size_bytes: publication.size_bytes,
47
+ sha256: publication.sha256,
48
+ bytes_base64: base64(publication.bytes),
49
+ }))),
41
50
  resources: Object.freeze(frozen.resources.filter((resource) => !Object.values(ARTIFACT_COMPILER_WITNESS_OBJECTS)
42
51
  .some((witness) => witness.resource_path === resource.path)).map((resource) => Object.freeze({
43
52
  path: resource.path, media_type: resource.media_type, encoding: "bytes",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@world-engines/scenario-review-snapshot",
3
3
  "private": false,
4
- "version": "0.1.4",
4
+ "version": "1.0.0",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
7
7
  "exports": {
@@ -26,24 +26,24 @@
26
26
  "dist"
27
27
  ],
28
28
  "dependencies": {
29
- "@world-engines/chatplay-vite-plugin": "0.1.4",
30
- "@world-engines/ladybug-bridge": "0.1.4",
31
- "@world-engines/protocol-ts": "0.1.4",
32
- "@world-engines/scenario-author-source": "0.1.4",
33
- "@world-engines/tmw": "0.1.4",
34
- "@world-engines/spatial-authoring": "0.1.4"
29
+ "@world-engines/chatplay-vite-plugin": "1.0.0",
30
+ "@world-engines/ladybug-bridge": "1.0.0",
31
+ "@world-engines/protocol-ts": "1.0.0",
32
+ "@world-engines/scenario-author-source": "1.0.0",
33
+ "@world-engines/tmw": "1.0.0",
34
+ "@world-engines/spatial-authoring": "1.0.0"
35
35
  },
36
36
  "publishConfig": {
37
37
  "access": "public",
38
38
  "registry": "https://registry.npmjs.org"
39
39
  },
40
- "worldengine_source_sha256": "89298a941a8b4da31f62d81274baef2e65f6f24fefed09c12564c3af300f2e2b",
40
+ "worldengine_source_sha256": "1d697deb5b085043a98d155e563e16c1f034945cb40f0786b00d274c967e1ffc",
41
41
  "worldengine_source_identity_provenance": {
42
42
  "schema": "worldengine-public-namespace-projection/v1",
43
43
  "kind": "composite-source-identity; namespace projection; not-full-source-recompile",
44
44
  "original_name": "@chat/scenario-review-snapshot",
45
- "original_archive_sha256": "1f56d330fd1f64c6a8aa57578a76c690c734c58f80e23be47ad0d8c891982101",
46
- "original_source_sha256": "6618e6cea8bdba745bca0e9be2771796030d11f670a6f49b5848ebe1585c36bd",
45
+ "original_archive_sha256": "152fbcbd8a9f7ecb458600a333708db8b42fed71f8238d3ad6b8ad3b1b19613b",
46
+ "original_source_sha256": "6742170897a854c1c859d642e5ac3cc77c874ec13263928abead5c30572b01ec",
47
47
  "projected_name": "@world-engines/scenario-review-snapshot",
48
48
  "package_name_mapping": {
49
49
  "@chat/blocks-editor": "@world-engines/blocks-editor",
@@ -55,7 +55,7 @@
55
55
  "@chat/scenario-review-snapshot": "@world-engines/scenario-review-snapshot",
56
56
  "@chat/tmw": "@world-engines/tmw",
57
57
  "@chat/view-exposure": "@world-engines/view-exposure",
58
- "@world-engines/chatplay-vite-plugin": "0.1.4",
58
+ "@world-engines/chatplay-vite-plugin": "@world-engines/chatplay-vite-plugin",
59
59
  "@worldengine/agent-kit": "@world-engines/agent-kit",
60
60
  "@worldengine/authoring-bridge": "@world-engines/authoring-bridge",
61
61
  "@worldengine/authoring-ui": "@world-engines/authoring-ui",