@neocompose/cli 0.36.11 → 0.36.13

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,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.36.13] - 2026-08-22
4
+
5
+ ### Fixed
6
+
7
+ - Refresh short-lived Convex JWTs during long-running commands. History plan
8
+ polling and other multi-request operations now keep using the existing CLI
9
+ device session instead of failing when the initial 15-minute JWT expires.
10
+
11
+ ## [0.36.12] - 2026-08-22
12
+
13
+ ### Fixed
14
+
15
+ - Let project owners and admins plan, apply, inspect, and resume history
16
+ rewrites through the CLI device login. The editor profile now requests the
17
+ changelog write grant, the registered OAuth client allows it, and every
18
+ history rewrite endpoint declares its read or write scope. Existing CLI
19
+ sessions must run `neo logout` followed by `neo login` once after upgrading.
20
+
3
21
  ## [0.36.11] - 2026-08-21
4
22
 
5
23
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -572,6 +572,7 @@ var init_login = __esm({
572
572
  "project:version:create",
573
573
  "project:version:status:read",
574
574
  "project:version:changelog:read",
575
+ "project:version:changelog:write",
575
576
  "project:record:schema:read",
576
577
  "project:record:schema:write",
577
578
  "project:record:values:read",
@@ -64547,6 +64548,11 @@ var init_constructor_argument_ownership = __esm({
64547
64548
  init_compile_ns_property();
64548
64549
  init_value_row_owner_members();
64549
64550
  MaterializedValueGraphContext = class {
64551
+ /**
64552
+ * P79 §7a. Generic in the caller's document view so a pass typed against a
64553
+ * narrowed view does not get the whole document handed back on the first
64554
+ * line it reads `graph.document`.
64555
+ */
64550
64556
  document;
64551
64557
  valuesById;
64552
64558
  childrenByContainerId;
@@ -72157,9 +72163,9 @@ function constructionInitEvaluator(ctx, createdValues, argumentScopes, construct
72157
72163
  let currentId = memberId;
72158
72164
  while (currentId !== void 0 && !visited.has(currentId)) {
72159
72165
  visited.add(currentId);
72160
- const declaringClassIds = indexes.declaringClassIdsByMemberId.get(currentId);
72166
+ const declaringClassIds2 = indexes.declaringClassIdsByMemberId.get(currentId);
72161
72167
  const owner = inheritanceChain.find(
72162
- (schemaClass2) => declaringClassIds?.has(schemaClass2.id)
72168
+ (schemaClass2) => declaringClassIds2?.has(schemaClass2.id)
72163
72169
  );
72164
72170
  if (owner !== void 0) return owner.id;
72165
72171
  currentId = indexes.containerMemberIdByEntryId.get(currentId) ?? indexes.memberById.get(currentId)?.extendsMemberId;
@@ -82139,8 +82145,17 @@ function transactionImpactsAnyOwnerMember(scope, impactedIds) {
82139
82145
  function collectNeoScriptRecompileTargets(args) {
82140
82146
  if (args.forceRecompile === true) return completeTargets(args.postDocument);
82141
82147
  const explicit = explicitTargetIds(args.changes);
82142
- const { impactedIds, impactedTypeNames, constructedClassIds } = collectChangedContractIds(args);
82148
+ const {
82149
+ impactedIds,
82150
+ changedValueIds,
82151
+ impactedTypeNames,
82152
+ constructedClassIds
82153
+ } = collectChangedContractIds(args);
82143
82154
  for (const id2 of args.additionalImpactedIds ?? []) impactedIds.add(id2);
82155
+ for (const id2 of args.additionalChangedValueIds ?? []) {
82156
+ changedValueIds.add(id2);
82157
+ explicit.valueIds.add(id2);
82158
+ }
82144
82159
  includeDerivedConstructionContracts(
82145
82160
  constructedClassIds,
82146
82161
  args.postDocument.classes
@@ -82157,7 +82172,7 @@ function collectNeoScriptRecompileTargets(args) {
82157
82172
  dependentsById.set(dependencyId, dependents);
82158
82173
  }
82159
82174
  }
82160
- const queue = [...impactedIds];
82175
+ const queue = [...impactedIds, ...changedValueIds];
82161
82176
  for (let index = 0; index < queue.length; index += 1) {
82162
82177
  const dependencyId = queue[index];
82163
82178
  if (dependencyId === void 0) continue;
@@ -82166,22 +82181,24 @@ function collectNeoScriptRecompileTargets(args) {
82166
82181
  }
82167
82182
  }
82168
82183
  const constructorOwnerById = constructorOwners(args.postDocument);
82169
- const impactedSourceNames = collectImpactedSourceNames({
82170
- currentDocument: args.currentDocument,
82171
- postDocument: args.postDocument,
82184
+ const gradesSourceOnlyBodies = impactedIds.size > 0 || impactedTypeNames.size > 0;
82185
+ const impactScope = {
82172
82186
  impactedIds,
82187
+ changedValueIds,
82173
82188
  impactedTypeNames,
82174
- constructedClassIds
82175
- });
82176
- const memberIds = new Set(explicit.memberIds);
82177
- for (const member of args.postDocument.members) {
82178
- if (recordDependsOnChangedContract(
82179
- member,
82189
+ constructedClassIds,
82190
+ impactedSourceNames: gradesSourceOnlyBodies ? collectImpactedSourceNames({
82191
+ currentDocument: args.currentDocument,
82192
+ postDocument: args.postDocument,
82180
82193
  impactedIds,
82194
+ changedValueIds,
82181
82195
  impactedTypeNames,
82182
- constructedClassIds,
82183
- impactedSourceNames
82184
- )) {
82196
+ constructedClassIds
82197
+ }) : /* @__PURE__ */ new Set()
82198
+ };
82199
+ const memberIds = new Set(explicit.memberIds);
82200
+ for (const member of args.postDocument.members) {
82201
+ if (recordDependsOnChangedContract(member, impactScope)) {
82185
82202
  memberIds.add(member.id);
82186
82203
  }
82187
82204
  }
@@ -82189,10 +82206,7 @@ function collectNeoScriptRecompileTargets(args) {
82189
82206
  const valueIds = selectedRecordIds(
82190
82207
  postValues,
82191
82208
  explicit.valueIds,
82192
- impactedIds,
82193
- impactedTypeNames,
82194
- constructedClassIds,
82195
- impactedSourceNames
82209
+ impactScope
82196
82210
  );
82197
82211
  if (impactedIds.size > 0 && postValues.length > 0) {
82198
82212
  const scope = withVariantOwnershipRoots(
@@ -82219,35 +82233,23 @@ function collectNeoScriptRecompileTargets(args) {
82219
82233
  constructorIds: selectedRecordIds(
82220
82234
  args.postDocument.constructors ?? [],
82221
82235
  explicit.constructorIds,
82222
- impactedIds,
82223
- impactedTypeNames,
82224
- constructedClassIds,
82225
- impactedSourceNames,
82236
+ impactScope,
82226
82237
  (record3) => constructorOwnerById.get(record3.id) ?? null
82227
82238
  ),
82228
82239
  migrationIds: selectedRecordIds(
82229
82240
  args.postDocument.migrations ?? [],
82230
82241
  explicit.migrationIds,
82231
- impactedIds,
82232
- impactedTypeNames,
82233
- constructedClassIds,
82234
- impactedSourceNames
82242
+ impactScope
82235
82243
  ),
82236
82244
  dialogueNodeIds: selectedRecordIds(
82237
82245
  args.postDocument.dialogueNodes ?? [],
82238
82246
  explicit.dialogueNodeIds,
82239
- impactedIds,
82240
- impactedTypeNames,
82241
- constructedClassIds,
82242
- impactedSourceNames
82247
+ impactScope
82243
82248
  ),
82244
82249
  dialogueGroupIds: selectedRecordIds(
82245
82250
  args.postDocument.dialogueGroups ?? [],
82246
82251
  explicit.dialogueGroupIds,
82247
- impactedIds,
82248
- impactedTypeNames,
82249
- constructedClassIds,
82250
- impactedSourceNames
82252
+ impactScope
82251
82253
  ),
82252
82254
  valueIds
82253
82255
  };
@@ -82323,6 +82325,7 @@ function isCompilerReferenceField(field) {
82323
82325
  }
82324
82326
  function collectChangedContractIds(args) {
82325
82327
  const impactedIds = /* @__PURE__ */ new Set();
82328
+ const changedValueIds = /* @__PURE__ */ new Set();
82326
82329
  const impactedTypeNames = /* @__PURE__ */ new Set();
82327
82330
  const constructedClassIds = /* @__PURE__ */ new Set();
82328
82331
  const currentMembers = new Map(
@@ -82383,7 +82386,7 @@ function collectChangedContractIds(args) {
82383
82386
  const current2 = currentValues.get(change.recordId);
82384
82387
  const next2 = postValues.get(change.recordId);
82385
82388
  if (current2 === void 0 || next2 === void 0 || Reflect.get(current2, "classId") !== Reflect.get(next2, "classId")) {
82386
- impactedIds.add(change.recordId);
82389
+ changedValueIds.add(change.recordId);
82387
82390
  }
82388
82391
  continue;
82389
82392
  }
@@ -82429,7 +82432,12 @@ function collectChangedContractIds(args) {
82429
82432
  }
82430
82433
  }
82431
82434
  }
82432
- return { impactedIds, impactedTypeNames, constructedClassIds };
82435
+ return {
82436
+ impactedIds,
82437
+ changedValueIds,
82438
+ impactedTypeNames,
82439
+ constructedClassIds
82440
+ };
82433
82441
  }
82434
82442
  function generatedConstructorMemberContractChanged(current, next) {
82435
82443
  if (current === void 0 || next === void 0) return true;
@@ -82616,50 +82624,51 @@ function explicitTargetIds(changes) {
82616
82624
  }
82617
82625
  return result;
82618
82626
  }
82619
- function selectedRecordIds(records2, explicitIds, impactedIds, impactedTypeNames, constructedClassIds, impactedSourceNames, ownerId = () => null) {
82627
+ function selectedRecordIds(records2, explicitIds, scope, ownerId = () => null) {
82620
82628
  const selected = new Set(explicitIds);
82621
82629
  for (const record3 of records2) {
82622
82630
  const owner = ownerId(record3);
82623
- if (owner !== null && impactedIds.has(owner) || recordDependsOnChangedContract(
82624
- record3,
82625
- impactedIds,
82626
- impactedTypeNames,
82627
- constructedClassIds,
82628
- impactedSourceNames
82629
- )) {
82631
+ if (owner !== null && scope.impactedIds.has(owner) || recordDependsOnChangedContract(record3, scope)) {
82630
82632
  selected.add(record3.id);
82631
82633
  }
82632
82634
  }
82633
82635
  return selected;
82634
82636
  }
82635
- function recordDependsOnChangedContract(record3, impactedIds, impactedTypeNames, constructedClassIds, impactedSourceNames) {
82637
+ function recordDependsOnChangedContract(record3, scope) {
82636
82638
  const recordId = isObjectRecord4(record3) ? record3.id : void 0;
82637
- if (typeof recordId === "string" && impactedIds.has(recordId)) return true;
82638
- if (intersects(collectCompilerReferenceIds(record3), impactedIds)) return true;
82639
- if (intersects(collectCompilerReferenceNames(record3), impactedTypeNames)) {
82639
+ if (typeof recordId === "string" && scope.impactedIds.has(recordId)) {
82640
82640
  return true;
82641
82641
  }
82642
- if (intersects(collectConstructedClassIds(record3), constructedClassIds)) {
82642
+ const referenceIds = collectCompilerReferenceIds(record3);
82643
+ if (intersects(referenceIds, scope.impactedIds)) return true;
82644
+ if (intersects(referenceIds, scope.changedValueIds)) return true;
82645
+ if (intersects(collectCompilerReferenceNames(record3), scope.impactedTypeNames)) {
82643
82646
  return true;
82644
82647
  }
82645
- if (impactedIds.size === 0 && impactedTypeNames.size === 0) return false;
82648
+ if (intersects(collectConstructedClassIds(record3), scope.constructedClassIds)) {
82649
+ return true;
82650
+ }
82651
+ if (scope.impactedIds.size === 0 && scope.impactedTypeNames.size === 0) {
82652
+ return false;
82653
+ }
82646
82654
  if (!containsAuthoredNeoScriptSource(record3)) return false;
82647
82655
  if (containsCompiledNeoScriptBody(record3)) return false;
82648
- if (impactedSourceNames === null) return true;
82656
+ if (scope.impactedSourceNames === null) return true;
82649
82657
  const sourceIdentifiers = collectAuthoredNeoScriptSourceIdentifiers(record3);
82650
82658
  if (sourceIdentifiers === null) return true;
82651
- return intersects(sourceIdentifiers, impactedSourceNames);
82659
+ return intersects(sourceIdentifiers, scope.impactedSourceNames);
82652
82660
  }
82653
82661
  function collectImpactedSourceNames(args) {
82654
82662
  const names = new Set(args.impactedTypeNames);
82655
82663
  let recoveredContract = args.impactedTypeNames.size > 0;
82664
+ const isImpactedRecordId = (id2) => args.impactedIds.has(id2) || args.changedValueIds.has(id2);
82656
82665
  const collectDocument = (document) => {
82657
82666
  const classById = new Map(
82658
82667
  document.classes.map((schemaClass2) => [schemaClass2.id, schemaClass2])
82659
82668
  );
82660
82669
  const ownerByConstructorId = constructorOwners(document);
82661
82670
  const impactedValueIds = new Set(
82662
- (document.values ?? []).filter((value) => args.impactedIds.has(value.id)).map((value) => value.id)
82671
+ (document.values ?? []).filter((value) => isImpactedRecordId(value.id)).map((value) => value.id)
82663
82672
  );
82664
82673
  const scope = withVariantOwnershipRoots(document, document.variants ?? []);
82665
82674
  const ownerByValueId = resolveOwnerMembersForValues(
@@ -82688,7 +82697,7 @@ function collectImpactedSourceNames(args) {
82688
82697
  ...document.values ?? []
82689
82698
  ];
82690
82699
  for (const record3 of records2) {
82691
- if (!args.impactedIds.has(record3.id)) continue;
82700
+ if (!isImpactedRecordId(record3.id)) continue;
82692
82701
  recoveredContract = true;
82693
82702
  collectDeclaredSourceNames(record3, names);
82694
82703
  const ownerClassId = ownerByConstructorId.get(record3.id);
@@ -89997,7 +90006,7 @@ function prepareServerOwnedSchemaCommit(args) {
89997
90006
  const hasValueChange = authoredChanges.some(
89998
90007
  (change) => change.recordKind === "value"
89999
90008
  );
90000
- const hasAdditionalContractImpact = (args.additionalImpactedIds?.size ?? 0) > 0;
90009
+ const hasAdditionalContractImpact = (args.additionalImpactedIds?.size ?? 0) > 0 || (args.additionalChangedValueIds?.size ?? 0) > 0;
90001
90010
  if (!hasSchemaChange && !hasMigrationChange && !hasDialogueSourceChange && !hasValueChange && !hasAdditionalContractImpact) {
90002
90011
  return authoredChanges;
90003
90012
  }
@@ -90010,6 +90019,7 @@ function prepareServerOwnedSchemaCommit(args) {
90010
90019
  postDocument: authoredPostDocument,
90011
90020
  changes: authoredChanges,
90012
90021
  additionalImpactedIds: args.additionalImpactedIds,
90022
+ additionalChangedValueIds: args.additionalChangedValueIds,
90013
90023
  forceRecompile: args.forceRecompile
90014
90024
  });
90015
90025
  const currentById = new Map(
@@ -90319,7 +90329,8 @@ function materializePreparedInstanceInitializers(args) {
90319
90329
  const existingValueById = new Map(
90320
90330
  args.document.values.map((value) => [value.id, value])
90321
90331
  );
90322
- const currentGraph = new MaterializedValueGraphContext(args.document);
90332
+ let currentGraph = null;
90333
+ const requireCurrentGraph = () => currentGraph ??= new MaterializedValueGraphContext(args.document);
90323
90334
  const rootKinds = /* @__PURE__ */ new Map();
90324
90335
  const materializationScope = withVariantOwnershipRoots(
90325
90336
  compiledDocument,
@@ -90359,7 +90370,7 @@ function materializePreparedInstanceInitializers(args) {
90359
90370
  if (existingConstructedRoot) {
90360
90371
  if (!isLiteralValueContent(materialized.root) || existingRoot.classId !== materialized.root.classId || !replayedCreationDataMatches({
90361
90372
  currentDocument: args.document,
90362
- currentGraph,
90373
+ currentGraph: requireCurrentGraph(),
90363
90374
  currentRoot: existingRoot,
90364
90375
  replayRoot: materialized.root,
90365
90376
  replayCreated: materialized.createdValues
@@ -91349,9 +91360,46 @@ function drainConstructedGraphValidations(args) {
91349
91360
  });
91350
91361
  }
91351
91362
  }
91363
+ function indexAuthoredSeedDocument(document) {
91364
+ const declaringClassIdsByMemberId = /* @__PURE__ */ new Map();
91365
+ for (const schemaClass2 of document.classes) {
91366
+ const declared = /* @__PURE__ */ new Set();
91367
+ for (const memberId of Object.values(schemaClass2.schema)) {
91368
+ if (typeof memberId !== "string") continue;
91369
+ if (declared.has(memberId)) continue;
91370
+ declared.add(memberId);
91371
+ const owners = declaringClassIdsByMemberId.get(memberId);
91372
+ if (owners === void 0) {
91373
+ declaringClassIdsByMemberId.set(memberId, [schemaClass2.id]);
91374
+ continue;
91375
+ }
91376
+ owners.push(schemaClass2.id);
91377
+ }
91378
+ }
91379
+ return {
91380
+ valuesById: indexRecordsById(document.values),
91381
+ membersById: indexRecordsById(document.members),
91382
+ localizedTextIds: new Set(
91383
+ (document.localizedTexts ?? []).map((text) => text.id)
91384
+ ),
91385
+ declaringClassIdsByMemberId
91386
+ };
91387
+ }
91388
+ function declaringClassIds(index, memberId) {
91389
+ return index.declaringClassIdsByMemberId.get(memberId) ?? [];
91390
+ }
91391
+ function hasLiveMemberChange(prepared) {
91392
+ return prepared.some(
91393
+ (change) => change.recordKind === "member" && change.operation !== "delete" && change.nextData !== void 0
91394
+ );
91395
+ }
91352
91396
  function materializeAuthoredValueSeeds(args) {
91397
+ if (args.authoredValueSeeds.length === 0 && !hasLiveMemberChange(args.prepared)) {
91398
+ return;
91399
+ }
91353
91400
  const pendingGraphValidations = [];
91354
91401
  materializeStaticSeedBindingMembers(args);
91402
+ const documentIndex = indexAuthoredSeedDocument(args.document);
91355
91403
  const seedByMemberId = new Map(
91356
91404
  args.authoredValueSeeds.map((seed) => [seed.memberId, seed])
91357
91405
  );
@@ -91371,6 +91419,7 @@ function materializeAuthoredValueSeeds(args) {
91371
91419
  consumedSeedMemberIds.add(member.id);
91372
91420
  materializeInstanceDefaultSeed({
91373
91421
  document: args.document,
91422
+ documentIndex,
91374
91423
  prepared: args.prepared,
91375
91424
  pendingGraphValidations,
91376
91425
  memberChange: { index, change },
@@ -91393,9 +91442,7 @@ function materializeAuthoredValueSeeds(args) {
91393
91442
  `Static Generic member "${member.name}" (${member.id}) cannot materialize a value.`
91394
91443
  );
91395
91444
  }
91396
- const owners = args.document.classes.filter(
91397
- (schemaClass2) => Object.values(schemaClass2.schema).includes(member.id)
91398
- );
91445
+ const owners = declaringClassIds(documentIndex, member.id);
91399
91446
  if (owners.length !== 1) {
91400
91447
  throw new Error(
91401
91448
  `Stored static member "${member.name}" (${member.id}) must have exactly one direct declaring class; found ${owners.length}.`
@@ -91403,7 +91450,7 @@ function materializeAuthoredValueSeeds(args) {
91403
91450
  }
91404
91451
  if (change.operation === "update" && seed === void 0) continue;
91405
91452
  if (change.operation === "create" && seed === void 0 && typeof member.valueId === "string") {
91406
- if (!args.document.values.some((value) => value.id === member.valueId)) {
91453
+ if (!documentIndex.valuesById.has(member.valueId)) {
91407
91454
  throw new Error(
91408
91455
  `Stored static member "${member.name}" (${member.id}) binds missing value "${member.valueId}".`
91409
91456
  );
@@ -91427,6 +91474,7 @@ function materializeAuthoredValueSeeds(args) {
91427
91474
  member: materializationMember
91428
91475
  }) : materializeAuthoredStaticSeed({
91429
91476
  document: args.document,
91477
+ documentIndex,
91430
91478
  member: materializationMember,
91431
91479
  // P61 must leave a computed static root as an init-backed prepared
91432
91480
  // row until `materializePreparedInstanceInitializers` runs. The
@@ -91436,7 +91484,7 @@ function materializeAuthoredValueSeeds(args) {
91436
91484
  seed: { ...seed, values: seed.values ?? [] }
91437
91485
  });
91438
91486
  const { createdValues, localizedTexts, storageKeyDeclarations, rootValue } = materialized;
91439
- const ownedValueId = args.document.members.find((candidate) => candidate.id === member.id)?.valueId ?? derivedMemberValueId(member.id);
91487
+ const ownedValueId = documentIndex.membersById.get(member.id)?.valueId ?? derivedMemberValueId(member.id);
91440
91488
  if (seed?.valueId !== void 0 && seed.valueId !== ownedValueId) {
91441
91489
  throw new Error(
91442
91490
  `Static value seed for member "${member.name}" (${member.id}) binds value "${seed.valueId}", but the member owns value "${ownedValueId}".`
@@ -91449,7 +91497,7 @@ function materializeAuthoredValueSeeds(args) {
91449
91497
  }
91450
91498
  if (seed?.values === void 0 && rootValue.id !== ownedValueId) {
91451
91499
  assertMintedRootValueIdIsFree({
91452
- document: args.document,
91500
+ documentIndex,
91453
91501
  prepared: args.prepared,
91454
91502
  member,
91455
91503
  valueId: ownedValueId
@@ -91471,7 +91519,7 @@ function materializeAuthoredValueSeeds(args) {
91471
91519
  rootValue.id,
91472
91520
  {
91473
91521
  mapKey: null,
91474
- classId: owners[0].id
91522
+ classId: owners[0]
91475
91523
  }
91476
91524
  ]
91477
91525
  ])
@@ -91519,12 +91567,11 @@ function materializeAuthoredValueSeeds(args) {
91519
91567
  }
91520
91568
  for (const seed of args.authoredValueSeeds) {
91521
91569
  if (consumedSeedMemberIds.has(seed.memberId)) continue;
91522
- const member = args.document.members.find(
91523
- (candidate) => candidate.id === seed.memberId
91524
- );
91570
+ const member = documentIndex.membersById.get(seed.memberId);
91525
91571
  if (member !== void 0 && member.isStatic !== true && !isProjectRootMember(args.document.project, member.id)) {
91526
91572
  materializeInstanceDefaultSeed({
91527
91573
  document: args.document,
91574
+ documentIndex,
91528
91575
  prepared: args.prepared,
91529
91576
  pendingGraphValidations,
91530
91577
  memberChange: null,
@@ -91536,6 +91583,7 @@ function materializeAuthoredValueSeeds(args) {
91536
91583
  }
91537
91584
  materializeExistingAuthoredValueSeed({
91538
91585
  document: args.document,
91586
+ documentIndex,
91539
91587
  prepared: args.prepared,
91540
91588
  pendingGraphValidations,
91541
91589
  seed,
@@ -91550,9 +91598,7 @@ function materializeAuthoredValueSeeds(args) {
91550
91598
  });
91551
91599
  }
91552
91600
  function materializeExistingAuthoredValueSeed(args) {
91553
- const member = args.document.members.find(
91554
- (candidate) => candidate.id === args.seed.memberId
91555
- );
91601
+ const member = args.documentIndex.membersById.get(args.seed.memberId);
91556
91602
  if (member === void 0) {
91557
91603
  throw new Error(
91558
91604
  `Member value seed "${args.seed.memberId}" does not target a stored member in this schema commit.`
@@ -91574,9 +91620,7 @@ function materializeExistingAuthoredValueSeed(args) {
91574
91620
  `Existing value graph seed for member "${member.name}" (${member.id}) targets value "${args.seed.valueId}", but the member binds "${String(member.valueId)}".`
91575
91621
  );
91576
91622
  }
91577
- const owners = args.document.classes.filter(
91578
- (schemaClass2) => Object.values(schemaClass2.schema).includes(member.id)
91579
- );
91623
+ const owners = declaringClassIds(args.documentIndex, member.id);
91580
91624
  if (!isRootMember && owners.length !== 1) {
91581
91625
  throw new Error(
91582
91626
  `Stored static member "${member.name}" (${member.id}) must have exactly one direct declaring class; found ${owners.length}.`
@@ -91584,6 +91628,7 @@ function materializeExistingAuthoredValueSeed(args) {
91584
91628
  }
91585
91629
  const materialized = materializeAuthoredStaticSeed({
91586
91630
  document: args.document,
91631
+ documentIndex: args.documentIndex,
91587
91632
  member,
91588
91633
  seed: { ...args.seed, values: args.seed.values },
91589
91634
  allowExistingRows: true
@@ -91604,7 +91649,7 @@ function materializeExistingAuthoredValueSeed(args) {
91604
91649
  mapKey: null,
91605
91650
  // A root member's row has no enclosing class, so declared storage
91606
91651
  // keys below it resolve against no parent class.
91607
- classId: owners[0]?.id
91652
+ classId: owners[0]
91608
91653
  }
91609
91654
  ]
91610
91655
  ])
@@ -91623,9 +91668,7 @@ function materializeExistingAuthoredValueSeed(args) {
91623
91668
  });
91624
91669
  for (const value of materialized.createdValues) {
91625
91670
  if (materialized.existingValueIds.has(value.id)) {
91626
- if (isInitValueContent(value) && !isInitValueContent(
91627
- args.document.values.find((candidate) => candidate.id === value.id)
91628
- )) {
91671
+ if (isInitValueContent(value) && !isInitValueContent(args.documentIndex.valuesById.get(value.id))) {
91629
91672
  args.prepared.push({
91630
91673
  recordKind: "value",
91631
91674
  recordId: value.id,
@@ -91829,9 +91872,7 @@ function materializeInstanceDefaultSeed(args) {
91829
91872
  `Constructed default seed for member "${args.member.name}" (${args.member.id}) requires a root value id and explicit value rows.`
91830
91873
  );
91831
91874
  }
91832
- const owners = args.document.classes.filter(
91833
- (schemaClass2) => Object.values(schemaClass2.schema).includes(args.member.id)
91834
- );
91875
+ const owners = declaringClassIds(args.documentIndex, args.member.id);
91835
91876
  if (owners.length !== 1) {
91836
91877
  throw new Error(
91837
91878
  `Constructed default member "${args.member.name}" (${args.member.id}) must have exactly one direct declaring class; found ${owners.length}.`
@@ -91849,6 +91890,7 @@ function materializeInstanceDefaultSeed(args) {
91849
91890
  existingValueIds
91850
91891
  } = materializeAuthoredStaticSeed({
91851
91892
  document: args.document,
91893
+ documentIndex: args.documentIndex,
91852
91894
  member: materializationMember,
91853
91895
  seed: { ...args.seed, values: args.seed.values },
91854
91896
  allowExistingRows: args.memberChange === null || args.memberChange.change.operation === "update"
@@ -91862,7 +91904,7 @@ function materializeInstanceDefaultSeed(args) {
91862
91904
  rootValue.id,
91863
91905
  {
91864
91906
  mapKey: null,
91865
- classId: owners[0].id
91907
+ classId: owners[0]
91866
91908
  }
91867
91909
  ]
91868
91910
  ])
@@ -91953,7 +91995,7 @@ function materializeDefaultStaticSeed(args) {
91953
91995
  };
91954
91996
  }
91955
91997
  function assertMintedRootValueIdIsFree(args) {
91956
- if (args.document.values.some((value) => value.id === args.valueId)) {
91998
+ if (args.documentIndex.valuesById.has(args.valueId)) {
91957
91999
  throw new Error(
91958
92000
  `Member "${args.member.name}" (${args.member.id}) owns value "${args.valueId}", which already exists in this version.`
91959
92001
  );
@@ -91990,7 +92032,7 @@ function materializeAuthoredStaticSeed(args) {
91990
92032
  const requestedIds = /* @__PURE__ */ new Set([requestedRootId, ...authoredRows.keys()]);
91991
92033
  const existingValueIds = /* @__PURE__ */ new Set();
91992
92034
  for (const id2 of requestedIds) {
91993
- const existing = args.document.values.find((value) => value.id === id2);
92035
+ const existing = args.documentIndex.valuesById.get(id2);
91994
92036
  if (existing === void 0) continue;
91995
92037
  if (args.allowExistingRows !== true) {
91996
92038
  throw new Error(`Value "${id2}" already exists.`);
@@ -92008,6 +92050,7 @@ function materializeAuthoredStaticSeed(args) {
92008
92050
  const usedRows = /* @__PURE__ */ new Set();
92009
92051
  const rootValue = materializeAuthoredSeedRow({
92010
92052
  document: args.document,
92053
+ documentIndex: args.documentIndex,
92011
92054
  member: args.member,
92012
92055
  row: seedRootRow(args.member, args.seed, requestedRootId),
92013
92056
  authoredRows,
@@ -92035,6 +92078,7 @@ function materializeAuthoredStaticSeed(args) {
92035
92078
  }
92036
92079
  const seededLocalizedTexts = materializeStaticLocalizedTexts({
92037
92080
  document: args.document,
92081
+ documentIndex: args.documentIndex,
92038
92082
  seeds: args.seed.localizedTexts ?? [],
92039
92083
  createdValues
92040
92084
  });
@@ -92170,9 +92214,7 @@ function materializeStaticLocalizedTexts(args) {
92170
92214
  throw new Error(`Localized text seed repeats id "${seed.id}".`);
92171
92215
  }
92172
92216
  seen.add(seed.id);
92173
- if ((args.document.localizedTexts ?? []).some(
92174
- (text) => text.id === seed.id
92175
- ) || args.document.values.some((value) => value.id === seed.id)) {
92217
+ if (args.documentIndex.localizedTextIds.has(seed.id) || args.documentIndex.valuesById.has(seed.id)) {
92176
92218
  throw new Error(`Localized text "${seed.id}" already exists.`);
92177
92219
  }
92178
92220
  const owner = args.createdValues.find((value) => value.id === seed.valueId);
@@ -92248,7 +92290,7 @@ function materializeAuthoredSeedRow(args) {
92248
92290
  ...isMemberClassBase(resolvedStoredMember) && args.row.classId !== null ? { topLevelClassId: args.row.classId } : {}
92249
92291
  });
92250
92292
  renameCreatedValueId({
92251
- document: args.document,
92293
+ documentIndex: args.documentIndex,
92252
92294
  createdValues: args.createdValues,
92253
92295
  storageKeyDeclarations: args.storageKeyDeclarations,
92254
92296
  generatedId: generatedRoot.id,
@@ -92336,7 +92378,7 @@ function materializeAuthoredClassFields(args) {
92336
92378
  `Class "${effectiveClassId}" has no stored member "${schemaKey}".`
92337
92379
  );
92338
92380
  }
92339
- const childMember = requireDocumentMember(args.document, childMemberId);
92381
+ const childMember = requireSeedMember(args.documentIndex, childMemberId);
92340
92382
  const currentBody = isStringValueRecord(args.rootValue.value) ? args.rootValue.value : {};
92341
92383
  const previousId = currentBody[schemaKey];
92342
92384
  if (previousId !== void 0 && previousId !== childId) {
@@ -92365,8 +92407,8 @@ function materializeAuthoredListItems(args) {
92365
92407
  if (!Array.isArray(args.row.value)) {
92366
92408
  throw new Error(`List value "${args.row.id}" requires an array body.`);
92367
92409
  }
92368
- const entryMember = requireDocumentMember(
92369
- args.document,
92410
+ const entryMember = requireSeedMember(
92411
+ args.documentIndex,
92370
92412
  args.member.entryMemberId
92371
92413
  );
92372
92414
  const unordered = listKindOf(args.member) === "unordered";
@@ -92397,8 +92439,8 @@ function materializeAuthoredDictionaryEntries(args) {
92397
92439
  `Dictionary value "${args.row.id}" requires an object body.`
92398
92440
  );
92399
92441
  }
92400
- const entryMember = requireDocumentMember(
92401
- args.document,
92442
+ const entryMember = requireSeedMember(
92443
+ args.documentIndex,
92402
92444
  args.member.entryMemberId
92403
92445
  );
92404
92446
  const body = {};
@@ -92416,9 +92458,7 @@ function materializeAuthoredChild(args) {
92416
92458
  if (authored !== void 0) {
92417
92459
  return materializeAuthoredSeedRow({ ...args, row: authored });
92418
92460
  }
92419
- const existing = args.document.values.find(
92420
- (value) => value.id === args.childId
92421
- );
92461
+ const existing = args.documentIndex.valuesById.get(args.childId);
92422
92462
  if (existing === void 0) {
92423
92463
  throw new Error(
92424
92464
  `Authored value graph references missing row "${args.childId}".`
@@ -92428,7 +92468,7 @@ function materializeAuthoredChild(args) {
92428
92468
  }
92429
92469
  function renameCreatedValueId(args) {
92430
92470
  if (args.generatedId === args.requestedId) return;
92431
- if (args.document.values.some((value2) => value2.id === args.requestedId) && !args.replaceExistingIds.has(args.requestedId) || args.createdValues.some(
92471
+ if (args.documentIndex.valuesById.has(args.requestedId) && !args.replaceExistingIds.has(args.requestedId) || args.createdValues.some(
92432
92472
  (value2) => value2.id === args.requestedId && value2.id !== args.generatedId
92433
92473
  )) {
92434
92474
  throw new Error(`Value "${args.requestedId}" already exists.`);
@@ -92500,10 +92540,8 @@ function collectStringReferences2(value, output) {
92500
92540
  collectStringReferences2(entry, output);
92501
92541
  }
92502
92542
  }
92503
- function requireDocumentMember(document, memberId) {
92504
- const member = document.members.find(
92505
- (candidate) => candidate.id === memberId
92506
- );
92543
+ function requireSeedMember(documentIndex, memberId) {
92544
+ const member = documentIndex.membersById.get(memberId);
92507
92545
  if (member === void 0) throw new Error(`Member "${memberId}" is missing.`);
92508
92546
  return member;
92509
92547
  }
@@ -92761,6 +92799,18 @@ function prepareServerOwnedValueInitializerBodies(args) {
92761
92799
  });
92762
92800
  }
92763
92801
  }
92802
+ function collectDelegateSweepRows(args) {
92803
+ if (args.targetIds.size === 0) return [];
92804
+ const preparedValueIds = indexChangesByRecordId(args.prepared, "value");
92805
+ const sweepRows = [];
92806
+ for (const value of args.postDocument.values) {
92807
+ if (!args.targetIds.has(value.id)) continue;
92808
+ if (preparedValueIds.has(value.id)) continue;
92809
+ const row = delegateValueRow(value);
92810
+ if (row !== null) sweepRows.push(row);
92811
+ }
92812
+ return sweepRows;
92813
+ }
92764
92814
  function delegateValueRow(value) {
92765
92815
  if (!isMemberValue(value) || !isLiteralValueContent(value)) return null;
92766
92816
  if (!isNSDelegateClosureValueDraft(value.value) && !isNSDelegateClosureValue(value.value)) {
@@ -92778,11 +92828,7 @@ function prepareServerOwnedDelegateValueBodies(args) {
92778
92828
  const row = delegateValueRow(change.nextData);
92779
92829
  if (row !== null) explicitRows.set(index, row);
92780
92830
  }
92781
- const sweepRows = args.postDocument.values.filter(
92782
- (value) => args.targetIds.has(value.id) && delegateValueRow(value) !== null && !args.prepared.some(
92783
- (change) => change.recordKind === "value" && change.recordId === value.id
92784
- )
92785
- );
92831
+ const sweepRows = collectDelegateSweepRows(args);
92786
92832
  if (explicitRows.size === 0 && sweepRows.length === 0) return;
92787
92833
  const committedDocument = applyProjectVersionWriteChanges(
92788
92834
  args.postDocument,
@@ -93305,8 +93351,7 @@ function applyProjectVersionWriteChanges(document, changes) {
93305
93351
  const hasGranularDialogueNodes = document.dialogueNodes !== void 0 || changes.some((change) => change.recordKind === "dialogue-node");
93306
93352
  const dialogueRecords = hasGranularDialogueRecords ? applyArrayChanges(document.dialogueRecords ?? [], changes, "dialogue") : void 0;
93307
93353
  const dialogueNodes = hasGranularDialogueNodes ? applyArrayChanges(document.dialogueNodes ?? [], changes, "dialogue-node") : void 0;
93308
- return {
93309
- ...document,
93354
+ const projected = {
93310
93355
  project: applySingletonChange(document.project, changes, "project"),
93311
93356
  members: applyArrayChanges(document.members, changes, "member"),
93312
93357
  classes: applyArrayChanges(document.classes, changes, "class"),
@@ -93360,8 +93405,6 @@ function applyProjectVersionWriteChanges(document, changes) {
93360
93405
  ),
93361
93406
  enums: applyArrayChanges(document.enums, changes, "enum"),
93362
93407
  interfaces: applyArrayChanges(document.interfaces, changes, "interface"),
93363
- ...dialogueRecords === void 0 ? {} : { dialogueRecords },
93364
- ...dialogueNodes === void 0 ? {} : { dialogueNodes },
93365
93408
  dialogues: dialogueRecords === void 0 || dialogueNodes === void 0 ? document.dialogues : materializeDialogues(
93366
93409
  dialogueRecords,
93367
93410
  dialogueNodes
@@ -93377,6 +93420,15 @@ function applyProjectVersionWriteChanges(document, changes) {
93377
93420
  "migration"
93378
93421
  )
93379
93422
  };
93423
+ return {
93424
+ ...document,
93425
+ ...projected,
93426
+ ...dialogueRecords === void 0 ? {} : { dialogueRecords },
93427
+ ...dialogueNodes === void 0 ? {} : { dialogueNodes }
93428
+ // The document spread above carries every bucket the caller's view declares
93429
+ // beyond the projected ones, so the result is the same view; TypeScript
93430
+ // cannot prove that of an object literal built over a type parameter.
93431
+ };
93380
93432
  }
93381
93433
  function applySingletonChange(current, changes, recordKind) {
93382
93434
  const change = changes.find(
@@ -93417,6 +93469,16 @@ function canonicallyEqual2(left, right) {
93417
93469
  return canonicalJsonStringify(left) === canonicalJsonStringify(right);
93418
93470
  }
93419
93471
  function reconcileVariantConstructorArgs(args) {
93472
+ const changeIndexByValueId = indexChangesByRecordId(args.prepared, "value");
93473
+ const commitTouchesProjectionSchema = variantConstructorArgsSchemaIsTouched(
93474
+ args.prepared
93475
+ );
93476
+ if (changeIndexByValueId.size === 0 && !commitTouchesProjectionSchema) {
93477
+ return;
93478
+ }
93479
+ if ((args.document.variants ?? []).length === 0 && !args.prepared.some((change) => change.recordKind === "variant")) {
93480
+ return;
93481
+ }
93420
93482
  const postDocument = applyProjectVersionWriteChanges(
93421
93483
  args.document,
93422
93484
  args.prepared
@@ -93427,7 +93489,6 @@ function reconcileVariantConstructorArgs(args) {
93427
93489
  const classById = indexRecordsById(postDocument.classes);
93428
93490
  const memberById2 = indexRecordsById(postDocument.members);
93429
93491
  const constructorById = indexRecordsById(postDocument.constructors ?? []);
93430
- const changeIndexByValueId = indexChangesByRecordId(args.prepared, "value");
93431
93492
  let baseHashByValueId = null;
93432
93493
  for (const variant of variants) {
93433
93494
  const root = valueById.get(variant.valueId);
@@ -93444,6 +93505,9 @@ function reconcileVariantConstructorArgs(args) {
93444
93505
  if (constructorRecord === void 0) continue;
93445
93506
  if (typeof root.value !== "object" || root.value === null) continue;
93446
93507
  if (Array.isArray(root.value)) continue;
93508
+ if (!commitTouchesProjectionSchema && !variantConstructorArgsAreTouched(root, changeIndexByValueId)) {
93509
+ continue;
93510
+ }
93447
93511
  const nextArgs = {};
93448
93512
  for (let index = 0; index < constructorRecord.argumentTypes.length; index += 1) {
93449
93513
  const argument2 = constructorRecord.argumentTypes[index];
@@ -93513,6 +93577,21 @@ function reconcileVariantConstructorArgs(args) {
93513
93577
  });
93514
93578
  }
93515
93579
  }
93580
+ function variantConstructorArgsSchemaIsTouched(prepared) {
93581
+ return prepared.some(
93582
+ (change) => VARIANT_CONSTRUCTOR_ARGS_SCHEMA_RECORD_KINDS.has(change.recordKind)
93583
+ );
93584
+ }
93585
+ function variantConstructorArgsAreTouched(root, changeIndexByValueId) {
93586
+ if (changeIndexByValueId.has(root.id)) return true;
93587
+ const body = root.value;
93588
+ for (const schemaKey of NEO_VARIANT_SCHEMA_KEY_BY_ARGUMENT_NAME.values()) {
93589
+ const childId = body[schemaKey];
93590
+ if (typeof childId !== "string") continue;
93591
+ if (changeIndexByValueId.has(childId)) return true;
93592
+ }
93593
+ return false;
93594
+ }
93516
93595
  function variantConstructorArgumentIsRestValue(schemaKey, value) {
93517
93596
  if (value === null) return true;
93518
93597
  if (schemaKey === NEO_VARIANT_OVERRIDES_SCHEMA_KEY) {
@@ -93561,7 +93640,7 @@ function appendVariantGraphCascadeDeletes(args) {
93561
93640
  });
93562
93641
  }
93563
93642
  }
93564
- var SERVER_COMPILATION_PROJECT_CACHE_LIMIT, serverCompilationProjectCache, READ_ONLY_SOURCE_CONVERSION_INTENT_SOURCE, SCHEMA_RECORD_KINDS, DIALOGUE_SOURCE_RECORD_KINDS, NEO_VARIANT_SCHEMA_KEY_BY_ARGUMENT_NAME;
93643
+ var SERVER_COMPILATION_PROJECT_CACHE_LIMIT, serverCompilationProjectCache, READ_ONLY_SOURCE_CONVERSION_INTENT_SOURCE, SCHEMA_RECORD_KINDS, DIALOGUE_SOURCE_RECORD_KINDS, NEO_VARIANT_SCHEMA_KEY_BY_ARGUMENT_NAME, VARIANT_CONSTRUCTOR_ARGS_SCHEMA_RECORD_KINDS;
93565
93644
  var init_project_version_schema_commit = __esm({
93566
93645
  "../src/database/project-version-schema-commit.ts"() {
93567
93646
  "use strict";
@@ -93623,6 +93702,9 @@ var init_project_version_schema_commit = __esm({
93623
93702
  ["overrides", NEO_VARIANT_OVERRIDES_SCHEMA_KEY],
93624
93703
  ["childoverrides", NEO_VARIANT_CHILD_OVERRIDES_SCHEMA_KEY]
93625
93704
  ]);
93705
+ VARIANT_CONSTRUCTOR_ARGS_SCHEMA_RECORD_KINDS = /* @__PURE__ */ new Set(
93706
+ ["constructor", "class", "member", "variant"]
93707
+ );
93626
93708
  }
93627
93709
  });
93628
93710
 
@@ -105739,6 +105821,7 @@ var init_api = __esm({
105739
105821
 
105740
105822
  // src/convex.ts
105741
105823
  import { ConvexHttpClient } from "convex/browser";
105824
+ import { Buffer as Buffer2 } from "node:buffer";
105742
105825
  async function mintConvexJwt(apiBaseUrl) {
105743
105826
  const sessionToken = loadToken(apiBaseUrl);
105744
105827
  if (sessionToken === null) {
@@ -105796,9 +105879,10 @@ async function resolveConvexUrl(workspace) {
105796
105879
  }
105797
105880
  async function createConvexClient(workspace) {
105798
105881
  const convexUrl = await resolveConvexUrl(workspace);
105799
- const client = new ConvexHttpClient(convexUrl);
105800
- client.setAuth(await mintConvexJwt(workspace.config.apiBaseUrl));
105801
- return client;
105882
+ return await createAuthenticatedConvexClient(
105883
+ convexUrl,
105884
+ workspace.config.apiBaseUrl
105885
+ );
105802
105886
  }
105803
105887
  async function createConvexClientForApi(apiBaseUrl) {
105804
105888
  const fromEnv = process.env.NEXT_PUBLIC_CONVEX_URL;
@@ -105816,9 +105900,52 @@ async function createConvexClientForApi(apiBaseUrl) {
105816
105900
  });
105817
105901
  convexUrl = await discoverConvexUrlFromProjectsResponse(response);
105818
105902
  }
105819
- const client = new ConvexHttpClient(convexUrl);
105820
- client.setAuth(await mintConvexJwt(apiBaseUrl));
105821
- return client;
105903
+ return await createAuthenticatedConvexClient(convexUrl, apiBaseUrl);
105904
+ }
105905
+ async function createAuthenticatedConvexClient(convexUrl, apiBaseUrl) {
105906
+ const tokenCache = new ConvexJwtCache(apiBaseUrl);
105907
+ const initialToken = await tokenCache.getToken();
105908
+ const authenticatedFetch = async (input, init) => {
105909
+ const headers = new Headers(
105910
+ init?.headers ?? (input instanceof Request ? input.headers : void 0)
105911
+ );
105912
+ headers.set("Authorization", `Bearer ${await tokenCache.getToken()}`);
105913
+ return await fetch(input, { ...init, headers });
105914
+ };
105915
+ return new ConvexHttpClient(convexUrl, {
105916
+ auth: initialToken,
105917
+ fetch: authenticatedFetch
105918
+ });
105919
+ }
105920
+ function convexJwtExpiration(token) {
105921
+ const segments = token.split(".");
105922
+ if (segments.length !== 3) {
105923
+ throw new Error("Convex token response is not a three-segment JWT.");
105924
+ }
105925
+ let payloadJson;
105926
+ try {
105927
+ payloadJson = Buffer2.from(segments[1], "base64url").toString("utf8");
105928
+ } catch {
105929
+ throw new Error(
105930
+ "Convex token response has an invalid JWT payload encoding."
105931
+ );
105932
+ }
105933
+ let payload;
105934
+ try {
105935
+ payload = JSON.parse(payloadJson);
105936
+ } catch {
105937
+ throw new Error("Convex token response has an invalid JWT payload.");
105938
+ }
105939
+ if (typeof payload !== "object" || payload === null) {
105940
+ throw new Error("Convex token response has a non-object JWT payload.");
105941
+ }
105942
+ if (!("exp" in payload)) {
105943
+ throw new Error('Convex token response JWT is missing an "exp" claim.');
105944
+ }
105945
+ if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) {
105946
+ throw new Error('Convex token response JWT has an invalid "exp" claim.');
105947
+ }
105948
+ return payload.exp * 1e3;
105822
105949
  }
105823
105950
  async function discoverConvexUrlFromProjectsResponse(response) {
105824
105951
  const headerUrl = response.headers.get("x-neo-convex-url");
@@ -105843,7 +105970,7 @@ function missingProjectsConvexUrlError() {
105843
105970
  "Could not discover the Convex deployment URL (/api/projects supplied neither a non-empty x-neo-convex-url header nor a non-empty convexUrl JSON field)."
105844
105971
  );
105845
105972
  }
105846
- var generatedApiModule, generatedApiValue, api2;
105973
+ var generatedApiModule, generatedApiValue, api2, CONVEX_JWT_REFRESH_LEEWAY_MS, ConvexJwtCache;
105847
105974
  var init_convex = __esm({
105848
105975
  "src/convex.ts"() {
105849
105976
  "use strict";
@@ -105856,6 +105983,38 @@ var init_convex = __esm({
105856
105983
  throw new Error("The generated Convex API module did not export api.");
105857
105984
  }
105858
105985
  api2 = generatedApiValue;
105986
+ CONVEX_JWT_REFRESH_LEEWAY_MS = 6e4;
105987
+ ConvexJwtCache = class {
105988
+ constructor(apiBaseUrl) {
105989
+ this.apiBaseUrl = apiBaseUrl;
105990
+ }
105991
+ apiBaseUrl;
105992
+ token = null;
105993
+ expiresAt = 0;
105994
+ refreshPromise = null;
105995
+ async getToken() {
105996
+ if (this.token !== null && Date.now() + CONVEX_JWT_REFRESH_LEEWAY_MS < this.expiresAt) {
105997
+ return this.token;
105998
+ }
105999
+ const pending = this.refreshPromise ?? this.refresh();
106000
+ this.refreshPromise = pending;
106001
+ try {
106002
+ return await pending;
106003
+ } finally {
106004
+ if (this.refreshPromise === pending) this.refreshPromise = null;
106005
+ }
106006
+ }
106007
+ async refresh() {
106008
+ const token = await mintConvexJwt(this.apiBaseUrl);
106009
+ const expiresAt = convexJwtExpiration(token);
106010
+ if (expiresAt <= Date.now()) {
106011
+ throw new Error("Convex token response contains an expired JWT.");
106012
+ }
106013
+ this.token = token;
106014
+ this.expiresAt = expiresAt;
106015
+ return token;
106016
+ }
106017
+ };
105859
106018
  }
105860
106019
  });
105861
106020
 
@@ -114586,7 +114745,7 @@ var init_registry2 = __esm({
114586
114745
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
114587
114746
  formatVersion: 3,
114588
114747
  contractVersion: "3.14",
114589
- cliVersion: "0.36.11",
114748
+ cliVersion: "0.36.13",
114590
114749
  projectFileUploadBatchSize: 32,
114591
114750
  documentRecords: {
114592
114751
  member: {
@@ -121205,7 +121364,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
121205
121364
  async function main() {
121206
121365
  const args = parseArgs(process.argv.slice(2));
121207
121366
  if (args.command === "--version") {
121208
- console.log("0.36.11");
121367
+ console.log("0.36.13");
121209
121368
  return;
121210
121369
  }
121211
121370
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.36.11",
3
+ "version": "0.36.13",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@inquirer/prompts": "^8.5.2",
38
- "convex": "^1.40.0",
38
+ "convex": "^1.45.0",
39
39
  "ignore": "^7.0.6"
40
40
  },
41
41
  "devDependencies": {
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.36.11 -->
12
+ <!-- reviewed-through-cli: 0.36.13 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -83,7 +83,7 @@ wrappers.
83
83
  The marker near the top of `SKILL.md` must exactly match the package version:
84
84
 
85
85
  ```html
86
- <!-- reviewed-through-cli: 0.36.11 -->
86
+ <!-- reviewed-through-cli: 0.36.13 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale
@@ -132,12 +132,24 @@ neo merge <branch> [--dry-run] [--migrate]
132
132
  neo release cut [--bump major|minor|patch] [--dry-run]
133
133
  neo history inspect
134
134
  neo history log
135
+ neo history prune [--project <id|@latest|@current>]
136
+ neo history flatten [--project <id|@latest|@current>] --version <id|@latest|@current>
137
+ neo history apply --plan <plan-id> [--confirm-scope <project-id>[/<version-id>]]
138
+ neo history status --run <run-id>
139
+ neo history resume --run <run-id>
135
140
  ```
136
141
 
137
142
  Treat branches as copy-on-write forks and releases as immutable snapshots. The
138
143
  server derives the minimum compatibility bump; a caller may only raise it.
139
144
  Require the release login profile for release operations.
140
145
 
146
+ History rewrites require project owner or admin authority. `prune` and
147
+ `flatten` create immutable plans without deleting anything. `apply` starts the
148
+ destructive run after the plan has sealed. Production also requires the exact
149
+ scope confirmation shown by the plan. CLI
150
+ tokens issued before 0.36.12 do not hold the history write grant, so run
151
+ `neo logout` followed by `neo login` once after upgrading.
152
+
141
153
  ## Authentication and automation
142
154
 
143
155
  ```sh
@@ -155,7 +167,9 @@ targeted repair is handed instead of scanning a project to rediscover them.
155
167
  The Node CLI talks directly to authenticated Convex APIs through the
156
168
  session-gated CAS boundary. Tokens use the OS credential store when available
157
169
  and a protected file only as fallback. Use `NEO_COMPOSE_TOKEN` or
158
- `--token-stdin` in CI. The editor profile cannot publish releases; server
170
+ `--token-stdin` in CI. The CLI automatically re-mints its short-lived Convex
171
+ JWT before expiry during long-running commands; the stored device session
172
+ remains the credential. The editor profile cannot publish releases; server
159
173
  scopes remain the security boundary.
160
174
 
161
175
  `neo logout [--api <url>]` deletes the stored credential for exactly one API