@elevasis/core 0.18.0 → 0.20.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.
Files changed (54) hide show
  1. package/dist/index.d.ts +82 -1
  2. package/dist/index.js +353 -171
  3. package/dist/knowledge/index.d.ts +44 -1
  4. package/dist/organization-model/index.d.ts +82 -1
  5. package/dist/organization-model/index.js +353 -171
  6. package/dist/test-utils/index.d.ts +41 -12
  7. package/dist/test-utils/index.js +352 -171
  8. package/package.json +4 -3
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +89 -69
  10. package/src/auth/multi-tenancy/organizations/__tests__/api-schemas.test.ts +194 -0
  11. package/src/auth/multi-tenancy/organizations/api-schemas.ts +136 -128
  12. package/src/business/acquisition/api-schemas.test.ts +199 -15
  13. package/src/business/acquisition/api-schemas.ts +116 -51
  14. package/src/business/acquisition/build-templates.test.ts +212 -0
  15. package/src/business/acquisition/derive-actions.test.ts +1 -1
  16. package/src/business/acquisition/types.ts +21 -38
  17. package/src/business/deals/api-schemas.ts +2 -2
  18. package/src/execution/engine/index.ts +436 -434
  19. package/src/execution/engine/tools/integration/server/adapters/google-calendar/google-calendar-adapter.ts +428 -0
  20. package/src/execution/engine/tools/integration/server/adapters/google-calendar/index.ts +2 -0
  21. package/src/execution/engine/tools/lead-service-types.ts +51 -9
  22. package/src/execution/engine/tools/platform/acquisition/company-tools.ts +7 -6
  23. package/src/execution/engine/tools/platform/acquisition/contact-tools.ts +6 -5
  24. package/src/execution/engine/tools/platform/acquisition/types.ts +20 -9
  25. package/src/execution/engine/tools/registry.ts +700 -698
  26. package/src/execution/engine/tools/tool-maps.ts +10 -0
  27. package/src/execution/external/__tests__/api-schemas.test.ts +127 -0
  28. package/src/integrations/oauth/__tests__/provider-registry.test.ts +7 -6
  29. package/src/integrations/oauth/provider-registry.ts +74 -61
  30. package/src/integrations/oauth/server/credentials.ts +43 -39
  31. package/src/knowledge/__tests__/queries.test.ts +89 -0
  32. package/src/organization-model/__tests__/graph.test.ts +108 -2
  33. package/src/organization-model/__tests__/icons.test.ts +61 -0
  34. package/src/organization-model/__tests__/knowledge.test.ts +118 -1
  35. package/src/organization-model/__tests__/prospecting-ssot.test.ts +91 -0
  36. package/src/organization-model/__tests__/schema.test.ts +122 -0
  37. package/src/organization-model/__tests__/surface-projection.test.ts +174 -0
  38. package/src/organization-model/defaults.ts +8 -0
  39. package/src/organization-model/domains/knowledge.ts +9 -0
  40. package/src/organization-model/domains/prospecting.ts +347 -226
  41. package/src/organization-model/domains/sales.ts +40 -30
  42. package/src/organization-model/graph/build.ts +74 -0
  43. package/src/organization-model/graph/schema.ts +1 -0
  44. package/src/organization-model/graph/types.ts +1 -0
  45. package/src/organization-model/icons.ts +3 -0
  46. package/src/organization-model/schema.ts +63 -0
  47. package/src/organization-model/surface-projection.ts +218 -0
  48. package/src/organization-model/types.ts +9 -1
  49. package/src/platform/constants/versions.ts +1 -1
  50. package/src/platform/utils/__tests__/validation.test.ts +1084 -1083
  51. package/src/platform/utils/validation.ts +425 -425
  52. package/src/reference/_generated/contracts.md +89 -69
  53. package/src/server.ts +6 -0
  54. package/src/supabase/database.types.ts +6 -12
@@ -3,6 +3,7 @@ import { z } from 'zod';
3
3
  // src/organization-model/schema.ts
4
4
  var ORGANIZATION_MODEL_ICON_TOKENS = [
5
5
  "nav.dashboard",
6
+ "nav.calendar",
6
7
  "nav.sales",
7
8
  "nav.crm",
8
9
  "nav.lead-gen",
@@ -17,6 +18,7 @@ var ORGANIZATION_MODEL_ICON_TOKENS = [
17
18
  "knowledge.strategy",
18
19
  "knowledge.reference",
19
20
  "feature.dashboard",
21
+ "feature.calendar",
20
22
  "feature.sales",
21
23
  "feature.crm",
22
24
  "feature.finance",
@@ -40,6 +42,7 @@ var ORGANIZATION_MODEL_ICON_TOKENS = [
40
42
  "integration.google-sheets",
41
43
  "integration.attio",
42
44
  "surface.dashboard",
45
+ "surface.calendar",
43
46
  "surface.overview",
44
47
  "surface.command-view",
45
48
  "surface.command-queue",
@@ -220,6 +223,89 @@ var DEFAULT_ORGANIZATION_MODEL_SALES = {
220
223
  }
221
224
  ]
222
225
  };
226
+ var LEAD_GEN_STAGE_CATALOG = {
227
+ // Prospecting — company population
228
+ scraped: {
229
+ key: "scraped",
230
+ label: "Scraped",
231
+ description: "Company was scraped from a source directory (Apify actor run).",
232
+ order: 1,
233
+ entity: "company"
234
+ },
235
+ populated: {
236
+ key: "populated",
237
+ label: "Companies found",
238
+ description: "Companies have been found and added to the lead-gen list.",
239
+ order: 2,
240
+ entity: "company"
241
+ },
242
+ extracted: {
243
+ key: "extracted",
244
+ label: "Websites analyzed",
245
+ description: "Company websites have been analyzed for business signals.",
246
+ order: 3,
247
+ entity: "company"
248
+ },
249
+ enriched: {
250
+ key: "enriched",
251
+ label: "Enriched",
252
+ description: "Company or contact enriched with third-party data (e.g. Tomba, Anymailfinder).",
253
+ order: 4,
254
+ entity: "company"
255
+ },
256
+ "decision-makers-enriched": {
257
+ key: "decision-makers-enriched",
258
+ label: "Decision-makers found",
259
+ description: "Decision-maker contacts discovered and attached to a qualified company.",
260
+ order: 6,
261
+ entity: "company"
262
+ },
263
+ // Prospecting — contact discovery
264
+ discovered: {
265
+ key: "discovered",
266
+ label: "Decision-makers found",
267
+ description: "Decision-maker contact details have been found.",
268
+ order: 5,
269
+ entity: "contact"
270
+ },
271
+ verified: {
272
+ key: "verified",
273
+ label: "Emails verified",
274
+ description: "Contact email addresses have been checked for deliverability.",
275
+ order: 7,
276
+ entity: "contact"
277
+ },
278
+ // Qualification
279
+ qualified: {
280
+ key: "qualified",
281
+ label: "Companies qualified",
282
+ description: "Companies have been scored against the qualification criteria.",
283
+ order: 8,
284
+ entity: "company"
285
+ },
286
+ // Outreach
287
+ personalized: {
288
+ key: "personalized",
289
+ label: "Personalized",
290
+ description: "Outreach message personalized for the contact (Instantly personalization workflow).",
291
+ order: 9,
292
+ entity: "contact"
293
+ },
294
+ uploaded: {
295
+ key: "uploaded",
296
+ label: "Reviewed and exported",
297
+ description: "Approved records have been reviewed and exported for handoff.",
298
+ order: 10,
299
+ entity: "contact"
300
+ },
301
+ interested: {
302
+ key: "interested",
303
+ label: "Interested",
304
+ description: "Contact replied with a positive signal (Instantly reply-handler transition).",
305
+ order: 11,
306
+ entity: "contact"
307
+ }
308
+ };
223
309
  var ProjectsDomainStateSchema = DisplayMetadataSchema.extend({
224
310
  id: ModelIdSchema,
225
311
  order: z.number().int().min(0)
@@ -300,6 +386,178 @@ var ProspectingBuildTemplateSchema = DisplayMetadataSchema.extend({
300
386
  id: ModelIdSchema,
301
387
  steps: z.array(ProspectingBuildTemplateStepSchema).min(1)
302
388
  });
389
+ z.object({
390
+ id: ModelIdSchema,
391
+ label: z.string(),
392
+ description: z.string(),
393
+ resourceId: ModelIdSchema
394
+ });
395
+ var PROSPECTING_STEPS = {
396
+ localServices: {
397
+ sourceCompanies: {
398
+ id: "source-companies",
399
+ label: "Companies found",
400
+ primaryEntity: "company",
401
+ outputs: ["company"],
402
+ stageKey: "populated",
403
+ dependencyMode: "per-record-eligibility",
404
+ capabilityKey: "lead-gen.company.source",
405
+ defaultBatchSize: 100,
406
+ maxBatchSize: 250
407
+ },
408
+ analyzeWebsites: {
409
+ id: "analyze-websites",
410
+ label: "Websites analyzed",
411
+ primaryEntity: "company",
412
+ outputs: ["company"],
413
+ stageKey: "extracted",
414
+ dependsOn: ["source-companies"],
415
+ dependencyMode: "per-record-eligibility",
416
+ capabilityKey: "lead-gen.company.website-extract",
417
+ defaultBatchSize: 50,
418
+ maxBatchSize: 100
419
+ },
420
+ qualifyCompanies: {
421
+ id: "qualify-companies",
422
+ label: "Companies qualified",
423
+ primaryEntity: "company",
424
+ outputs: ["company"],
425
+ stageKey: "qualified",
426
+ dependsOn: ["analyze-websites"],
427
+ dependencyMode: "per-record-eligibility",
428
+ capabilityKey: "lead-gen.company.qualify",
429
+ defaultBatchSize: 100,
430
+ maxBatchSize: 250
431
+ },
432
+ findContacts: {
433
+ id: "find-contacts",
434
+ label: "Decision-makers found",
435
+ primaryEntity: "contact",
436
+ outputs: ["contact"],
437
+ stageKey: "discovered",
438
+ dependsOn: ["qualify-companies"],
439
+ dependencyMode: "per-record-eligibility",
440
+ capabilityKey: "lead-gen.contact.discover",
441
+ defaultBatchSize: 50,
442
+ maxBatchSize: 100
443
+ },
444
+ verifyEmails: {
445
+ id: "verify-emails",
446
+ label: "Emails verified",
447
+ primaryEntity: "contact",
448
+ outputs: ["contact"],
449
+ stageKey: "verified",
450
+ dependsOn: ["find-contacts"],
451
+ dependencyMode: "per-record-eligibility",
452
+ capabilityKey: "lead-gen.contact.verify-email",
453
+ defaultBatchSize: 100,
454
+ maxBatchSize: 500
455
+ },
456
+ personalize: {
457
+ id: "personalize",
458
+ label: "Personalize",
459
+ primaryEntity: "contact",
460
+ outputs: ["contact"],
461
+ stageKey: "personalized",
462
+ dependsOn: ["verify-emails"],
463
+ dependencyMode: "per-record-eligibility",
464
+ capabilityKey: "lead-gen.contact.personalize",
465
+ defaultBatchSize: 25,
466
+ maxBatchSize: 100
467
+ },
468
+ review: {
469
+ id: "review",
470
+ label: "Reviewed and exported",
471
+ primaryEntity: "contact",
472
+ outputs: ["export"],
473
+ stageKey: "uploaded",
474
+ dependsOn: ["personalize"],
475
+ dependencyMode: "per-record-eligibility",
476
+ capabilityKey: "lead-gen.review.outreach-ready",
477
+ defaultBatchSize: 25,
478
+ maxBatchSize: 100
479
+ }
480
+ },
481
+ dtcApolloClickup: {
482
+ importApolloSearch: {
483
+ id: "import-apollo-search",
484
+ label: "Companies found",
485
+ description: "Pull companies and seed contact data from a predefined Apollo search or list.",
486
+ primaryEntity: "company",
487
+ outputs: ["company", "contact"],
488
+ stageKey: "populated",
489
+ dependencyMode: "per-record-eligibility",
490
+ capabilityKey: "lead-gen.company.apollo-import",
491
+ defaultBatchSize: 250,
492
+ maxBatchSize: 1e3
493
+ },
494
+ analyzeWebsites: {
495
+ id: "analyze-websites",
496
+ label: "Websites analyzed",
497
+ description: "Extract subscription, product, retention, and tech-stack signals from each brand website.",
498
+ primaryEntity: "company",
499
+ outputs: ["company"],
500
+ stageKey: "extracted",
501
+ dependsOn: ["import-apollo-search"],
502
+ dependencyMode: "per-record-eligibility",
503
+ capabilityKey: "lead-gen.company.website-extract",
504
+ defaultBatchSize: 50,
505
+ maxBatchSize: 100
506
+ },
507
+ scoreDtcFit: {
508
+ id: "score-dtc-fit",
509
+ label: "Companies qualified",
510
+ description: "Classify subscription potential, consumable-product fit, retention maturity, and disqualifiers.",
511
+ primaryEntity: "company",
512
+ outputs: ["company"],
513
+ stageKey: "qualified",
514
+ dependsOn: ["analyze-websites"],
515
+ dependencyMode: "per-record-eligibility",
516
+ capabilityKey: "lead-gen.company.dtc-subscription-qualify",
517
+ defaultBatchSize: 100,
518
+ maxBatchSize: 250
519
+ },
520
+ enrichDecisionMakers: {
521
+ id: "enrich-decision-makers",
522
+ label: "Decision-makers found",
523
+ description: "Use Apollo to find qualified contacts at qualified companies - founders, retention leads, lifecycle leads, and marketing owners.",
524
+ primaryEntity: "company",
525
+ outputs: ["contact"],
526
+ stageKey: "decision-makers-enriched",
527
+ dependsOn: ["score-dtc-fit"],
528
+ dependencyMode: "per-record-eligibility",
529
+ capabilityKey: "lead-gen.contact.apollo-decision-maker-enrich",
530
+ defaultBatchSize: 100,
531
+ maxBatchSize: 250
532
+ },
533
+ verifyEmails: {
534
+ id: "verify-emails",
535
+ label: "Emails verified",
536
+ description: "Verify deliverability before the QC and handoff step.",
537
+ primaryEntity: "contact",
538
+ outputs: ["contact"],
539
+ stageKey: "verified",
540
+ dependsOn: ["enrich-decision-makers"],
541
+ dependencyMode: "per-record-eligibility",
542
+ capabilityKey: "lead-gen.contact.verify-email",
543
+ defaultBatchSize: 250,
544
+ maxBatchSize: 500
545
+ },
546
+ reviewAndExport: {
547
+ id: "review-and-export",
548
+ label: "Reviewed and exported",
549
+ description: "Operator QC approves or rejects leads, then approved records are exported as a lead list.",
550
+ primaryEntity: "company",
551
+ outputs: ["export"],
552
+ stageKey: "uploaded",
553
+ dependsOn: ["verify-emails"],
554
+ dependencyMode: "per-record-eligibility",
555
+ capabilityKey: "lead-gen.export.list",
556
+ defaultBatchSize: 100,
557
+ maxBatchSize: 250
558
+ }
559
+ }
560
+ };
303
561
  var OrganizationModelProspectingSchema = z.object({
304
562
  listEntityId: ModelIdSchema,
305
563
  companyEntityId: ModelIdSchema,
@@ -310,21 +568,22 @@ var OrganizationModelProspectingSchema = z.object({
310
568
  defaultBuildTemplateId: ModelIdSchema,
311
569
  buildTemplates: z.array(ProspectingBuildTemplateSchema).min(1)
312
570
  });
571
+ function toProspectingLifecycleStage(stage) {
572
+ return {
573
+ id: stage.key,
574
+ label: stage.label,
575
+ order: stage.order
576
+ };
577
+ }
578
+ function leadGenStagesForEntity(entity) {
579
+ return Object.values(LEAD_GEN_STAGE_CATALOG).filter((stage) => stage.entity === entity).sort((a, b) => a.order - b.order).map(toProspectingLifecycleStage);
580
+ }
313
581
  var DEFAULT_ORGANIZATION_MODEL_PROSPECTING = {
314
582
  listEntityId: "leadgen.list",
315
583
  companyEntityId: "leadgen.company",
316
584
  contactEntityId: "leadgen.contact",
317
- companyStages: [
318
- { id: "populated", label: "Populated", order: 1 },
319
- { id: "extracted", label: "Extracted", order: 2 },
320
- { id: "qualified", label: "Qualified", order: 3 }
321
- ],
322
- contactStages: [
323
- { id: "discovered", label: "Discovered", order: 1 },
324
- { id: "verified", label: "Verified", order: 2 },
325
- { id: "personalized", label: "Personalized", order: 3 },
326
- { id: "uploaded", label: "Uploaded", order: 4 }
327
- ],
585
+ companyStages: leadGenStagesForEntity("company"),
586
+ contactStages: leadGenStagesForEntity("contact"),
328
587
  defaultBuildTemplateId: "local-services",
329
588
  buildTemplates: [
330
589
  {
@@ -332,89 +591,13 @@ var DEFAULT_ORGANIZATION_MODEL_PROSPECTING = {
332
591
  label: "Local Services Prospecting",
333
592
  description: "Curated local-services list build using company sourcing, website analysis, qualification, contact discovery, verification, personalization, and review.",
334
593
  steps: [
335
- {
336
- id: "source-companies",
337
- label: "Source companies",
338
- primaryEntity: "company",
339
- outputs: ["company"],
340
- stageKey: "populated",
341
- dependencyMode: "per-record-eligibility",
342
- capabilityKey: "lead-gen.company.source",
343
- defaultBatchSize: 100,
344
- maxBatchSize: 250
345
- },
346
- {
347
- id: "analyze-websites",
348
- label: "Analyze websites",
349
- primaryEntity: "company",
350
- outputs: ["company"],
351
- stageKey: "extracted",
352
- dependsOn: ["source-companies"],
353
- dependencyMode: "per-record-eligibility",
354
- capabilityKey: "lead-gen.company.website-extract",
355
- defaultBatchSize: 50,
356
- maxBatchSize: 100
357
- },
358
- {
359
- id: "qualify-companies",
360
- label: "Qualify companies",
361
- primaryEntity: "company",
362
- outputs: ["company"],
363
- stageKey: "qualified",
364
- dependsOn: ["analyze-websites"],
365
- dependencyMode: "per-record-eligibility",
366
- capabilityKey: "lead-gen.company.qualify",
367
- defaultBatchSize: 100,
368
- maxBatchSize: 250
369
- },
370
- {
371
- id: "find-contacts",
372
- label: "Find contacts",
373
- primaryEntity: "contact",
374
- outputs: ["contact"],
375
- stageKey: "discovered",
376
- dependsOn: ["qualify-companies"],
377
- dependencyMode: "per-record-eligibility",
378
- capabilityKey: "lead-gen.contact.discover",
379
- defaultBatchSize: 50,
380
- maxBatchSize: 100
381
- },
382
- {
383
- id: "verify-emails",
384
- label: "Verify emails",
385
- primaryEntity: "contact",
386
- outputs: ["contact"],
387
- stageKey: "verified",
388
- dependsOn: ["find-contacts"],
389
- dependencyMode: "per-record-eligibility",
390
- capabilityKey: "lead-gen.contact.verify-email",
391
- defaultBatchSize: 100,
392
- maxBatchSize: 500
393
- },
394
- {
395
- id: "personalize",
396
- label: "Personalize",
397
- primaryEntity: "contact",
398
- outputs: ["contact"],
399
- stageKey: "personalized",
400
- dependsOn: ["verify-emails"],
401
- dependencyMode: "per-record-eligibility",
402
- capabilityKey: "lead-gen.contact.personalize",
403
- defaultBatchSize: 25,
404
- maxBatchSize: 100
405
- },
406
- {
407
- id: "review",
408
- label: "Review",
409
- primaryEntity: "contact",
410
- outputs: ["export"],
411
- stageKey: "uploaded",
412
- dependsOn: ["personalize"],
413
- dependencyMode: "per-record-eligibility",
414
- capabilityKey: "lead-gen.review.outreach-ready",
415
- defaultBatchSize: 25,
416
- maxBatchSize: 100
417
- }
594
+ PROSPECTING_STEPS.localServices.sourceCompanies,
595
+ PROSPECTING_STEPS.localServices.analyzeWebsites,
596
+ PROSPECTING_STEPS.localServices.qualifyCompanies,
597
+ PROSPECTING_STEPS.localServices.findContacts,
598
+ PROSPECTING_STEPS.localServices.verifyEmails,
599
+ PROSPECTING_STEPS.localServices.personalize,
600
+ PROSPECTING_STEPS.localServices.review
418
601
  ]
419
602
  },
420
603
  {
@@ -422,83 +605,12 @@ var DEFAULT_ORGANIZATION_MODEL_PROSPECTING = {
422
605
  label: "DTC Subscription Apollo Export",
423
606
  description: "Prospecting pipeline for DTC subscription or subscription-ready brands where Apollo is the source and contact-enrichment layer, Elevasis handles company research and fit scoring, and approved leads export as an approved lead list.",
424
607
  steps: [
425
- {
426
- id: "import-apollo-search",
427
- label: "Import Apollo search",
428
- description: "Pull companies and seed contact data from a predefined Apollo search or list.",
429
- primaryEntity: "company",
430
- outputs: ["company", "contact"],
431
- stageKey: "populated",
432
- dependencyMode: "per-record-eligibility",
433
- capabilityKey: "lead-gen.company.apollo-import",
434
- defaultBatchSize: 250,
435
- maxBatchSize: 1e3
436
- },
437
- {
438
- id: "analyze-websites",
439
- label: "Analyze websites",
440
- description: "Extract subscription, product, retention, and tech-stack signals from each brand website.",
441
- primaryEntity: "company",
442
- outputs: ["company"],
443
- stageKey: "extracted",
444
- dependsOn: ["import-apollo-search"],
445
- dependencyMode: "per-record-eligibility",
446
- capabilityKey: "lead-gen.company.website-extract",
447
- defaultBatchSize: 50,
448
- maxBatchSize: 100
449
- },
450
- {
451
- id: "score-dtc-fit",
452
- label: "Score DTC fit",
453
- description: "Classify subscription potential, consumable-product fit, retention maturity, and disqualifiers.",
454
- primaryEntity: "company",
455
- outputs: ["company"],
456
- stageKey: "qualified",
457
- dependsOn: ["analyze-websites"],
458
- dependencyMode: "per-record-eligibility",
459
- capabilityKey: "lead-gen.company.dtc-subscription-qualify",
460
- defaultBatchSize: 100,
461
- maxBatchSize: 250
462
- },
463
- {
464
- id: "enrich-decision-makers",
465
- label: "Enrich decision-makers",
466
- description: "Use Apollo to find qualified contacts such as founders, retention leads, lifecycle leads, and marketing owners.",
467
- primaryEntity: "contact",
468
- outputs: ["contact"],
469
- stageKey: "discovered",
470
- dependsOn: ["score-dtc-fit"],
471
- dependencyMode: "per-record-eligibility",
472
- capabilityKey: "lead-gen.contact.apollo-decision-maker-enrich",
473
- defaultBatchSize: 100,
474
- maxBatchSize: 250
475
- },
476
- {
477
- id: "verify-emails",
478
- label: "Verify emails",
479
- description: "Verify deliverability before the QC and handoff step.",
480
- primaryEntity: "contact",
481
- outputs: ["contact"],
482
- stageKey: "verified",
483
- dependsOn: ["enrich-decision-makers"],
484
- dependencyMode: "per-record-eligibility",
485
- capabilityKey: "lead-gen.contact.verify-email",
486
- defaultBatchSize: 250,
487
- maxBatchSize: 500
488
- },
489
- {
490
- id: "review-and-export",
491
- label: "Review and export",
492
- description: "Operator QC approves or rejects leads, then approved records are exported as a lead list.",
493
- primaryEntity: "company",
494
- outputs: ["export"],
495
- stageKey: "uploaded",
496
- dependsOn: ["verify-emails"],
497
- dependencyMode: "per-record-eligibility",
498
- capabilityKey: "lead-gen.export.list",
499
- defaultBatchSize: 100,
500
- maxBatchSize: 250
501
- }
608
+ PROSPECTING_STEPS.dtcApolloClickup.importApolloSearch,
609
+ PROSPECTING_STEPS.dtcApolloClickup.analyzeWebsites,
610
+ PROSPECTING_STEPS.dtcApolloClickup.scoreDtcFit,
611
+ PROSPECTING_STEPS.dtcApolloClickup.enrichDecisionMakers,
612
+ PROSPECTING_STEPS.dtcApolloClickup.verifyEmails,
613
+ PROSPECTING_STEPS.dtcApolloClickup.reviewAndExport
502
614
  ]
503
615
  }
504
616
  ]
@@ -945,6 +1057,8 @@ var DEFAULT_ORGANIZATION_MODEL_STATUSES = {
945
1057
  var KnowledgeLinkSchema = z.object({
946
1058
  nodeId: NodeIdStringSchema
947
1059
  });
1060
+ var KnowledgeSkillBindingSchema = z.string().trim().min(1).max(120);
1061
+ var KnowledgeDomainBindingSchema = z.string().trim().min(1).max(80);
948
1062
  var OrgKnowledgeKindSchema = z.enum(["playbook", "strategy", "reference"]);
949
1063
  var OrgKnowledgeNodeSchema = z.object({
950
1064
  id: ModelIdSchema,
@@ -959,6 +1073,10 @@ var OrgKnowledgeNodeSchema = z.object({
959
1073
  * Each link emits a `governs` edge: knowledge-node -> target node.
960
1074
  */
961
1075
  links: z.array(KnowledgeLinkSchema).default([]),
1076
+ /** Operator skill or command bindings relevant to this node. */
1077
+ skills: z.array(KnowledgeSkillBindingSchema).optional(),
1078
+ /** Domain key used to derive fast graph->skill registries. */
1079
+ domain: KnowledgeDomainBindingSchema.optional(),
962
1080
  /** Identifiers of the roles or members who own this knowledge node. */
963
1081
  ownerIds: z.array(ModelIdSchema).default([]),
964
1082
  /** ISO date string (YYYY-MM-DD or full ISO 8601) of last meaningful update. */
@@ -1012,8 +1130,12 @@ var LEGACY_FEATURE_ALIASES = /* @__PURE__ */ new Map([
1012
1130
  function hasFeature(featuresById, featureId) {
1013
1131
  return featuresById.has(featureId) || featuresById.has(LEGACY_FEATURE_ALIASES.get(featureId) ?? "");
1014
1132
  }
1133
+ function defaultFeaturePathFor(id) {
1134
+ return `/${id.replaceAll(".", "/")}`;
1135
+ }
1015
1136
  var OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((model, ctx) => {
1016
1137
  const featuresById = collectIds(model.features, ctx, ["features"], "Feature");
1138
+ const featureIdsByEffectivePath = /* @__PURE__ */ new Map();
1017
1139
  model.features.forEach((feature, featureIndex) => {
1018
1140
  const segments = feature.id.split(".");
1019
1141
  if (segments.length > 1) {
@@ -1029,6 +1151,20 @@ var OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((model, ct
1029
1151
  const hasChildren = model.features.some(
1030
1152
  (candidate) => candidate.id.startsWith(`${feature.id}.`) && candidate.id !== feature.id
1031
1153
  );
1154
+ const contributesRoutePath = feature.path !== void 0 || !hasChildren;
1155
+ if (contributesRoutePath) {
1156
+ const effectivePath = feature.path ?? defaultFeaturePathFor(feature.id);
1157
+ const existingFeatureId = featureIdsByEffectivePath.get(effectivePath);
1158
+ if (existingFeatureId !== void 0) {
1159
+ addIssue(
1160
+ ctx,
1161
+ ["features", featureIndex, feature.path === void 0 ? "id" : "path"],
1162
+ `Feature "${feature.id}" effective path "${effectivePath}" duplicates feature "${existingFeatureId}"`
1163
+ );
1164
+ } else {
1165
+ featureIdsByEffectivePath.set(effectivePath, feature.id);
1166
+ }
1167
+ }
1032
1168
  if (hasChildren && feature.enabled) {
1033
1169
  const hasEnabledDescendant = model.features.some(
1034
1170
  (candidate) => candidate.id.startsWith(`${feature.id}.`) && candidate.enabled
@@ -1042,6 +1178,43 @@ var OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((model, ct
1042
1178
  }
1043
1179
  }
1044
1180
  });
1181
+ const surfacesById = collectIds(model.navigation.surfaces, ctx, ["navigation", "surfaces"], "Navigation surface");
1182
+ if (model.navigation.defaultSurfaceId !== void 0 && !surfacesById.has(model.navigation.defaultSurfaceId)) {
1183
+ addIssue(
1184
+ ctx,
1185
+ ["navigation", "defaultSurfaceId"],
1186
+ `Navigation defaultSurfaceId references unknown surface "${model.navigation.defaultSurfaceId}"`
1187
+ );
1188
+ }
1189
+ model.navigation.groups.forEach((group, groupIndex) => {
1190
+ group.surfaceIds.forEach((surfaceId, surfaceIndex) => {
1191
+ if (!surfacesById.has(surfaceId)) {
1192
+ addIssue(
1193
+ ctx,
1194
+ ["navigation", "groups", groupIndex, "surfaceIds", surfaceIndex],
1195
+ `Navigation group "${group.id}" references unknown surface "${surfaceId}"`
1196
+ );
1197
+ }
1198
+ });
1199
+ });
1200
+ model.navigation.surfaces.forEach((surface, surfaceIndex) => {
1201
+ if (surface.featureId !== void 0 && !hasFeature(featuresById, surface.featureId)) {
1202
+ addIssue(
1203
+ ctx,
1204
+ ["navigation", "surfaces", surfaceIndex, "featureId"],
1205
+ `Navigation surface "${surface.id}" references unknown feature "${surface.featureId}"`
1206
+ );
1207
+ }
1208
+ surface.featureIds.forEach((featureId, featureIndex) => {
1209
+ if (!hasFeature(featuresById, featureId)) {
1210
+ addIssue(
1211
+ ctx,
1212
+ ["navigation", "surfaces", surfaceIndex, "featureIds", featureIndex],
1213
+ `Navigation surface "${surface.id}" references unknown feature "${featureId}"`
1214
+ );
1215
+ }
1216
+ });
1217
+ });
1045
1218
  const segmentsById = new Map(model.customers.segments.map((seg) => [seg.id, seg]));
1046
1219
  model.offerings.products.forEach((product, productIndex) => {
1047
1220
  product.targetSegmentIds.forEach((segmentId, segmentIndex) => {
@@ -1087,6 +1260,7 @@ var OrganizationGraphNodeKindSchema = z.enum([
1087
1260
  "surface",
1088
1261
  "entity",
1089
1262
  "capability",
1263
+ "stage",
1090
1264
  "resource",
1091
1265
  "knowledge"
1092
1266
  ]);
@@ -1262,6 +1436,14 @@ var DEFAULT_ORGANIZATION_MODEL = {
1262
1436
  enabled: true,
1263
1437
  uiPosition: "sidebar-primary"
1264
1438
  },
1439
+ {
1440
+ id: "monitoring.calendar",
1441
+ label: "Calendar",
1442
+ description: "Google Calendar events and agenda views",
1443
+ enabled: true,
1444
+ path: "/monitoring/calendar",
1445
+ icon: "feature.calendar"
1446
+ },
1265
1447
  {
1266
1448
  id: "monitoring.activity-log",
1267
1449
  label: "Activity Log",