@bevel-software/platform-core-backend 0.8.0 → 0.9.1

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.
Files changed (86) hide show
  1. package/dist/core/core-ports.d.ts +7 -0
  2. package/dist/core/core-ports.d.ts.map +1 -1
  3. package/dist/core/core-ports.js.map +1 -1
  4. package/dist/core/create-core-server.d.ts.map +1 -1
  5. package/dist/core/create-core-server.js +24 -11
  6. package/dist/core/create-core-server.js.map +1 -1
  7. package/dist/core/create-core-services.d.ts +7 -2
  8. package/dist/core/create-core-services.d.ts.map +1 -1
  9. package/dist/core/create-core-services.js +39 -18
  10. package/dist/core/create-core-services.js.map +1 -1
  11. package/dist/modules/access/access.routes.d.ts +3 -1
  12. package/dist/modules/access/access.routes.d.ts.map +1 -1
  13. package/dist/modules/access/access.routes.js +4 -2
  14. package/dist/modules/access/access.routes.js.map +1 -1
  15. package/dist/modules/access/render-roles-yaml.d.ts +22 -0
  16. package/dist/modules/access/render-roles-yaml.d.ts.map +1 -0
  17. package/dist/modules/access/render-roles-yaml.js +56 -0
  18. package/dist/modules/access/render-roles-yaml.js.map +1 -0
  19. package/dist/modules/access/roles-admin.service.d.ts +15 -1
  20. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  21. package/dist/modules/access/roles-admin.service.js +30 -15
  22. package/dist/modules/access/roles-admin.service.js.map +1 -1
  23. package/dist/modules/settings/setup.routes.d.ts +10 -1
  24. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  25. package/dist/modules/settings/setup.routes.js +111 -6
  26. package/dist/modules/settings/setup.routes.js.map +1 -1
  27. package/dist/modules/workspace/startup/kb-git.d.ts +23 -0
  28. package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -0
  29. package/dist/modules/workspace/startup/kb-git.js +86 -0
  30. package/dist/modules/workspace/startup/kb-git.js.map +1 -0
  31. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +74 -0
  32. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -0
  33. package/dist/modules/workspace/startup/kb-startup-runner.js +528 -0
  34. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -0
  35. package/dist/modules/workspace/startup/on-server-start.d.ts +105 -0
  36. package/dist/modules/workspace/startup/on-server-start.d.ts.map +1 -0
  37. package/dist/modules/workspace/startup/on-server-start.js +21 -0
  38. package/dist/modules/workspace/startup/on-server-start.js.map +1 -0
  39. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +46 -0
  40. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -0
  41. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +492 -0
  42. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -0
  43. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +23 -0
  44. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -0
  45. package/dist/modules/workspace/startup/steps/roles-yaml.step.js +69 -0
  46. package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -0
  47. package/dist/modules/workspace/startup/steps/seed-tree.d.ts +17 -0
  48. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -0
  49. package/dist/modules/workspace/startup/steps/seed-tree.js +109 -0
  50. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -0
  51. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +103 -0
  52. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -0
  53. package/dist/modules/workspace/startup/steps/template-files.step.js +337 -0
  54. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -0
  55. package/dist/modules/workspace/workspace.service.d.ts +0 -35
  56. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  57. package/dist/modules/workspace/workspace.service.js +2 -101
  58. package/dist/modules/workspace/workspace.service.js.map +1 -1
  59. package/kb-template/AGENTS.md +11 -5
  60. package/kb-template/access.md +40 -30
  61. package/kb-template/gitignore.template +16 -0
  62. package/package.json +3 -3
  63. package/src/core/core-ports.ts +7 -0
  64. package/src/core/create-core-server.ts +30 -11
  65. package/src/core/create-core-services.ts +43 -22
  66. package/src/modules/access/__tests__/roles-admin.service.test.ts +24 -2
  67. package/src/modules/access/access.routes.ts +3 -0
  68. package/src/modules/access/render-roles-yaml.ts +65 -0
  69. package/src/modules/access/roles-admin.service.ts +32 -14
  70. package/src/modules/settings/__tests__/setup.routes.test.ts +124 -3
  71. package/src/modules/settings/setup.routes.ts +112 -5
  72. package/src/modules/workspace/__tests__/workspace.service.test.ts +5 -94
  73. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +495 -0
  74. package/src/modules/workspace/startup/kb-git.ts +94 -0
  75. package/src/modules/workspace/startup/kb-startup-runner.ts +597 -0
  76. package/src/modules/workspace/startup/on-server-start.ts +97 -0
  77. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +636 -0
  78. package/src/modules/workspace/{plugins-migration.ts → startup/steps/groups-to-plugins.step.ts} +342 -260
  79. package/src/modules/workspace/startup/steps/roles-yaml.step.ts +71 -0
  80. package/src/modules/workspace/startup/steps/seed-tree.ts +115 -0
  81. package/src/modules/workspace/startup/steps/template-files.step.ts +360 -0
  82. package/src/modules/workspace/workspace.service.ts +2 -106
  83. package/src/modules/workspace/__tests__/kb-seed.service.test.ts +0 -512
  84. package/src/modules/workspace/__tests__/plugins-migration.test.ts +0 -427
  85. package/src/modules/workspace/kb-seed.interface.ts +0 -36
  86. package/src/modules/workspace/kb-seed.service.ts +0 -584
@@ -9,7 +9,6 @@ import { BevelIgnoreStack } from './bevel-ignore.js';
9
9
  import { assertValidBranchName } from '../workflow/git/branch-name.js';
10
10
  import { cloneTrackingConfigArgs, SAFE_IMPLICIT_FETCH_ARGS } from '../workflow/git/clone-config.js';
11
11
  import type { IDiffService } from '../diff/diff.interface.js';
12
- import type { IKbSeedService } from './kb-seed.interface.js';
13
12
 
14
13
  /**
15
14
  * Workspaces are per-branch, not per-user (PLAN §3). One on-disk clone per
@@ -119,22 +118,6 @@ export class WorkspaceService implements IWorkspaceService {
119
118
  */
120
119
  private readonly inFlightBootstraps = new Map<string, Promise<void>>();
121
120
 
122
- /**
123
- * Branches whose clone already got this process's scaffolding top-up.
124
- * Per-process on purpose: each boot of a new build re-offers the top-up
125
- * once, which is exactly when an upgrade's migrations must run. Cleared
126
- * wherever `branchDirs` forgets a branch (delete, failed bootstrap), so a
127
- * re-created clone is offered the top-up again rather than judged by the
128
- * fate of one that no longer exists.
129
- *
130
- * `inFlightTopUps` is the concurrency half: membership in the Set is
131
- * claimed BEFORE the (slow, file-moving) top-up finishes, so a concurrent
132
- * caller that finds the branch claimed must still await the in-flight run
133
- * — returning early would hand out a workspace mid-migration.
134
- */
135
- private readonly toppedUpBranches = new Set<string>();
136
- private readonly inFlightTopUps = new Map<string, Promise<void>>();
137
-
138
121
  /** Per-workspace-dir `git fetch origin` timestamp + in-flight tracker. */
139
122
  private readonly lastFetchAt = new Map<string, number>();
140
123
  private readonly inFlightFetches = new Map<string, Promise<void>>();
@@ -145,14 +128,6 @@ export class WorkspaceService implements IWorkspaceService {
145
128
  */
146
129
  private diffService: IDiffService | null = null;
147
130
 
148
- /**
149
- * Late-bound by the composition root. Awaited once before the first per-branch
150
- * clone so the KB remote is guaranteed to carry the protected branches + base
151
- * scaffolding — a fresh (or partially-populated) remote is seeded rather than
152
- * assumed. Null in tests / setups that pre-provision the remote themselves.
153
- */
154
- private seedService: IKbSeedService | null = null;
155
-
156
131
  /**
157
132
  * Late-bound by the composition root: notified with the workspace id
158
133
  * right after a branch's clone is created. The git layer uses this to
@@ -187,10 +162,6 @@ export class WorkspaceService implements IWorkspaceService {
187
162
  this.diffService = diffService;
188
163
  }
189
164
 
190
- setSeedService(seedService: IKbSeedService): void {
191
- this.seedService = seedService;
192
- }
193
-
194
165
  setWorkspaceClonedListener(listener: (workspaceId: string) => void): void {
195
166
  this.onWorkspaceCloned = listener;
196
167
  }
@@ -354,11 +325,6 @@ export class WorkspaceService implements IWorkspaceService {
354
325
  // pulls it. Once per branch per process — the cached paths above return
355
326
  // before reaching here.
356
327
  await this.normalizeCloneTracking(repoDir, branch);
357
- // Same restart-survivor reasoning for the scaffolding top-up: an
358
- // UPGRADED deployment reuses this clone, so a top-up bound to fresh
359
- // clones alone never runs a new build's scaffolding or migrations —
360
- // the Groups→Plugins rename sat out an upgrade exactly this way.
361
- await this.topUpScaffolding(repoDir, branch);
362
328
  this.branchDirs.set(branch, workspaceDir);
363
329
  return this.buildWorkspaceInfo(branch, workspaceDir);
364
330
  } catch {
@@ -384,22 +350,14 @@ export class WorkspaceService implements IWorkspaceService {
384
350
  this.inFlightBootstraps.set(branch, bootstrap);
385
351
 
386
352
  try {
387
- // Ensure the remote is seeded before the first clone of any branch. A
388
- // clone with `-b <branch>` fails outright on an empty remote, so seeding
389
- // must happen ahead of it. The seed service is single-flight, so this is
390
- // a cheap already-resolved await on every clone after the first.
391
- if (this.seedService) {
392
- await this.seedService.ensureRemoteSeeded();
393
- }
353
+ // A clone with `-b <branch>` against a never-seeded remote fails
354
+ // naturally; seeding the remote is the KB startup phase's job, at boot.
394
355
  await fs.mkdir(workspaceDir, { recursive: true });
395
356
  await this.cloneProcessMapForBranch(workspaceDir, branch);
396
357
  this.branchDirs.set(branch, workspaceDir);
397
358
  resolveBootstrap();
398
359
  } catch (err) {
399
360
  this.branchDirs.delete(branch);
400
- // The clone was rolled back, and any top-up claim belonged to it — a
401
- // retried bootstrap must be offered the top-up afresh.
402
- this.toppedUpBranches.delete(branch);
403
361
  rejectBootstrap(err);
404
362
  throw err;
405
363
  } finally {
@@ -444,7 +402,6 @@ export class WorkspaceService implements IWorkspaceService {
444
402
  await pending;
445
403
  } catch (err) {
446
404
  this.branchDirs.delete(branch);
447
- this.toppedUpBranches.delete(branch);
448
405
  throw err;
449
406
  }
450
407
  }
@@ -475,44 +432,6 @@ export class WorkspaceService implements IWorkspaceService {
475
432
  }
476
433
  }
477
434
 
478
- /**
479
- * The ONE spelling of the best-effort scaffolding top-up (three call sites:
480
- * the restart-survivor path in `getOrCreateForBranch`, and the
481
- * already-cloned / fresh-clone paths in `cloneProcessMapForBranch`). Once
482
- * per branch per process, and SINGLE-FLIGHT: the branch is claimed in
483
- * `toppedUpBranches` before the run starts, so a concurrent caller that
484
- * finds it claimed awaits the in-flight run instead of returning a
485
- * workspace the migration is still moving files under. Never throws — a
486
- * failed top-up costs the upgrade, not the clone.
487
- */
488
- private async topUpScaffolding(repoDir: string, branch: string): Promise<void> {
489
- const seedService = this.seedService;
490
- if (!seedService) return;
491
- if (this.toppedUpBranches.has(branch)) {
492
- await this.inFlightTopUps.get(branch);
493
- return;
494
- }
495
- this.toppedUpBranches.add(branch);
496
- const run = (async () => {
497
- try {
498
- await seedService.topUpWorkspace(repoDir, branch);
499
- } catch (topUpErr) {
500
- console.error(
501
- `[workspace] scaffolding top-up failed for branch "${branch}" (clone kept):`,
502
- redactError(topUpErr),
503
- );
504
- }
505
- })();
506
- this.inFlightTopUps.set(branch, run);
507
- try {
508
- await run;
509
- } finally {
510
- if (this.inFlightTopUps.get(branch) === run) {
511
- this.inFlightTopUps.delete(branch);
512
- }
513
- }
514
- }
515
-
516
435
  private async cloneProcessMapForBranch(workspaceDir: string, branch: string): Promise<void> {
517
436
  const targetDir = path.join(workspaceDir, this.kbDirName);
518
437
 
@@ -561,11 +480,6 @@ export class WorkspaceService implements IWorkspaceService {
561
480
  // it never touches the working tree, and a drifted config is what
562
481
  // breaks the next pull.
563
482
  await this.normalizeCloneTracking(targetDir, branch);
564
- // An upgraded deployment REUSES this persistent clone, so a top-up
565
- // bound to fresh clones alone never runs a new build's scaffolding
566
- // or migrations here — the Groups→Plugins rename sat out an upgrade
567
- // for exactly this reason.
568
- await this.topUpScaffolding(targetDir, branch);
569
483
  return;
570
484
  }
571
485
  // Half-built dir — wipe and re-clone.
@@ -606,15 +520,6 @@ export class WorkspaceService implements IWorkspaceService {
606
520
  redactError(listenerErr),
607
521
  );
608
522
  }
609
- // Fill in any base scaffolding this branch is missing, now that it's
610
- // freshly checked out. Runs on whichever branch the user loaded (not
611
- // just protected ones). Best-effort inside the helper — like the
612
- // onWorkspaceCloned listener above — so even a future change that lets
613
- // it throw can never roll back an otherwise-good clone. (The
614
- // once-per-process guard is a formality here: a branch being freshly
615
- // cloned was either never topped up or had its claim cleared with the
616
- // clone it belonged to.)
617
- await this.topUpScaffolding(targetDir, branch);
618
523
  } catch (err) {
619
524
  const redacted = redactError(err);
620
525
  console.error(`[workspace] Failed to clone for branch "${branch}":`, redacted);
@@ -643,10 +548,6 @@ export class WorkspaceService implements IWorkspaceService {
643
548
  await fs.rm(workspaceDir, { recursive: true, force: true });
644
549
  const branch = branchForWorkspaceId(workspaceId);
645
550
  this.branchDirs.delete(branch);
646
- // The top-up claim died with the clone it was made against — a branch
647
- // re-created later gets a fresh clone and must be offered it again (and
648
- // without this, long-lived processes would accumulate dead branch names).
649
- this.toppedUpBranches.delete(branch);
650
551
  }
651
552
 
652
553
  async listFiles(workspaceId: string, readFilter?: ReadTreeFilter): Promise<FileTreeEntry> {
@@ -1284,11 +1185,6 @@ export class WorkspaceService implements IWorkspaceService {
1284
1185
  try {
1285
1186
  await fs.rm(dir, { recursive: true, force: true });
1286
1187
  this.branchDirs.delete(branchForWorkspaceId(entry.name));
1287
- // The top-up claim dies with the clone (same rule as every other
1288
- // eviction site): a branch re-created after the sweep gets a fresh
1289
- // clone that must be OFFERED the scaffolding top-up, not judged by
1290
- // the fate of a directory that no longer exists.
1291
- this.toppedUpBranches.delete(branchForWorkspaceId(entry.name));
1292
1188
  removed.push(entry.name);
1293
1189
  } catch (err) {
1294
1190
  console.warn(