@neocompose/cli 0.6.5 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ### Changed
6
+
7
+ - Document the final relation-only world layer-link contract and keep the
8
+ format-4 Hello World corpus free of the retired value-side binding field.
9
+
10
+ ## [0.6.7] - 2026-07-21
11
+
12
+ ### Fixed
13
+
14
+ - Treat class-level layer-link relations, including inherited targets, as the
15
+ authoritative binding during pull, status, diff, and push.
16
+ - Reject concrete system layer-link bases, targetless concrete descendants,
17
+ ambiguous or invalid targets, abstract construction, and authored
18
+ `layerClassId` sidecars before a source transaction reaches the server.
19
+
20
+ ## [0.6.6] - 2026-07-21
21
+
22
+ ### Fixed
23
+
24
+ - Include relation-kind metadata when pushing internal record relations so
25
+ `@relations` changes pass server transaction admission.
26
+ - Report the actual HTTP response status when a push is rejected.
27
+
3
28
  ## [0.6.5] - 2026-07-21
4
29
 
5
30
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -13981,6 +13981,7 @@ function validateProjectSourceSemantics(documents) {
13981
13981
  );
13982
13982
  }
13983
13983
  }
13984
+ validateWorldLayerLinkSourceContracts(documents, environment, diagnostics);
13984
13985
  return diagnostics;
13985
13986
  }
13986
13987
  function contextualAnnotationFields(document, position) {
@@ -14500,6 +14501,188 @@ function relationFields(context, environment) {
14500
14501
  }
14501
14502
  return [];
14502
14503
  }
14504
+ function validateWorldLayerLinkSourceContracts(documents, environment, diagnostics) {
14505
+ for (const [uri, document] of documents) {
14506
+ for (const declaration of document.declarations) {
14507
+ if (declaration.kind !== "class") continue;
14508
+ const descriptor = worldLayerLinkSourceDescriptor(
14509
+ declaration,
14510
+ environment
14511
+ );
14512
+ if (descriptor === null) continue;
14513
+ const directTargets = directLayerLinkTargets(declaration, uri);
14514
+ const systemWorldKind = contextualAnnotationEnum(
14515
+ declaration.annotations,
14516
+ "system",
14517
+ "worldKind"
14518
+ );
14519
+ const isSystemBase = systemWorldKind === descriptor.linkWorldKind;
14520
+ if (isSystemBase) {
14521
+ if (!declaration.modifiers.includes("abstract")) {
14522
+ pushDiagnostic(
14523
+ diagnostics,
14524
+ uri,
14525
+ declaration.nameRange,
14526
+ "concrete-system-layer-link-base",
14527
+ `System layer-link base '${declaration.name}' must be abstract.`
14528
+ );
14529
+ }
14530
+ if (directTargets.length > 0) {
14531
+ pushDiagnostic(
14532
+ diagnostics,
14533
+ uri,
14534
+ directTargets[0].range,
14535
+ "system-layer-link-target-relation",
14536
+ `System layer-link base '${declaration.name}' cannot declare targetLayer. Declare the relation on a project-authored descendant instead.`
14537
+ );
14538
+ }
14539
+ }
14540
+ for (const target of directTargets) {
14541
+ validateLayerLinkTargetSource(
14542
+ declaration,
14543
+ descriptor,
14544
+ target,
14545
+ environment,
14546
+ diagnostics
14547
+ );
14548
+ }
14549
+ if (isSystemBase || declaration.modifiers.includes("abstract")) continue;
14550
+ const effectiveTargets = effectiveLayerLinkTargets(
14551
+ declaration,
14552
+ uri,
14553
+ environment
14554
+ );
14555
+ if (effectiveTargets.length === 0) {
14556
+ pushDiagnostic(
14557
+ diagnostics,
14558
+ uri,
14559
+ declaration.nameRange,
14560
+ "concrete-layer-link-missing-target",
14561
+ `Concrete layer-link class '${declaration.name}' must resolve exactly one inherited or directly declared targetLayer relation.`
14562
+ );
14563
+ } else if (effectiveTargets.length > 1) {
14564
+ pushDiagnostic(
14565
+ diagnostics,
14566
+ uri,
14567
+ declaration.nameRange,
14568
+ "concrete-layer-link-multiple-targets",
14569
+ `Concrete layer-link class '${declaration.name}' resolves ${effectiveTargets.length} targetLayer relations at the nearest declaration depth; exactly one is required.`
14570
+ );
14571
+ }
14572
+ }
14573
+ }
14574
+ }
14575
+ function worldLayerLinkSourceDescriptor(declaration, environment) {
14576
+ const worldKind = effectiveWorldKind(declaration, environment);
14577
+ if (worldKind === "TileLayerLink") {
14578
+ return {
14579
+ linkWorldKind: "TileLayerLink",
14580
+ targetWorldKind: "TileLayer"
14581
+ };
14582
+ }
14583
+ if (worldKind === "ObjectLayerLink") {
14584
+ return {
14585
+ linkWorldKind: "ObjectLayerLink",
14586
+ targetWorldKind: "ObjectLayer"
14587
+ };
14588
+ }
14589
+ return null;
14590
+ }
14591
+ function directLayerLinkTargets(declaration, uri) {
14592
+ const result = [];
14593
+ for (const annotation2 of declaration.annotations) {
14594
+ if (annotation2.name !== "relations") continue;
14595
+ for (const argument2 of annotation2.arguments) {
14596
+ if (argument2.name !== "targetLayer") continue;
14597
+ let expression;
14598
+ try {
14599
+ expression = parseExpression(argument2.text);
14600
+ } catch (error) {
14601
+ if (error instanceof CompileError) continue;
14602
+ throw error;
14603
+ }
14604
+ const entries = expression.kind === "litList" ? expression.elements : [expression];
14605
+ for (const entry of entries) {
14606
+ result.push({
14607
+ expression: unwrapAnnotatedExpression(entry),
14608
+ range: argument2.range,
14609
+ uri
14610
+ });
14611
+ }
14612
+ }
14613
+ }
14614
+ return result;
14615
+ }
14616
+ function effectiveLayerLinkTargets(declaration, uri, environment) {
14617
+ const visited = /* @__PURE__ */ new Set();
14618
+ let current = declaration;
14619
+ let currentUri = uri;
14620
+ while (current !== void 0 && !visited.has(current.name)) {
14621
+ visited.add(current.name);
14622
+ const systemWorldKind = contextualAnnotationEnum(
14623
+ current.annotations,
14624
+ "system",
14625
+ "worldKind"
14626
+ );
14627
+ if (systemWorldKind === "TileLayerLink" || systemWorldKind === "ObjectLayerLink") {
14628
+ return [];
14629
+ }
14630
+ const direct = directLayerLinkTargets(current, currentUri);
14631
+ if (direct.length > 0) return direct;
14632
+ const parent = current.baseTypes.map((base) => environment.types.get(base.name)).find((candidate) => candidate !== void 0);
14633
+ current = parent;
14634
+ currentUri = uri;
14635
+ }
14636
+ return [];
14637
+ }
14638
+ function validateLayerLinkTargetSource(declaration, descriptor, targetSource, environment, diagnostics) {
14639
+ const expression = targetSource.expression;
14640
+ if (isReferenceCall(expression)) return;
14641
+ if (expression.kind !== "ident") {
14642
+ pushDiagnostic(
14643
+ diagnostics,
14644
+ targetSource.uri,
14645
+ targetSource.range,
14646
+ "invalid-layer-link-target",
14647
+ `Layer-link class '${declaration.name}' targetLayer must be a layer class symbol or typed Reference.`
14648
+ );
14649
+ return;
14650
+ }
14651
+ const target = environment.types.get(expression.name);
14652
+ if (target === void 0) {
14653
+ pushDiagnostic(
14654
+ diagnostics,
14655
+ targetSource.uri,
14656
+ targetSource.range,
14657
+ "unknown-layer-link-target",
14658
+ `Layer-link class '${declaration.name}' targets unknown class '${expression.name}'.`
14659
+ );
14660
+ return;
14661
+ }
14662
+ const targetWorldKind = effectiveWorldKind(target, environment);
14663
+ if (targetWorldKind !== descriptor.targetWorldKind) {
14664
+ pushDiagnostic(
14665
+ diagnostics,
14666
+ targetSource.uri,
14667
+ targetSource.range,
14668
+ "wrong-layer-link-target-kind",
14669
+ `Layer-link class '${declaration.name}' targetLayer '${target.name}' must extend world class kind '${descriptor.targetWorldKind}'.`
14670
+ );
14671
+ return;
14672
+ }
14673
+ if (target.modifiers.includes("abstract")) {
14674
+ pushDiagnostic(
14675
+ diagnostics,
14676
+ targetSource.uri,
14677
+ targetSource.range,
14678
+ "abstract-layer-link-target",
14679
+ `Layer-link class '${declaration.name}' targetLayer '${target.name}' must be concrete.`
14680
+ );
14681
+ }
14682
+ }
14683
+ function unwrapAnnotatedExpression(expression) {
14684
+ return expression.kind === "annotated" ? unwrapAnnotatedExpression(expression.expression) : expression;
14685
+ }
14503
14686
  function effectiveWorldKind(declaration, environment) {
14504
14687
  const queue = [declaration];
14505
14688
  const seen = /* @__PURE__ */ new Set();
@@ -14829,7 +15012,26 @@ function validateExpression(expression, expected, scope, environment, uri, range
14829
15012
  if (expression.kind === "new") {
14830
15013
  const typeName = expression.className ?? expected?.name;
14831
15014
  const declaration = typeName ? environment.types.get(typeName) : void 0;
15015
+ for (const entry of expression.initializer ?? []) {
15016
+ if (entry.name !== "layerClassId") continue;
15017
+ pushDiagnostic(
15018
+ diagnostics,
15019
+ uri,
15020
+ range2,
15021
+ "reserved-layer-class-id-source",
15022
+ "layerClassId is an unsupported legacy field. Declare @relations(targetLayer: ...) on the concrete layer-link class instead."
15023
+ );
15024
+ }
14832
15025
  if (!declaration) return;
15026
+ if (declaration.modifiers.includes("abstract")) {
15027
+ pushDiagnostic(
15028
+ diagnostics,
15029
+ uri,
15030
+ range2,
15031
+ "abstract-class-instantiation",
15032
+ `Cannot instantiate abstract class '${declaration.name}'. Use a concrete descendant instead.`
15033
+ );
15034
+ }
14833
15035
  const fields = declaration?.members.filter(
14834
15036
  (member) => member.kind === "field" && !member.modifiers.includes("static")
14835
15037
  ) ?? [];
@@ -14847,8 +15049,10 @@ function validateExpression(expression, expected, scope, environment, uri, range
14847
15049
  );
14848
15050
  });
14849
15051
  for (const entry of expression.initializer ?? []) {
14850
- const field = declaration?.members.find(
14851
- (candidate) => candidate.name === entry.name
15052
+ const field = sourceClassFieldByName(
15053
+ declaration,
15054
+ entry.name,
15055
+ environment
14852
15056
  );
14853
15057
  validateExpression(
14854
15058
  entry.value,
@@ -14950,6 +15154,19 @@ function validateExpression(expression, expected, scope, environment, uri, range
14950
15154
  );
14951
15155
  }
14952
15156
  }
15157
+ function sourceClassFieldByName(declaration, name, environment) {
15158
+ const visited = /* @__PURE__ */ new Set();
15159
+ let current = declaration;
15160
+ while (current !== void 0 && !visited.has(current.name)) {
15161
+ visited.add(current.name);
15162
+ const member = current.members.find(
15163
+ (candidate) => candidate.kind === "field" && candidate.name === name
15164
+ );
15165
+ if (member !== void 0) return member;
15166
+ current = current.baseTypes.map((base) => environment.types.get(base.name)).find((candidate) => candidate !== void 0);
15167
+ }
15168
+ return void 0;
15169
+ }
14953
15170
  function semanticTypesAssignable(actual, expected) {
14954
15171
  if (actual.name === expected.name) return true;
14955
15172
  if (actual.name === "int" && (expected.name === "float" || expected.name === "decimal")) {
@@ -40331,6 +40548,11 @@ function lowerSeedRow(context, member, sourceExpression, source, valueId, path,
40331
40548
  if (effectiveClass === void 0) {
40332
40549
  throw new Error(`Unknown value class ${member.classId}.`);
40333
40550
  }
40551
+ if (effectiveClass.declarationModifier === "abstract") {
40552
+ throw new Error(
40553
+ `Cannot instantiate abstract class ${effectiveClass.name} for ${path}. Use a concrete descendant instead.`
40554
+ );
40555
+ }
40334
40556
  classId = effectiveClass.id;
40335
40557
  const body = {};
40336
40558
  for (const assignment of expression.initializer ?? []) {
@@ -40528,6 +40750,11 @@ function lowerClassValue(context, member, expression, base, source) {
40528
40750
  const schemaClass2 = context.classes.get(classId);
40529
40751
  if (schemaClass2 === void 0)
40530
40752
  throw new Error(`Unknown value class ${classId}.`);
40753
+ if (schemaClass2.declarationModifier === "abstract") {
40754
+ throw new Error(
40755
+ `Cannot instantiate abstract class ${schemaClass2.name} for value ${String(base.id)}. Use a concrete descendant instead.`
40756
+ );
40757
+ }
40531
40758
  const baseBody = isObjectRecord2(base.value) ? base.value : {};
40532
40759
  const body = { ...baseBody };
40533
40760
  for (const assignment of expression.initializer ?? []) {
@@ -41368,7 +41595,7 @@ function classValue(context, member, value, visited, targetTyped) {
41368
41595
  return targetTyped ? "new()" : `new ${name}()`;
41369
41596
  const schema = isObjectRecord2(schemaClass2.schema) ? schemaClass2.schema : {};
41370
41597
  const order = Array.isArray(schemaClass2.schemaKeyOrder) ? schemaClass2.schemaKeyOrder.filter(
41371
- (entry) => typeof entry === "string"
41598
+ (entry) => typeof entry === "string" && typeof schema[entry] === "string"
41372
41599
  ) : Object.keys(schema);
41373
41600
  const fields = [];
41374
41601
  for (const key of [
@@ -44914,7 +45141,7 @@ var init_project_migration_types = __esm({
44914
45141
  });
44915
45142
 
44916
45143
  // ../src/models/project/internal-record-relations.ts
44917
- function worldClassContract(relationKind, sourceWorldKind, targetWorldKind, targetClassPolicy, merge, allowAbstractTarget) {
45144
+ function worldClassContract(relationKind, sourceWorldKind, targetWorldKind, targetClassPolicy, merge, allowAbstractTarget, allowDirectSystemSource = true) {
44918
45145
  return {
44919
45146
  relationKind,
44920
45147
  endpointPairs: [{ sourceRecordKind: "class", targetRecordKind: "class" }],
@@ -44924,6 +45151,7 @@ function worldClassContract(relationKind, sourceWorldKind, targetWorldKind, targ
44924
45151
  allowCycles: false,
44925
45152
  sourceWorldKind,
44926
45153
  targetWorldKind,
45154
+ allowDirectSystemSource,
44927
45155
  allowAbstractTarget
44928
45156
  };
44929
45157
  }
@@ -45018,6 +45246,7 @@ var init_internal_record_relations = __esm({
45018
45246
  NeoWorldSystemClassKind.TileLayer,
45019
45247
  "exact",
45020
45248
  "nearest-single",
45249
+ false,
45021
45250
  false
45022
45251
  ),
45023
45252
  worldClassContract(
@@ -45026,6 +45255,7 @@ var init_internal_record_relations = __esm({
45026
45255
  NeoWorldSystemClassKind.ObjectLayer,
45027
45256
  "exact",
45028
45257
  "nearest-single",
45258
+ false,
45029
45259
  false
45030
45260
  ),
45031
45261
  {
@@ -49561,15 +49791,6 @@ var init_doctor = __esm({
49561
49791
  });
49562
49792
 
49563
49793
  // ../src/database/project-version-intents.ts
49564
- var project_version_intents_exports = {};
49565
- __export(project_version_intents_exports, {
49566
- PROJECT_VERSION_INTENT_SCHEMA_VERSION: () => PROJECT_VERSION_INTENT_SCHEMA_VERSION,
49567
- ProjectVersionIntentType: () => ProjectVersionIntentType,
49568
- assertProjectVersionIntentIsKnown: () => assertProjectVersionIntentIsKnown,
49569
- assertProjectVersionIntentMatchesDiff: () => assertProjectVersionIntentMatchesDiff,
49570
- createProjectVersionIntent: () => createProjectVersionIntent,
49571
- isKnownProjectVersionIntentType: () => isKnownProjectVersionIntentType
49572
- });
49573
49794
  function createProjectVersionIntent(type, details) {
49574
49795
  if (details === void 0) {
49575
49796
  return {
@@ -49588,24 +49809,6 @@ function isKnownProjectVersionIntentType(value) {
49588
49809
  value
49589
49810
  );
49590
49811
  }
49591
- function assertProjectVersionIntentIsKnown(intent) {
49592
- if (!isKnownProjectVersionIntentType(intent.type)) {
49593
- throw new Error(`Unknown project version intent type "${intent.type}".`);
49594
- }
49595
- if (intent.schemaVersion !== PROJECT_VERSION_INTENT_SCHEMA_VERSION) {
49596
- throw new Error(
49597
- `Project version intent "${intent.type}" uses schema version ${intent.schemaVersion}; expected ${PROJECT_VERSION_INTENT_SCHEMA_VERSION}.`
49598
- );
49599
- }
49600
- }
49601
- function assertProjectVersionIntentMatchesDiff(args) {
49602
- assertProjectVersionIntentIsKnown(args.intent);
49603
- if (args.diff.set === void 0 && args.diff.unset === void 0) {
49604
- throw new Error(
49605
- `Project version intent "${args.intent.type}" cannot be recorded with an empty diff.`
49606
- );
49607
- }
49608
- }
49609
49812
  var ProjectVersionIntentType, PROJECT_VERSION_INTENT_SCHEMA_VERSION;
49610
49813
  var init_project_version_intents = __esm({
49611
49814
  "../src/database/project-version-intents.ts"() {
@@ -62082,6 +62285,34 @@ var init_project_file_push = __esm({
62082
62285
  }
62083
62286
  });
62084
62287
 
62288
+ // src/commands/push-change-intent.ts
62289
+ function createNeoCliPushIntent(change) {
62290
+ const intentType = `${change.recordKind}.${change.kind}`;
62291
+ if (!isKnownProjectVersionIntentType(intentType)) {
62292
+ throw new Error(`No project version intent exists for "${intentType}".`);
62293
+ }
62294
+ if (change.recordKind !== "internal-record-relation") {
62295
+ return createProjectVersionIntent(intentType, { source: "neo-cli" });
62296
+ }
62297
+ const relationData = change.kind === "delete" ? change.baseData : change.nextData;
62298
+ if (!isInternalRecordRelation(relationData)) {
62299
+ throw new Error(
62300
+ `Cannot push internal record relation "${change.recordId}": ${change.kind} change has invalid relation data.`
62301
+ );
62302
+ }
62303
+ return createProjectVersionIntent(intentType, {
62304
+ source: "neo-cli",
62305
+ relationKind: relationData.relationKind
62306
+ });
62307
+ }
62308
+ var init_push_change_intent = __esm({
62309
+ "src/commands/push-change-intent.ts"() {
62310
+ "use strict";
62311
+ init_project_version_intents();
62312
+ init_project2();
62313
+ }
62314
+ });
62315
+
62085
62316
  // src/commands/push.ts
62086
62317
  var push_exports = {};
62087
62318
  __export(push_exports, {
@@ -62736,22 +62967,13 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
62736
62967
  {
62737
62968
  operation: transactionOperation,
62738
62969
  changes: status.changes.map((change) => {
62739
- const intentType = `${change.recordKind}.${change.kind}`;
62740
- if (!isKnownProjectVersionIntentType2(intentType)) {
62741
- throw new Error(
62742
- `No project version intent exists for "${intentType}".`
62743
- );
62744
- }
62745
62970
  return {
62746
62971
  recordKind: change.recordKind,
62747
62972
  recordId: change.recordId,
62748
62973
  operation: change.kind,
62749
62974
  nextData: change.kind === "delete" ? void 0 : stripServerDerivedNeoScript(change.nextData),
62750
62975
  deleted: change.kind === "delete" ? true : void 0,
62751
- intent: createProjectVersionIntent2(
62752
- intentType,
62753
- { source: "neo-cli" }
62754
- ),
62976
+ intent: createNeoCliPushIntent(change),
62755
62977
  expectedBaseContentHash: change.kind === "create" ? null : change.casBaseHash ?? null
62756
62978
  };
62757
62979
  }),
@@ -62929,7 +63151,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
62929
63151
  } catch (retryError) {
62930
63152
  progress.stop();
62931
63153
  if (retryError instanceof NeoApiError) {
62932
- reportPushRejection(retryError.body);
63154
+ reportPushRejection(retryError.status, retryError.body);
62933
63155
  process.exitCode = 1;
62934
63156
  return;
62935
63157
  }
@@ -62942,7 +63164,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
62942
63164
  process.exitCode = 1;
62943
63165
  return;
62944
63166
  }
62945
- reportPushRejection(rejection);
63167
+ reportPushRejection(error.status, rejection);
62946
63168
  process.exitCode = 1;
62947
63169
  return;
62948
63170
  }
@@ -63064,7 +63286,7 @@ function stripServerDerivedNeoScript(value) {
63064
63286
  if (next.kind === 23) delete next.action;
63065
63287
  return next;
63066
63288
  }
63067
- function reportPushRejection(body) {
63289
+ function reportPushRejection(status, body) {
63068
63290
  if (isObjectRecord2(body) && body.error === "base-hash-conflict") {
63069
63291
  console.error(
63070
63292
  'Push rejected: the server has newer state for these records. Run "neo pull" to merge, then push again.'
@@ -63091,7 +63313,7 @@ function reportPushRejection(body) {
63091
63313
  );
63092
63314
  return;
63093
63315
  }
63094
- console.error(`Push rejected (409): ${JSON.stringify(body)}`);
63316
+ console.error(`Push rejected (${status}): ${JSON.stringify(body)}`);
63095
63317
  }
63096
63318
  function immediateTransactionId(result) {
63097
63319
  if (!isObjectRecord2(result)) return null;
@@ -64064,7 +64286,7 @@ function compileMigrationAction(schema, migrationData) {
64064
64286
  migrationContext: true
64065
64287
  });
64066
64288
  }
64067
- var compileNSAction2, compileNSFunction2, compileNSGetter2, compileNSSetter2, createProjectVersionIntent2, isKnownProjectVersionIntentType2, ProjectTransactionInterruptedError, ProjectTransactionFailedError, PROJECT_TRANSACTION_POLL_DELAYS_MS;
64289
+ var compileNSAction2, compileNSFunction2, compileNSGetter2, compileNSSetter2, ProjectTransactionInterruptedError, ProjectTransactionFailedError, PROJECT_TRANSACTION_POLL_DELAYS_MS;
64068
64290
  var init_push = __esm({
64069
64291
  "src/commands/push.ts"() {
64070
64292
  "use strict";
@@ -64074,7 +64296,6 @@ var init_push = __esm({
64074
64296
  init_document();
64075
64297
  init_http();
64076
64298
  init_ui();
64077
- init_project_version_intents();
64078
64299
  init_workspace();
64079
64300
  init_project_documents();
64080
64301
  init_workspace_status();
@@ -64084,8 +64305,8 @@ var init_push = __esm({
64084
64305
  init_project_file_push();
64085
64306
  init_project_manifest();
64086
64307
  init_merge();
64308
+ init_push_change_intent();
64087
64309
  ({ compileNSAction: compileNSAction2, compileNSFunction: compileNSFunction2, compileNSGetter: compileNSGetter2, compileNSSetter: compileNSSetter2 } = compiler_adapter_exports);
64088
- ({ createProjectVersionIntent: createProjectVersionIntent2, isKnownProjectVersionIntentType: isKnownProjectVersionIntentType2 } = project_version_intents_exports);
64089
64310
  ProjectTransactionInterruptedError = class extends Error {
64090
64311
  constructor(transactionId) {
64091
64312
  super(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -205,6 +205,14 @@ declarations. Structurally owned descriptors and concrete generic bindings
205
205
  derive their identities from stable owner roles; do not invent `@id`
206
206
  annotations for them.
207
207
 
208
+ For world layer links, `NeoTileLayerLink` and `NeoObjectLayerLink` are abstract,
209
+ relation-free system bases. Instantiate a concrete project-authored descendant
210
+ that resolves exactly one `targetLayer` relation, declared directly or inherited
211
+ from a project-authored ancestor. The class relation is the complete binding:
212
+ native source emits no value-level target metadata, and link values carry no
213
+ second persistence representation. Painting and other world-content writes
214
+ never create or repair link targets.
215
+
208
216
  ## Root and authored values
209
217
 
210
218
  `Root.neo` contains a compiler-owned envelope of this form: