@eduardbar/drift 1.2.0 → 1.4.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 (195) hide show
  1. package/.gga +50 -0
  2. package/.github/actions/drift-review/README.md +60 -0
  3. package/.github/actions/drift-review/action.yml +131 -0
  4. package/.github/actions/drift-scan/README.md +28 -32
  5. package/.github/actions/drift-scan/action.yml +78 -14
  6. package/.github/workflows/publish-vscode.yml +3 -3
  7. package/.github/workflows/publish.yml +3 -3
  8. package/.github/workflows/review-pr.yml +94 -9
  9. package/AGENTS.md +75 -245
  10. package/CHANGELOG.md +28 -0
  11. package/README.md +308 -51
  12. package/ROADMAP.md +6 -5
  13. package/dist/analyzer.d.ts +2 -2
  14. package/dist/analyzer.js +420 -159
  15. package/dist/benchmark.d.ts +2 -0
  16. package/dist/benchmark.js +204 -0
  17. package/dist/cli.js +693 -67
  18. package/dist/config.js +16 -2
  19. package/dist/diff.js +66 -10
  20. package/dist/doctor.d.ts +5 -0
  21. package/dist/doctor.js +133 -0
  22. package/dist/format.d.ts +17 -0
  23. package/dist/format.js +45 -0
  24. package/dist/git.js +12 -0
  25. package/dist/guard-types.d.ts +57 -0
  26. package/dist/guard-types.js +2 -0
  27. package/dist/guard.d.ts +14 -0
  28. package/dist/guard.js +239 -0
  29. package/dist/index.d.ts +12 -3
  30. package/dist/index.js +6 -1
  31. package/dist/init.d.ts +15 -0
  32. package/dist/init.js +273 -0
  33. package/dist/map-cycles.d.ts +2 -0
  34. package/dist/map-cycles.js +34 -0
  35. package/dist/map-svg.d.ts +19 -0
  36. package/dist/map-svg.js +97 -0
  37. package/dist/map.js +78 -138
  38. package/dist/metrics.js +70 -55
  39. package/dist/output-metadata.d.ts +13 -0
  40. package/dist/output-metadata.js +17 -0
  41. package/dist/plugins-capabilities.d.ts +4 -0
  42. package/dist/plugins-capabilities.js +21 -0
  43. package/dist/plugins-messages.d.ts +10 -0
  44. package/dist/plugins-messages.js +16 -0
  45. package/dist/plugins-rules.d.ts +9 -0
  46. package/dist/plugins-rules.js +137 -0
  47. package/dist/plugins.d.ts +2 -1
  48. package/dist/plugins.js +80 -28
  49. package/dist/printer.js +4 -0
  50. package/dist/reporter-constants.d.ts +16 -0
  51. package/dist/reporter-constants.js +39 -0
  52. package/dist/reporter.d.ts +3 -3
  53. package/dist/reporter.js +35 -55
  54. package/dist/review.d.ts +2 -1
  55. package/dist/review.js +4 -3
  56. package/dist/rules/comments.js +2 -2
  57. package/dist/rules/complexity.js +2 -7
  58. package/dist/rules/nesting.js +3 -13
  59. package/dist/rules/phase0-basic.js +10 -10
  60. package/dist/rules/phase3-configurable.js +23 -15
  61. package/dist/rules/shared.d.ts +2 -0
  62. package/dist/rules/shared.js +27 -3
  63. package/dist/saas/constants.d.ts +15 -0
  64. package/dist/saas/constants.js +48 -0
  65. package/dist/saas/dashboard.d.ts +8 -0
  66. package/dist/saas/dashboard.js +132 -0
  67. package/dist/saas/errors.d.ts +19 -0
  68. package/dist/saas/errors.js +37 -0
  69. package/dist/saas/helpers.d.ts +21 -0
  70. package/dist/saas/helpers.js +110 -0
  71. package/dist/saas/ingest.d.ts +3 -0
  72. package/dist/saas/ingest.js +249 -0
  73. package/dist/saas/organization.d.ts +5 -0
  74. package/dist/saas/organization.js +82 -0
  75. package/dist/saas/plan-change.d.ts +10 -0
  76. package/dist/saas/plan-change.js +15 -0
  77. package/dist/saas/store.d.ts +21 -0
  78. package/dist/saas/store.js +159 -0
  79. package/dist/saas/types.d.ts +191 -0
  80. package/dist/saas/types.js +2 -0
  81. package/dist/saas.d.ts +8 -82
  82. package/dist/saas.js +7 -320
  83. package/dist/sarif.d.ts +74 -0
  84. package/dist/sarif.js +122 -0
  85. package/dist/trust-advanced.d.ts +14 -0
  86. package/dist/trust-advanced.js +65 -0
  87. package/dist/trust-kpi-fs.d.ts +3 -0
  88. package/dist/trust-kpi-fs.js +141 -0
  89. package/dist/trust-kpi-parse.d.ts +7 -0
  90. package/dist/trust-kpi-parse.js +186 -0
  91. package/dist/trust-kpi-types.d.ts +16 -0
  92. package/dist/trust-kpi-types.js +2 -0
  93. package/dist/trust-kpi.d.ts +7 -0
  94. package/dist/trust-kpi.js +185 -0
  95. package/dist/trust-policy.d.ts +32 -0
  96. package/dist/trust-policy.js +160 -0
  97. package/dist/trust-render.d.ts +9 -0
  98. package/dist/trust-render.js +54 -0
  99. package/dist/trust-scoring.d.ts +9 -0
  100. package/dist/trust-scoring.js +208 -0
  101. package/dist/trust.d.ts +37 -0
  102. package/dist/trust.js +168 -0
  103. package/dist/types/app.d.ts +30 -0
  104. package/dist/types/app.js +2 -0
  105. package/dist/types/config.d.ts +25 -0
  106. package/dist/types/config.js +2 -0
  107. package/dist/types/core.d.ts +100 -0
  108. package/dist/types/core.js +2 -0
  109. package/dist/types/diff.d.ts +55 -0
  110. package/dist/types/diff.js +2 -0
  111. package/dist/types/plugin.d.ts +41 -0
  112. package/dist/types/plugin.js +2 -0
  113. package/dist/types/trust.d.ts +120 -0
  114. package/dist/types/trust.js +2 -0
  115. package/dist/types.d.ts +8 -211
  116. package/docs/PRD.md +187 -109
  117. package/docs/plugin-contract.md +61 -0
  118. package/docs/release-notes-draft.md +40 -0
  119. package/docs/rules-catalog.md +49 -0
  120. package/docs/trust-core-release-checklist.md +87 -0
  121. package/package.json +6 -3
  122. package/packages/vscode-drift/src/code-actions.ts +1 -1
  123. package/schemas/drift-ai-output.v1.json +162 -0
  124. package/schemas/drift-report.v1.json +151 -0
  125. package/schemas/drift-trust.v1.json +131 -0
  126. package/scripts/smoke-repo.mjs +394 -0
  127. package/src/analyzer.ts +484 -155
  128. package/src/benchmark.ts +266 -0
  129. package/src/cli.ts +840 -85
  130. package/src/config.ts +19 -2
  131. package/src/diff.ts +84 -10
  132. package/src/doctor.ts +173 -0
  133. package/src/format.ts +81 -0
  134. package/src/git.ts +16 -0
  135. package/src/guard-types.ts +64 -0
  136. package/src/guard.ts +324 -0
  137. package/src/index.ts +83 -0
  138. package/src/init.ts +298 -0
  139. package/src/map-cycles.ts +38 -0
  140. package/src/map-svg.ts +124 -0
  141. package/src/map.ts +111 -142
  142. package/src/metrics.ts +78 -59
  143. package/src/output-metadata.ts +30 -0
  144. package/src/plugins-capabilities.ts +36 -0
  145. package/src/plugins-messages.ts +35 -0
  146. package/src/plugins-rules.ts +296 -0
  147. package/src/plugins.ts +148 -27
  148. package/src/printer.ts +4 -0
  149. package/src/reporter-constants.ts +46 -0
  150. package/src/reporter.ts +64 -65
  151. package/src/review.ts +6 -4
  152. package/src/rules/comments.ts +2 -2
  153. package/src/rules/complexity.ts +2 -7
  154. package/src/rules/nesting.ts +3 -13
  155. package/src/rules/phase0-basic.ts +11 -12
  156. package/src/rules/phase3-configurable.ts +39 -26
  157. package/src/rules/shared.ts +31 -3
  158. package/src/saas/constants.ts +56 -0
  159. package/src/saas/dashboard.ts +172 -0
  160. package/src/saas/errors.ts +45 -0
  161. package/src/saas/helpers.ts +140 -0
  162. package/src/saas/ingest.ts +278 -0
  163. package/src/saas/organization.ts +99 -0
  164. package/src/saas/plan-change.ts +19 -0
  165. package/src/saas/store.ts +172 -0
  166. package/src/saas/types.ts +216 -0
  167. package/src/saas.ts +49 -433
  168. package/src/sarif.ts +232 -0
  169. package/src/trust-advanced.ts +99 -0
  170. package/src/trust-kpi-fs.ts +169 -0
  171. package/src/trust-kpi-parse.ts +219 -0
  172. package/src/trust-kpi-types.ts +19 -0
  173. package/src/trust-kpi.ts +210 -0
  174. package/src/trust-policy.ts +246 -0
  175. package/src/trust-render.ts +61 -0
  176. package/src/trust-scoring.ts +231 -0
  177. package/src/trust.ts +260 -0
  178. package/src/types/app.ts +30 -0
  179. package/src/types/config.ts +27 -0
  180. package/src/types/core.ts +105 -0
  181. package/src/types/diff.ts +61 -0
  182. package/src/types/plugin.ts +46 -0
  183. package/src/types/trust.ts +134 -0
  184. package/src/types.ts +78 -238
  185. package/tests/cli-sarif.test.ts +92 -0
  186. package/tests/diff.test.ts +124 -0
  187. package/tests/format.test.ts +157 -0
  188. package/tests/new-features.test.ts +80 -1
  189. package/tests/phase1-init-doctor-guard.test.ts +199 -0
  190. package/tests/plugins.test.ts +219 -0
  191. package/tests/rules.test.ts +23 -1
  192. package/tests/saas-foundation.test.ts +358 -1
  193. package/tests/sarif.test.ts +160 -0
  194. package/tests/trust-kpi.test.ts +147 -0
  195. package/tests/trust.test.ts +602 -0
@@ -0,0 +1,82 @@
1
+ import { resolve } from 'node:path';
2
+ import { assertPermissionInStore, defaultSaasStorePath, loadStoreInternal, saveStore } from './store.js';
3
+ import { monthKey, normalizePlan, workspaceKey } from './helpers.js';
4
+ import { appendPlanChange } from './plan-change.js';
5
+ export function changeOrganizationPlan(options) {
6
+ const storeFile = resolve(options.storeFile ?? defaultSaasStorePath());
7
+ const store = loadStoreInternal(storeFile, options.policy);
8
+ const nowIso = new Date().toISOString();
9
+ const organization = store.organizations[options.organizationId];
10
+ if (!organization)
11
+ throw new Error(`Organization '${options.organizationId}' does not exist.`);
12
+ assertPermissionInStore(store, {
13
+ operation: 'billing:write',
14
+ organizationId: options.organizationId,
15
+ actorUserId: options.actorUserId,
16
+ });
17
+ const nextPlan = normalizePlan(options.newPlan);
18
+ if (organization.plan === nextPlan) {
19
+ const unchanged = appendPlanChange(store, {
20
+ organizationId: organization.id,
21
+ fromPlan: organization.plan,
22
+ toPlan: nextPlan,
23
+ changedAt: nowIso,
24
+ changedByUserId: options.actorUserId,
25
+ reason: options.reason,
26
+ });
27
+ saveStore(storeFile, store);
28
+ return unchanged;
29
+ }
30
+ const previousPlan = organization.plan;
31
+ organization.plan = nextPlan;
32
+ organization.lastSeenAt = nowIso;
33
+ const change = appendPlanChange(store, {
34
+ organizationId: organization.id,
35
+ fromPlan: previousPlan,
36
+ toPlan: nextPlan,
37
+ changedAt: nowIso,
38
+ changedByUserId: options.actorUserId,
39
+ reason: options.reason,
40
+ });
41
+ saveStore(storeFile, store);
42
+ return change;
43
+ }
44
+ export function listOrganizationPlanChanges(options) {
45
+ const storeFile = resolve(options.storeFile ?? defaultSaasStorePath());
46
+ const store = loadStoreInternal(storeFile, options.policy);
47
+ assertPermissionInStore(store, {
48
+ operation: 'billing:read',
49
+ organizationId: options.organizationId,
50
+ actorUserId: options.actorUserId,
51
+ });
52
+ return store.planChanges
53
+ .filter((change) => change.organizationId === options.organizationId)
54
+ .sort((a, b) => b.changedAt.localeCompare(a.changedAt));
55
+ }
56
+ export function getOrganizationUsageSnapshot(options) {
57
+ const storeFile = resolve(options.storeFile ?? defaultSaasStorePath());
58
+ const store = loadStoreInternal(storeFile, options.policy);
59
+ assertPermissionInStore(store, {
60
+ operation: 'billing:read',
61
+ organizationId: options.organizationId,
62
+ actorUserId: options.actorUserId,
63
+ });
64
+ const organization = store.organizations[options.organizationId];
65
+ if (!organization)
66
+ throw new Error(`Organization '${options.organizationId}' does not exist.`);
67
+ const month = options.month ?? monthKey(new Date().toISOString());
68
+ const organizationRunSnapshots = store.snapshots.filter((snapshot) => snapshot.organizationId === options.organizationId);
69
+ return {
70
+ organizationId: options.organizationId,
71
+ plan: organization.plan,
72
+ capturedAt: new Date().toISOString(),
73
+ workspaceCount: organization.workspaceIds.length,
74
+ repoCount: organization.workspaceIds
75
+ .map((workspaceId) => store.workspaces[workspaceKey(options.organizationId, workspaceId)])
76
+ .filter((workspace) => Boolean(workspace))
77
+ .reduce((count, workspace) => count + workspace.repoIds.length, 0),
78
+ runCount: organizationRunSnapshots.length,
79
+ runCountThisMonth: organizationRunSnapshots.filter((snapshot) => monthKey(snapshot.createdAt) === month).length,
80
+ };
81
+ }
82
+ //# sourceMappingURL=organization.js.map
@@ -0,0 +1,10 @@
1
+ import type { SaasPlan, SaasPlanChange, SaasStore } from './types.js';
2
+ export declare function appendPlanChange(store: SaasStore, input: {
3
+ organizationId: string;
4
+ fromPlan: SaasPlan;
5
+ toPlan: SaasPlan;
6
+ changedByUserId: string;
7
+ reason?: string;
8
+ changedAt: string;
9
+ }): SaasPlanChange;
10
+ //# sourceMappingURL=plan-change.d.ts.map
@@ -0,0 +1,15 @@
1
+ import { createRandomId } from './constants.js';
2
+ export function appendPlanChange(store, input) {
3
+ const change = {
4
+ id: createRandomId(input.changedAt),
5
+ organizationId: input.organizationId,
6
+ fromPlan: input.fromPlan,
7
+ toPlan: input.toPlan,
8
+ changedAt: input.changedAt,
9
+ changedByUserId: input.changedByUserId,
10
+ reason: input.reason,
11
+ };
12
+ store.planChanges.push(change);
13
+ return change;
14
+ }
15
+ //# sourceMappingURL=plan-change.js.map
@@ -0,0 +1,21 @@
1
+ import type { SaasEffectiveLimits, SaasOperation, SaasPermissionContext, SaasPermissionResult, SaasPlan, SaasPolicyOverrides, SaasRole, SaasStore } from './types.js';
2
+ export declare function defaultSaasStorePath(root?: string): string;
3
+ export declare function applyRetentionPolicy(store: SaasStore): void;
4
+ export declare function saveStore(storeFile: string, store: SaasStore): void;
5
+ export declare function loadStoreInternal(storeFile: string, policy?: SaasPolicyOverrides): SaasStore;
6
+ export declare function assertPermissionInStore(store: SaasStore, context: SaasPermissionContext): SaasPermissionResult;
7
+ export declare function getRequiredRoleForOperation(operation: SaasOperation): SaasRole;
8
+ export declare function assertSaasPermission(context: SaasPermissionContext & {
9
+ storeFile?: string;
10
+ policy?: SaasPolicyOverrides;
11
+ }): SaasPermissionResult;
12
+ export declare function getSaasEffectiveLimits(input: {
13
+ plan: SaasPlan;
14
+ policy?: SaasPolicyOverrides;
15
+ }): SaasEffectiveLimits;
16
+ export declare function getOrganizationEffectiveLimits(options: {
17
+ organizationId: string;
18
+ storeFile?: string;
19
+ policy?: SaasPolicyOverrides;
20
+ }): SaasEffectiveLimits;
21
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1,159 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { DEFAULT_ORGANIZATION_ID, REQUIRED_ROLE_BY_OPERATION, ROLE_PRIORITY, STORE_VERSION } from './constants.js';
4
+ import { SaasActorRequiredError, SaasPermissionError } from './errors.js';
5
+ import { hasRoleAtLeast, membershipKey, mergePolicy, normalizePlan, resolveSaasPolicy } from './helpers.js';
6
+ const HOURS_PER_DAY = 24;
7
+ const MINUTES_PER_HOUR = 60;
8
+ const SECONDS_PER_MINUTE = 60;
9
+ const MILLISECONDS_PER_SECOND = 1000;
10
+ export function defaultSaasStorePath(root = '.') {
11
+ return resolve(root, '.drift-cloud', 'store.json');
12
+ }
13
+ function createEmptyStore(policy) {
14
+ return {
15
+ version: STORE_VERSION,
16
+ policy: resolveSaasPolicy(policy),
17
+ users: {},
18
+ organizations: {},
19
+ workspaces: {},
20
+ memberships: {},
21
+ repos: {},
22
+ snapshots: [],
23
+ planChanges: [],
24
+ };
25
+ }
26
+ function ensureStoreFile(storeFile, policy) {
27
+ const dir = dirname(storeFile);
28
+ if (!existsSync(dir))
29
+ mkdirSync(dir, { recursive: true });
30
+ if (!existsSync(storeFile)) {
31
+ const initial = createEmptyStore(policy);
32
+ writeFileSync(storeFile, JSON.stringify(initial, null, 2), 'utf8');
33
+ }
34
+ }
35
+ function applyStoreEntityDefaults(store) {
36
+ for (const workspace of Object.values(store.workspaces)) {
37
+ if (!workspace.organizationId)
38
+ workspace.organizationId = DEFAULT_ORGANIZATION_ID;
39
+ }
40
+ for (const repo of Object.values(store.repos)) {
41
+ if (!repo.organizationId)
42
+ repo.organizationId = DEFAULT_ORGANIZATION_ID;
43
+ }
44
+ for (const snapshot of store.snapshots) {
45
+ if (!snapshot.organizationId)
46
+ snapshot.organizationId = DEFAULT_ORGANIZATION_ID;
47
+ if (!snapshot.plan)
48
+ snapshot.plan = 'free';
49
+ if (!snapshot.role)
50
+ snapshot.role = 'member';
51
+ }
52
+ }
53
+ function ensureOrganizationFromWorkspace(store, workspace) {
54
+ const orgId = workspace.organizationId;
55
+ const existingOrg = store.organizations[orgId];
56
+ if (!existingOrg) {
57
+ store.organizations[orgId] = {
58
+ id: orgId,
59
+ plan: 'free',
60
+ createdAt: workspace.createdAt,
61
+ lastSeenAt: workspace.lastSeenAt,
62
+ workspaceIds: [workspace.id],
63
+ };
64
+ return;
65
+ }
66
+ if (!existingOrg.workspaceIds.includes(workspace.id))
67
+ existingOrg.workspaceIds.push(workspace.id);
68
+ if (workspace.lastSeenAt > existingOrg.lastSeenAt)
69
+ existingOrg.lastSeenAt = workspace.lastSeenAt;
70
+ }
71
+ function hydrateOrganizationsFromWorkspaces(store) {
72
+ for (const workspace of Object.values(store.workspaces)) {
73
+ ensureOrganizationFromWorkspace(store, workspace);
74
+ }
75
+ }
76
+ export function applyRetentionPolicy(store) {
77
+ const millisecondsPerDay = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND;
78
+ const cutoff = Date.now() - store.policy.retentionDays * millisecondsPerDay;
79
+ store.snapshots = store.snapshots.filter((snapshot) => new Date(snapshot.createdAt).getTime() >= cutoff);
80
+ }
81
+ export function saveStore(storeFile, store) {
82
+ writeFileSync(storeFile, JSON.stringify(store, null, 2), 'utf8');
83
+ }
84
+ export function loadStoreInternal(storeFile, policy) {
85
+ ensureStoreFile(storeFile, policy);
86
+ const raw = readFileSync(storeFile, 'utf8');
87
+ const parsed = JSON.parse(raw);
88
+ const merged = createEmptyStore(parsed.policy);
89
+ merged.version = parsed.version ?? STORE_VERSION;
90
+ merged.users = parsed.users ?? {};
91
+ merged.organizations = parsed.organizations ?? {};
92
+ merged.workspaces = parsed.workspaces ?? {};
93
+ merged.memberships = parsed.memberships ?? {};
94
+ merged.repos = parsed.repos ?? {};
95
+ merged.snapshots = parsed.snapshots ?? [];
96
+ merged.planChanges = parsed.planChanges ?? [];
97
+ merged.policy = mergePolicy(policy, merged.policy);
98
+ applyStoreEntityDefaults(merged);
99
+ hydrateOrganizationsFromWorkspaces(merged);
100
+ applyRetentionPolicy(merged);
101
+ return merged;
102
+ }
103
+ function resolveActorRole(store, organizationId, actorUserId, workspaceId) {
104
+ if (workspaceId) {
105
+ const scopedMembershipId = membershipKey(organizationId, workspaceId, actorUserId);
106
+ return store.memberships[scopedMembershipId]?.role;
107
+ }
108
+ let highestRole;
109
+ for (const membership of Object.values(store.memberships)) {
110
+ if (membership.organizationId !== organizationId)
111
+ continue;
112
+ if (membership.userId !== actorUserId)
113
+ continue;
114
+ if (!highestRole || ROLE_PRIORITY[membership.role] > ROLE_PRIORITY[highestRole])
115
+ highestRole = membership.role;
116
+ if (highestRole === 'owner')
117
+ break;
118
+ }
119
+ return highestRole;
120
+ }
121
+ export function assertPermissionInStore(store, context) {
122
+ const requiredRole = REQUIRED_ROLE_BY_OPERATION[context.operation];
123
+ if (!context.actorUserId) {
124
+ if (store.policy.strictActorEnforcement)
125
+ throw new SaasActorRequiredError(context);
126
+ return { requiredRole };
127
+ }
128
+ const actorRole = resolveActorRole(store, context.organizationId, context.actorUserId, context.workspaceId);
129
+ if (!hasRoleAtLeast(actorRole, requiredRole)) {
130
+ throw new SaasPermissionError(context, requiredRole, actorRole);
131
+ }
132
+ return { requiredRole, actorRole };
133
+ }
134
+ export function getRequiredRoleForOperation(operation) {
135
+ return REQUIRED_ROLE_BY_OPERATION[operation];
136
+ }
137
+ export function assertSaasPermission(context) {
138
+ const storeFile = resolve(context.storeFile ?? defaultSaasStorePath());
139
+ const store = loadStoreInternal(storeFile, context.policy);
140
+ return assertPermissionInStore(store, context);
141
+ }
142
+ export function getSaasEffectiveLimits(input) {
143
+ const policy = resolveSaasPolicy(input.policy);
144
+ const plan = normalizePlan(input.plan);
145
+ return {
146
+ plan,
147
+ maxWorkspaces: policy.maxWorkspacesPerOrganizationByPlan[plan],
148
+ maxReposPerWorkspace: policy.maxReposPerWorkspace,
149
+ maxRunsPerWorkspacePerMonth: policy.maxRunsPerWorkspacePerMonth,
150
+ retentionDays: policy.retentionDays,
151
+ };
152
+ }
153
+ export function getOrganizationEffectiveLimits(options) {
154
+ const storeFile = resolve(options.storeFile ?? defaultSaasStorePath());
155
+ const store = loadStoreInternal(storeFile, options.policy);
156
+ const plan = normalizePlan(store.organizations[options.organizationId]?.plan);
157
+ return getSaasEffectiveLimits({ plan, policy: store.policy });
158
+ }
159
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1,191 @@
1
+ import type { DriftConfig, DriftReport } from '../types.js';
2
+ export interface SaasPolicy {
3
+ freeUserThreshold: number;
4
+ maxRunsPerWorkspacePerMonth: number;
5
+ maxReposPerWorkspace: number;
6
+ retentionDays: number;
7
+ strictActorEnforcement: boolean;
8
+ maxWorkspacesPerOrganizationByPlan: Record<SaasPlan, number>;
9
+ }
10
+ export type SaasRole = 'owner' | 'member' | 'viewer';
11
+ export type SaasPlan = 'free' | 'sponsor' | 'team' | 'business';
12
+ export interface SaasUser {
13
+ id: string;
14
+ createdAt: string;
15
+ lastSeenAt: string;
16
+ }
17
+ export interface SaasOrganization {
18
+ id: string;
19
+ plan: SaasPlan;
20
+ createdAt: string;
21
+ lastSeenAt: string;
22
+ workspaceIds: string[];
23
+ }
24
+ export interface SaasWorkspace {
25
+ id: string;
26
+ organizationId: string;
27
+ createdAt: string;
28
+ lastSeenAt: string;
29
+ userIds: string[];
30
+ repoIds: string[];
31
+ }
32
+ export interface SaasRepo {
33
+ id: string;
34
+ organizationId: string;
35
+ workspaceId: string;
36
+ name: string;
37
+ createdAt: string;
38
+ lastSeenAt: string;
39
+ }
40
+ export interface SaasMembership {
41
+ id: string;
42
+ organizationId: string;
43
+ workspaceId: string;
44
+ userId: string;
45
+ role: SaasRole;
46
+ createdAt: string;
47
+ lastSeenAt: string;
48
+ }
49
+ export interface SaasPlanChange {
50
+ id: string;
51
+ organizationId: string;
52
+ fromPlan: SaasPlan;
53
+ toPlan: SaasPlan;
54
+ changedAt: string;
55
+ changedByUserId: string;
56
+ reason?: string;
57
+ }
58
+ export interface SaasSnapshot {
59
+ id: string;
60
+ createdAt: string;
61
+ scannedAt: string;
62
+ organizationId: string;
63
+ workspaceId: string;
64
+ userId: string;
65
+ role: SaasRole;
66
+ plan: SaasPlan;
67
+ repoId: string;
68
+ repoName: string;
69
+ targetPath: string;
70
+ totalScore: number;
71
+ totalIssues: number;
72
+ totalFiles: number;
73
+ summary: {
74
+ errors: number;
75
+ warnings: number;
76
+ infos: number;
77
+ };
78
+ }
79
+ export interface SaasStore {
80
+ version: number;
81
+ policy: SaasPolicy;
82
+ users: Record<string, SaasUser>;
83
+ organizations: Record<string, SaasOrganization>;
84
+ workspaces: Record<string, SaasWorkspace>;
85
+ memberships: Record<string, SaasMembership>;
86
+ repos: Record<string, SaasRepo>;
87
+ snapshots: SaasSnapshot[];
88
+ planChanges: SaasPlanChange[];
89
+ }
90
+ export type SaasOperation = 'snapshot:write' | 'snapshot:read' | 'summary:read' | 'billing:write' | 'billing:read';
91
+ export interface SaasPermissionContext {
92
+ operation: SaasOperation;
93
+ organizationId: string;
94
+ workspaceId?: string;
95
+ actorUserId?: string;
96
+ }
97
+ export interface SaasPermissionResult {
98
+ actorRole?: SaasRole;
99
+ requiredRole: SaasRole;
100
+ }
101
+ export interface SaasEffectiveLimits {
102
+ plan: SaasPlan;
103
+ maxWorkspaces: number;
104
+ maxReposPerWorkspace: number;
105
+ maxRunsPerWorkspacePerMonth: number;
106
+ retentionDays: number;
107
+ }
108
+ export interface SaasOrganizationUsageSnapshot {
109
+ organizationId: string;
110
+ plan: SaasPlan;
111
+ capturedAt: string;
112
+ workspaceCount: number;
113
+ repoCount: number;
114
+ runCount: number;
115
+ runCountThisMonth: number;
116
+ }
117
+ export interface ChangeOrganizationPlanOptions {
118
+ organizationId: string;
119
+ actorUserId: string;
120
+ newPlan: SaasPlan;
121
+ reason?: string;
122
+ storeFile?: string;
123
+ policy?: SaasPolicyOverrides;
124
+ }
125
+ export interface SaasUsageQueryOptions {
126
+ organizationId: string;
127
+ month?: string;
128
+ storeFile?: string;
129
+ policy?: SaasPolicyOverrides;
130
+ actorUserId?: string;
131
+ }
132
+ export interface SaasPlanChangeQueryOptions {
133
+ organizationId: string;
134
+ storeFile?: string;
135
+ policy?: SaasPolicyOverrides;
136
+ actorUserId?: string;
137
+ }
138
+ export interface SaasSummary {
139
+ policy: SaasPolicy;
140
+ usersRegistered: number;
141
+ workspacesActive: number;
142
+ reposActive: number;
143
+ runsPerMonth: Record<string, number>;
144
+ totalSnapshots: number;
145
+ phase: 'free' | 'paid';
146
+ thresholdReached: boolean;
147
+ freeUsersRemaining: number;
148
+ }
149
+ export interface SaasPolicyOverrides {
150
+ freeUserThreshold?: number;
151
+ maxRunsPerWorkspacePerMonth?: number;
152
+ maxReposPerWorkspace?: number;
153
+ retentionDays?: number;
154
+ strictActorEnforcement?: boolean;
155
+ maxWorkspacesPerOrganizationByPlan?: Partial<Record<SaasPlan, number>>;
156
+ }
157
+ export interface SaasQueryOptions {
158
+ storeFile?: string;
159
+ policy?: SaasPolicyOverrides;
160
+ organizationId?: string;
161
+ workspaceId?: string;
162
+ actorUserId?: string;
163
+ }
164
+ export interface IngestOptions {
165
+ organizationId?: string;
166
+ workspaceId: string;
167
+ userId: string;
168
+ role?: SaasRole;
169
+ plan?: SaasPlan;
170
+ repoName?: string;
171
+ actorUserId?: string;
172
+ storeFile?: string;
173
+ policy?: SaasPolicyOverrides;
174
+ }
175
+ export interface ScopedIdentity {
176
+ organizationId: string;
177
+ workspaceId: string;
178
+ workspaceKey: string;
179
+ repoName: string;
180
+ repoId: string;
181
+ }
182
+ export interface IngestMutationContext {
183
+ store: SaasStore;
184
+ scoped: ScopedIdentity;
185
+ options: IngestOptions;
186
+ nowIso: string;
187
+ requestedPlan: SaasPlan;
188
+ }
189
+ export type SaasPolicyInput = SaasPolicyOverrides | DriftConfig['saas'] | undefined;
190
+ export type DriftReportInput = DriftReport;
191
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
package/dist/saas.d.ts CHANGED
@@ -1,83 +1,9 @@
1
- import type { DriftReport, DriftConfig } from './types.js';
2
- export interface SaasPolicy {
3
- freeUserThreshold: number;
4
- maxRunsPerWorkspacePerMonth: number;
5
- maxReposPerWorkspace: number;
6
- retentionDays: number;
7
- }
8
- export interface SaasUser {
9
- id: string;
10
- createdAt: string;
11
- lastSeenAt: string;
12
- }
13
- export interface SaasWorkspace {
14
- id: string;
15
- createdAt: string;
16
- lastSeenAt: string;
17
- userIds: string[];
18
- repoIds: string[];
19
- }
20
- export interface SaasRepo {
21
- id: string;
22
- workspaceId: string;
23
- name: string;
24
- createdAt: string;
25
- lastSeenAt: string;
26
- }
27
- export interface SaasSnapshot {
28
- id: string;
29
- createdAt: string;
30
- scannedAt: string;
31
- workspaceId: string;
32
- userId: string;
33
- repoId: string;
34
- repoName: string;
35
- targetPath: string;
36
- totalScore: number;
37
- totalIssues: number;
38
- totalFiles: number;
39
- summary: {
40
- errors: number;
41
- warnings: number;
42
- infos: number;
43
- };
44
- }
45
- export interface SaasStore {
46
- version: number;
47
- policy: SaasPolicy;
48
- users: Record<string, SaasUser>;
49
- workspaces: Record<string, SaasWorkspace>;
50
- repos: Record<string, SaasRepo>;
51
- snapshots: SaasSnapshot[];
52
- }
53
- export interface SaasSummary {
54
- policy: SaasPolicy;
55
- usersRegistered: number;
56
- workspacesActive: number;
57
- reposActive: number;
58
- runsPerMonth: Record<string, number>;
59
- totalSnapshots: number;
60
- phase: 'free' | 'paid';
61
- thresholdReached: boolean;
62
- freeUsersRemaining: number;
63
- }
64
- export interface IngestOptions {
65
- workspaceId: string;
66
- userId: string;
67
- repoName?: string;
68
- storeFile?: string;
69
- policy?: Partial<SaasPolicy>;
70
- }
71
- export declare const DEFAULT_SAAS_POLICY: SaasPolicy;
72
- export declare function resolveSaasPolicy(policy?: Partial<SaasPolicy> | DriftConfig['saas']): SaasPolicy;
73
- export declare function defaultSaasStorePath(root?: string): string;
74
- export declare function ingestSnapshotFromReport(report: DriftReport, options: IngestOptions): SaasSnapshot;
75
- export declare function getSaasSummary(options?: {
76
- storeFile?: string;
77
- policy?: Partial<SaasPolicy>;
78
- }): SaasSummary;
79
- export declare function generateSaasDashboardHtml(options?: {
80
- storeFile?: string;
81
- policy?: Partial<SaasPolicy>;
82
- }): string;
1
+ export { DEFAULT_SAAS_POLICY } from './saas/constants.js';
2
+ export { SaasActorRequiredError, SaasPermissionError } from './saas/errors.js';
3
+ export { resolveSaasPolicy, } from './saas/helpers.js';
4
+ export { defaultSaasStorePath, getRequiredRoleForOperation, assertSaasPermission, getSaasEffectiveLimits, getOrganizationEffectiveLimits, } from './saas/store.js';
5
+ export { ingestSnapshotFromReport } from './saas/ingest.js';
6
+ export { changeOrganizationPlan, listOrganizationPlanChanges, getOrganizationUsageSnapshot, } from './saas/organization.js';
7
+ export { listSaasSnapshots, getSaasSummary, generateSaasDashboardHtml, } from './saas/dashboard.js';
8
+ export type { SaasUser, SaasOrganization, SaasWorkspace, SaasRepo, SaasMembership, SaasRole, SaasPlan, SaasPolicy, SaasPolicyOverrides, SaasStore, SaasSummary, SaasSnapshot, SaasQueryOptions, IngestOptions, SaasPlanChange, SaasOperation, SaasPermissionContext, SaasPermissionResult, SaasEffectiveLimits, SaasOrganizationUsageSnapshot, ChangeOrganizationPlanOptions, SaasUsageQueryOptions, SaasPlanChangeQueryOptions, } from './saas/types.js';
83
9
  //# sourceMappingURL=saas.d.ts.map