@selvajs/server 0.2.0-beta.1 → 0.2.0-beta.2

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 (141) hide show
  1. package/dist/access/__tests__/project-access-input.test.d.ts +2 -0
  2. package/dist/access/__tests__/project-access-input.test.d.ts.map +1 -0
  3. package/dist/access/__tests__/project-access-input.test.js +146 -0
  4. package/dist/access/__tests__/project-access-input.test.js.map +1 -0
  5. package/dist/access/index.d.ts +2 -0
  6. package/dist/access/index.d.ts.map +1 -0
  7. package/dist/access/index.js +4 -0
  8. package/dist/access/index.js.map +1 -0
  9. package/dist/access/project-access-input.d.ts +52 -0
  10. package/dist/access/project-access-input.d.ts.map +1 -0
  11. package/dist/access/project-access-input.js +56 -0
  12. package/dist/access/project-access-input.js.map +1 -0
  13. package/dist/compute/__tests__/client-cache.test.js +52 -1
  14. package/dist/compute/__tests__/client-cache.test.js.map +1 -1
  15. package/dist/compute/__tests__/rate-limit.test.js +36 -0
  16. package/dist/compute/__tests__/rate-limit.test.js.map +1 -1
  17. package/dist/compute/__tests__/solve-pipeline.test.js +60 -13
  18. package/dist/compute/__tests__/solve-pipeline.test.js.map +1 -1
  19. package/dist/compute/__tests__/transform-input.test.js +7 -1
  20. package/dist/compute/__tests__/transform-input.test.js.map +1 -1
  21. package/dist/compute/client-cache.d.ts +20 -8
  22. package/dist/compute/client-cache.d.ts.map +1 -1
  23. package/dist/compute/client-cache.js +12 -3
  24. package/dist/compute/client-cache.js.map +1 -1
  25. package/dist/compute/limits.d.ts +10 -0
  26. package/dist/compute/limits.d.ts.map +1 -1
  27. package/dist/compute/limits.js +3 -1
  28. package/dist/compute/limits.js.map +1 -1
  29. package/dist/compute/rate-limit.d.ts +12 -0
  30. package/dist/compute/rate-limit.d.ts.map +1 -1
  31. package/dist/compute/rate-limit.js +18 -0
  32. package/dist/compute/rate-limit.js.map +1 -1
  33. package/dist/compute/solve-pipeline.d.ts.map +1 -1
  34. package/dist/compute/solve-pipeline.js +20 -7
  35. package/dist/compute/solve-pipeline.js.map +1 -1
  36. package/dist/compute/transform-input.d.ts.map +1 -1
  37. package/dist/compute/transform-input.js +21 -6
  38. package/dist/compute/transform-input.js.map +1 -1
  39. package/dist/errors/SentryErrorReporter.d.ts +17 -0
  40. package/dist/errors/SentryErrorReporter.d.ts.map +1 -0
  41. package/dist/errors/SentryErrorReporter.js +60 -0
  42. package/dist/errors/SentryErrorReporter.js.map +1 -0
  43. package/dist/errors/index.d.ts +2 -0
  44. package/dist/errors/index.d.ts.map +1 -0
  45. package/dist/errors/index.js +3 -0
  46. package/dist/errors/index.js.map +1 -0
  47. package/dist/http/__tests__/body-size.test.d.ts +2 -0
  48. package/dist/http/__tests__/body-size.test.d.ts.map +1 -0
  49. package/dist/http/__tests__/body-size.test.js +24 -0
  50. package/dist/http/__tests__/body-size.test.js.map +1 -0
  51. package/dist/http/__tests__/route-classifier.test.d.ts +2 -0
  52. package/dist/http/__tests__/route-classifier.test.d.ts.map +1 -0
  53. package/dist/http/__tests__/route-classifier.test.js +54 -0
  54. package/dist/http/__tests__/route-classifier.test.js.map +1 -0
  55. package/dist/http/__tests__/safe-redirect.test.d.ts +2 -0
  56. package/dist/http/__tests__/safe-redirect.test.d.ts.map +1 -0
  57. package/dist/http/__tests__/safe-redirect.test.js +44 -0
  58. package/dist/http/__tests__/safe-redirect.test.js.map +1 -0
  59. package/dist/http/__tests__/security-headers.test.d.ts +2 -0
  60. package/dist/http/__tests__/security-headers.test.d.ts.map +1 -0
  61. package/dist/http/__tests__/security-headers.test.js +24 -0
  62. package/dist/http/__tests__/security-headers.test.js.map +1 -0
  63. package/dist/http/body-size.d.ts +21 -0
  64. package/dist/http/body-size.d.ts.map +1 -0
  65. package/dist/http/body-size.js +20 -0
  66. package/dist/http/body-size.js.map +1 -0
  67. package/dist/http/index.d.ts +5 -0
  68. package/dist/http/index.d.ts.map +1 -0
  69. package/dist/http/index.js +6 -0
  70. package/dist/http/index.js.map +1 -0
  71. package/dist/http/redirect.d.ts +11 -0
  72. package/dist/http/redirect.d.ts.map +1 -0
  73. package/dist/http/redirect.js +20 -0
  74. package/dist/http/redirect.js.map +1 -0
  75. package/dist/http/route-classifier.d.ts +47 -0
  76. package/dist/http/route-classifier.d.ts.map +1 -0
  77. package/dist/http/route-classifier.js +37 -0
  78. package/dist/http/route-classifier.js.map +1 -0
  79. package/dist/http/security-headers.d.ts +31 -0
  80. package/dist/http/security-headers.d.ts.map +1 -0
  81. package/dist/http/security-headers.js +31 -0
  82. package/dist/http/security-headers.js.map +1 -0
  83. package/dist/index.d.ts +11 -2
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +11 -2
  86. package/dist/index.js.map +1 -1
  87. package/dist/ops/__tests__/semver.test.d.ts +2 -0
  88. package/dist/ops/__tests__/semver.test.d.ts.map +1 -0
  89. package/dist/ops/__tests__/semver.test.js +38 -0
  90. package/dist/ops/__tests__/semver.test.js.map +1 -0
  91. package/dist/ops/index.d.ts +2 -0
  92. package/dist/ops/index.d.ts.map +1 -0
  93. package/dist/ops/index.js +3 -0
  94. package/dist/ops/index.js.map +1 -0
  95. package/dist/ops/semver.d.ts +35 -0
  96. package/dist/ops/semver.d.ts.map +1 -0
  97. package/dist/ops/semver.js +55 -0
  98. package/dist/ops/semver.js.map +1 -0
  99. package/dist/tokens/__tests__/token-codec.test.d.ts +2 -0
  100. package/dist/tokens/__tests__/token-codec.test.d.ts.map +1 -0
  101. package/dist/tokens/__tests__/token-codec.test.js +50 -0
  102. package/dist/tokens/__tests__/token-codec.test.js.map +1 -0
  103. package/dist/tokens/index.d.ts +2 -0
  104. package/dist/tokens/index.d.ts.map +1 -0
  105. package/dist/tokens/index.js +3 -0
  106. package/dist/tokens/index.js.map +1 -0
  107. package/dist/tokens/token-codec.d.ts +54 -0
  108. package/dist/tokens/token-codec.d.ts.map +1 -0
  109. package/dist/tokens/token-codec.js +62 -0
  110. package/dist/tokens/token-codec.js.map +1 -0
  111. package/package.json +35 -2
  112. package/src/access/__tests__/project-access-input.test.ts +175 -0
  113. package/src/access/index.ts +9 -0
  114. package/src/access/project-access-input.ts +145 -0
  115. package/src/compute/__tests__/client-cache.test.ts +61 -1
  116. package/src/compute/__tests__/rate-limit.test.ts +37 -0
  117. package/src/compute/__tests__/solve-pipeline.test.ts +60 -13
  118. package/src/compute/__tests__/transform-input.test.ts +8 -1
  119. package/src/compute/client-cache.ts +35 -13
  120. package/src/compute/limits.ts +13 -1
  121. package/src/compute/rate-limit.ts +27 -0
  122. package/src/compute/solve-pipeline.ts +22 -7
  123. package/src/compute/transform-input.ts +20 -6
  124. package/src/errors/SentryErrorReporter.ts +88 -0
  125. package/src/errors/index.ts +3 -0
  126. package/src/http/__tests__/body-size.test.ts +27 -0
  127. package/src/http/__tests__/route-classifier.test.ts +60 -0
  128. package/src/http/__tests__/safe-redirect.test.ts +47 -0
  129. package/src/http/__tests__/security-headers.test.ts +29 -0
  130. package/src/http/body-size.ts +24 -0
  131. package/src/http/index.ts +10 -0
  132. package/src/http/redirect.ts +16 -0
  133. package/src/http/route-classifier.ts +74 -0
  134. package/src/http/security-headers.ts +42 -0
  135. package/src/index.ts +11 -2
  136. package/src/ops/__tests__/semver.test.ts +42 -0
  137. package/src/ops/index.ts +3 -0
  138. package/src/ops/semver.ts +60 -0
  139. package/src/tokens/__tests__/token-codec.test.ts +63 -0
  140. package/src/tokens/index.ts +8 -0
  141. package/src/tokens/token-codec.ts +86 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=project-access-input.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-access-input.test.d.ts","sourceRoot":"","sources":["../../../src/access/__tests__/project-access-input.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,146 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { createProjectAccessInputBuilder } from '../project-access-input.js';
3
+ const CTX = {
4
+ userId: 'user-1',
5
+ actingOrgId: 'org-1',
6
+ platformPermissions: ['manage_compute'],
7
+ orgPermissions: ['manage_org_members']
8
+ };
9
+ function project(visibility) {
10
+ return {
11
+ id: 'proj-1',
12
+ orgId: 'org-1',
13
+ name: 'P',
14
+ slug: 'p',
15
+ visibility,
16
+ ownerId: 'user-9',
17
+ createdBy: 'user-9',
18
+ updatedBy: 'user-9',
19
+ autoJoinOnUpload: false,
20
+ createdAt: '2026-01-01T00:00:00Z',
21
+ updatedAt: '2026-01-01T00:00:00Z'
22
+ };
23
+ }
24
+ // Row identity is all the builder consumes — it passes rows through opaquely.
25
+ const MEMBER = {
26
+ projectId: 'proj-1',
27
+ userId: 'user-1',
28
+ role: 'editor'
29
+ };
30
+ const ORG_MEMBER = {
31
+ orgId: 'org-1',
32
+ userId: 'user-1',
33
+ role: 'member'
34
+ };
35
+ const GRANT = {
36
+ projectId: 'proj-1',
37
+ orgId: 'org-2',
38
+ canSolve: true
39
+ };
40
+ function makeDeps(flags = {}) {
41
+ return {
42
+ getProjectMember: vi.fn().mockResolvedValue(MEMBER),
43
+ getOrgMember: vi.fn().mockResolvedValue(ORG_MEMBER),
44
+ listPlatformGrants: vi.fn().mockResolvedValue([GRANT]),
45
+ flags: vi.fn().mockReturnValue({
46
+ allowCrossOrgPublic: false,
47
+ enablePlatformProjects: false,
48
+ ...flags
49
+ })
50
+ };
51
+ }
52
+ describe('buildProjectAccessInput — per-visibility row fetching', () => {
53
+ it('private: fetches only the project member row', async () => {
54
+ const deps = makeDeps();
55
+ const builder = createProjectAccessInputBuilder(deps);
56
+ const input = await builder.buildProjectAccessInput(CTX, project('private'));
57
+ expect(input.member).toEqual(MEMBER);
58
+ expect(input.orgMember).toBeNull();
59
+ expect(input.platformGrants).toEqual([]);
60
+ expect(deps.getOrgMember).not.toHaveBeenCalled();
61
+ expect(deps.listPlatformGrants).not.toHaveBeenCalled();
62
+ });
63
+ it.each(['org', 'public'])('%s: fetches project member + org member', async (vis) => {
64
+ const deps = makeDeps();
65
+ const builder = createProjectAccessInputBuilder(deps);
66
+ const input = await builder.buildProjectAccessInput(CTX, project(vis));
67
+ expect(input.member).toEqual(MEMBER);
68
+ expect(input.orgMember).toEqual(ORG_MEMBER);
69
+ expect(deps.getOrgMember).toHaveBeenCalledWith(CTX, 'org-1', 'user-1');
70
+ });
71
+ it('public + ALLOW_CROSS_ORG_PUBLIC: skips the org member row', async () => {
72
+ const deps = makeDeps({ allowCrossOrgPublic: true });
73
+ const builder = createProjectAccessInputBuilder(deps);
74
+ const input = await builder.buildProjectAccessInput(CTX, project('public'));
75
+ expect(input.member).toEqual(MEMBER);
76
+ expect(input.orgMember).toBeNull();
77
+ expect(deps.getOrgMember).not.toHaveBeenCalled();
78
+ });
79
+ it('org visibility still fetches the org row when cross-org public is on', async () => {
80
+ const deps = makeDeps({ allowCrossOrgPublic: true });
81
+ const builder = createProjectAccessInputBuilder(deps);
82
+ const input = await builder.buildProjectAccessInput(CTX, project('org'));
83
+ expect(input.orgMember).toEqual(ORG_MEMBER);
84
+ });
85
+ it('platform + flag on: fetches grants only', async () => {
86
+ const deps = makeDeps({ enablePlatformProjects: true });
87
+ const builder = createProjectAccessInputBuilder(deps);
88
+ const input = await builder.buildProjectAccessInput(CTX, project('platform'));
89
+ expect(input.platformGrants).toEqual([GRANT]);
90
+ expect(input.member).toBeNull();
91
+ expect(deps.getProjectMember).not.toHaveBeenCalled();
92
+ expect(deps.getOrgMember).not.toHaveBeenCalled();
93
+ });
94
+ it('platform + flag off: skips the grant lookup entirely (feature disabled stays cheap)', async () => {
95
+ const deps = makeDeps({ enablePlatformProjects: false });
96
+ const builder = createProjectAccessInputBuilder(deps);
97
+ const input = await builder.buildProjectAccessInput(CTX, project('platform'));
98
+ expect(input.platformGrants).toEqual([]);
99
+ expect(deps.listPlatformGrants).not.toHaveBeenCalled();
100
+ });
101
+ it('threads ctx fields and flags into the input, with overrides winning', async () => {
102
+ const deps = makeDeps({ allowCrossOrgPublic: true });
103
+ const builder = createProjectAccessInputBuilder(deps);
104
+ const input = await builder.buildProjectAccessInput(CTX, project('private'), {
105
+ member: null
106
+ });
107
+ expect(input.userId).toBe('user-1');
108
+ expect(input.actingOrgId).toBe('org-1');
109
+ expect(input.orgPermissions).toEqual(CTX.orgPermissions);
110
+ expect(input.platformPermissions).toEqual(CTX.platformPermissions);
111
+ expect(input.allowCrossOrgPublic).toBe(true);
112
+ expect(input.member).toBeNull(); // override beat the fetched row
113
+ });
114
+ it('reads flags per call, not at builder creation', async () => {
115
+ const deps = makeDeps();
116
+ const builder = createProjectAccessInputBuilder(deps);
117
+ await builder.buildProjectAccessInput(CTX, project('private'));
118
+ deps.flags.mockReturnValue({ allowCrossOrgPublic: true, enablePlatformProjects: true });
119
+ const input = await builder.buildProjectAccessInput(CTX, project('private'));
120
+ expect(input.enablePlatformProjects).toBe(true);
121
+ });
122
+ });
123
+ describe('projectAccessInputFromRows', () => {
124
+ it('assembles from caller rows with zero I/O', () => {
125
+ const deps = makeDeps();
126
+ const builder = createProjectAccessInputBuilder(deps);
127
+ const input = builder.projectAccessInputFromRows(CTX, project('org'), {
128
+ member: MEMBER,
129
+ orgMember: ORG_MEMBER
130
+ });
131
+ expect(input.member).toEqual(MEMBER);
132
+ expect(input.orgMember).toEqual(ORG_MEMBER);
133
+ expect(input.platformGrants).toEqual([]);
134
+ expect(deps.getProjectMember).not.toHaveBeenCalled();
135
+ expect(deps.getOrgMember).not.toHaveBeenCalled();
136
+ expect(deps.listPlatformGrants).not.toHaveBeenCalled();
137
+ });
138
+ it('defaults omitted rows to null/empty', () => {
139
+ const builder = createProjectAccessInputBuilder(makeDeps());
140
+ const input = builder.projectAccessInputFromRows(CTX, project('private'), {});
141
+ expect(input.member).toBeNull();
142
+ expect(input.orgMember).toBeNull();
143
+ expect(input.platformGrants).toEqual([]);
144
+ });
145
+ });
146
+ //# sourceMappingURL=project-access-input.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-access-input.test.js","sourceRoot":"","sources":["../../../src/access/__tests__/project-access-input.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { createProjectAccessInputBuilder, type ProjectAccessFlags, type ProjectAccessInputBuilder, type ProjectAccessInputDeps } from './project-access-input.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/access/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACN,+BAA+B,EAC/B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,4 @@
1
+ // Access-rule input assembly — marshals providers + flags into what
2
+ // platform's pure canView/canEdit/… rules consume.
3
+ export { createProjectAccessInputBuilder } from './project-access-input.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/access/index.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,mDAAmD;AAEnD,OAAO,EACN,+BAA+B,EAI/B,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { Project, ProjectAccessInput, RequestContext } from '@selvajs/platform';
2
+ /**
3
+ * Assembly of `ProjectAccessInput` — the marshalling layer between an app's
4
+ * providers and platform's pure access rules (`canView`/`canEdit`/`canSolve`/
5
+ * `canManage`/…). The rules are pure functions over one input shape; the
6
+ * knowledge captured here is **which rows each project visibility needs**, so
7
+ * a consuming app doesn't reimplement (and mis-fetch) it:
8
+ *
9
+ * - `platform` → grants (skipped entirely while the feature flag is off)
10
+ * - `private` → caller's project member row
11
+ * - `org` / `public` → caller's project member row (for canEdit/canManage) and
12
+ * org member row (for canView). Cross-org public skips the org row.
13
+ *
14
+ * Wiring is injected: row lookups as functions (so lazily-initialized
15
+ * providers work unchanged) and flags read per call (so runtime flag flips
16
+ * behave like the app's own `flag()` reads).
17
+ */
18
+ export interface ProjectAccessFlags {
19
+ /** `ALLOW_CROSS_ORG_PUBLIC` — public visibility spans orgs when true. */
20
+ allowCrossOrgPublic: boolean;
21
+ /** `ENABLE_PLATFORM_PROJECTS` — platform visibility is live when true. */
22
+ enablePlatformProjects: boolean;
23
+ }
24
+ export interface ProjectAccessInputDeps {
25
+ getProjectMember(ctx: RequestContext, projectId: string, userId: string): Promise<ProjectAccessInput['member']>;
26
+ getOrgMember(ctx: RequestContext, orgId: string, userId: string): Promise<ProjectAccessInput['orgMember']>;
27
+ listPlatformGrants(ctx: RequestContext, projectId: string): Promise<ProjectAccessInput['platformGrants']>;
28
+ /** Read the two access-relevant feature flags. Called once per assembly. */
29
+ flags(): ProjectAccessFlags;
30
+ }
31
+ export interface ProjectAccessInputBuilder {
32
+ /**
33
+ * Build the rule input for any project-scope rule, fetching exactly the
34
+ * rows the rule will consult based on `project.visibility`. Other fields
35
+ * default to safe values; pass `overrides` for the rare callers that
36
+ * already loaded a row (e.g. tests, batched listing pages).
37
+ */
38
+ buildProjectAccessInput(ctx: RequestContext, project: Project, overrides?: Partial<ProjectAccessInput>): Promise<ProjectAccessInput>;
39
+ /**
40
+ * Build a `ProjectAccessInput` from caller-provided rows without any I/O.
41
+ * Used by listing pages that have already batch-loaded membership for many
42
+ * projects; the per-row predicate calls this instead of
43
+ * `buildProjectAccessInput` to avoid an N+1 fetch.
44
+ */
45
+ projectAccessInputFromRows(ctx: RequestContext, project: Project, rows: {
46
+ member?: ProjectAccessInput['member'];
47
+ orgMember?: ProjectAccessInput['orgMember'];
48
+ platformGrants?: ProjectAccessInput['platformGrants'];
49
+ }): ProjectAccessInput;
50
+ }
51
+ export declare function createProjectAccessInputBuilder(deps: ProjectAccessInputDeps): ProjectAccessInputBuilder;
52
+ //# sourceMappingURL=project-access-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-access-input.d.ts","sourceRoot":"","sources":["../../src/access/project-access-input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAErF;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,kBAAkB;IAClC,yEAAyE;IACzE,mBAAmB,EAAE,OAAO,CAAC;IAC7B,0EAA0E;IAC1E,sBAAsB,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,WAAW,sBAAsB;IACtC,gBAAgB,CACf,GAAG,EAAE,cAAc,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzC,YAAY,CACX,GAAG,EAAE,cAAc,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC,CAAC;IAC5C,kBAAkB,CACjB,GAAG,EAAE,cAAc,EACnB,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACjD,4EAA4E;IAC5E,KAAK,IAAI,kBAAkB,CAAC;CAC5B;AAED,MAAM,WAAW,yBAAyB;IACzC;;;;;OAKG;IACH,uBAAuB,CACtB,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GACrC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC/B;;;;;OAKG;IACH,0BAA0B,CACzB,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE;QACL,MAAM,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QACtC,SAAS,CAAC,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC;QAC5C,cAAc,CAAC,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;KACtD,GACC,kBAAkB,CAAC;CACtB;AAED,wBAAgB,+BAA+B,CAC9C,IAAI,EAAE,sBAAsB,GAC1B,yBAAyB,CAoE3B"}
@@ -0,0 +1,56 @@
1
+ export function createProjectAccessInputBuilder(deps) {
2
+ return {
3
+ async buildProjectAccessInput(ctx, project, overrides = {}) {
4
+ const { allowCrossOrgPublic, enablePlatformProjects } = deps.flags();
5
+ let member = null;
6
+ let orgMember = null;
7
+ let platformGrants = [];
8
+ if (project.visibility === 'platform') {
9
+ // When the flag is off the rule short-circuits before reading grants —
10
+ // skip the lookup to keep "feature disabled" cheap.
11
+ if (enablePlatformProjects) {
12
+ platformGrants = await deps.listPlatformGrants(ctx, project.id);
13
+ }
14
+ }
15
+ else if (project.visibility === 'private') {
16
+ member = await deps.getProjectMember(ctx, project.id, ctx.userId);
17
+ }
18
+ else {
19
+ const skipOrgMember = project.visibility === 'public' && allowCrossOrgPublic;
20
+ [member, orgMember] = await Promise.all([
21
+ deps.getProjectMember(ctx, project.id, ctx.userId),
22
+ skipOrgMember ? Promise.resolve(null) : deps.getOrgMember(ctx, project.orgId, ctx.userId)
23
+ ]);
24
+ }
25
+ return {
26
+ orgPermissions: ctx.orgPermissions,
27
+ platformPermissions: ctx.platformPermissions,
28
+ project,
29
+ member,
30
+ orgMember,
31
+ allowCrossOrgPublic,
32
+ enablePlatformProjects,
33
+ platformGrants,
34
+ actingOrgId: ctx.actingOrgId ?? null,
35
+ userId: ctx.userId,
36
+ ...overrides
37
+ };
38
+ },
39
+ projectAccessInputFromRows(ctx, project, rows) {
40
+ const { allowCrossOrgPublic, enablePlatformProjects } = deps.flags();
41
+ return {
42
+ orgPermissions: ctx.orgPermissions,
43
+ platformPermissions: ctx.platformPermissions,
44
+ project,
45
+ member: rows.member ?? null,
46
+ orgMember: rows.orgMember ?? null,
47
+ allowCrossOrgPublic,
48
+ enablePlatformProjects,
49
+ platformGrants: rows.platformGrants ?? [],
50
+ actingOrgId: ctx.actingOrgId ?? null,
51
+ userId: ctx.userId
52
+ };
53
+ }
54
+ };
55
+ }
56
+ //# sourceMappingURL=project-access-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-access-input.js","sourceRoot":"","sources":["../../src/access/project-access-input.ts"],"names":[],"mappings":"AA0EA,MAAM,UAAU,+BAA+B,CAC9C,IAA4B;IAE5B,OAAO;QACN,KAAK,CAAC,uBAAuB,CAC5B,GAAmB,EACnB,OAAgB,EAChB,YAAyC,EAAE;YAE3C,MAAM,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YAErE,IAAI,MAAM,GAAiC,IAAI,CAAC;YAChD,IAAI,SAAS,GAAoC,IAAI,CAAC;YACtD,IAAI,cAAc,GAAyC,EAAE,CAAC;YAE9D,IAAI,OAAO,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;gBACvC,uEAAuE;gBACvE,oDAAoD;gBACpD,IAAI,sBAAsB,EAAE,CAAC;oBAC5B,cAAc,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;gBACjE,CAAC;YACF,CAAC;iBAAM,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC7C,MAAM,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YACnE,CAAC;iBAAM,CAAC;gBACP,MAAM,aAAa,GAAG,OAAO,CAAC,UAAU,KAAK,QAAQ,IAAI,mBAAmB,CAAC;gBAC7E,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;oBACvC,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC;oBAClD,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC;iBACzF,CAAC,CAAC;YACJ,CAAC;YAED,OAAO;gBACN,cAAc,EAAE,GAAG,CAAC,cAAc;gBAClC,mBAAmB,EAAE,GAAG,CAAC,mBAAmB;gBAC5C,OAAO;gBACP,MAAM;gBACN,SAAS;gBACT,mBAAmB;gBACnB,sBAAsB;gBACtB,cAAc;gBACd,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,IAAI;gBACpC,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,GAAG,SAAS;aACZ,CAAC;QACH,CAAC;QAED,0BAA0B,CACzB,GAAmB,EACnB,OAAgB,EAChB,IAIC;YAED,MAAM,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YACrE,OAAO;gBACN,cAAc,EAAE,GAAG,CAAC,cAAc;gBAClC,mBAAmB,EAAE,GAAG,CAAC,mBAAmB;gBAC5C,OAAO;gBACP,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;gBAC3B,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI;gBACjC,mBAAmB;gBACnB,sBAAsB;gBACtB,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,EAAE;gBACzC,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,IAAI;gBACpC,MAAM,EAAE,GAAG,CAAC,MAAM;aAClB,CAAC;QACH,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -10,6 +10,7 @@
10
10
  import { describe, it, expect, beforeEach, vi } from 'vitest';
11
11
  // --- Mock @selvajs/compute -------------------------------------------------
12
12
  const createdConfigs = [];
13
+ const createdSchedulerOptions = [];
13
14
  const disposedSchedulers = [];
14
15
  vi.mock('@selvajs/compute', () => {
15
16
  class GrasshopperClient {
@@ -21,7 +22,8 @@ vi.mock('@selvajs/compute', () => {
21
22
  constructor(config) {
22
23
  this.config = config;
23
24
  }
24
- createScheduler() {
25
+ createScheduler(options) {
26
+ createdSchedulerOptions.push(options);
25
27
  const scheduler = {
26
28
  disposed: false,
27
29
  dispose() {
@@ -38,6 +40,7 @@ import { createClientCache, serverIdentity } from '../client-cache.js';
38
40
  function baseConfig() {
39
41
  return {
40
42
  maxSolveDurationMs: 30_000,
43
+ maxConcurrentSolves: 4,
41
44
  cachesolve: true,
42
45
  cacheerroredsolves: false,
43
46
  reuseServerDefinitionCache: true,
@@ -52,6 +55,7 @@ const server = (id, over = {}) => ({
52
55
  });
53
56
  beforeEach(() => {
54
57
  createdConfigs.length = 0;
58
+ createdSchedulerOptions.length = 0;
55
59
  disposedSchedulers.length = 0;
56
60
  });
57
61
  describe('createClientCache — keying', () => {
@@ -141,6 +145,53 @@ describe('createClientCache — X-Selva-Definition (ADR 0004 D2)', () => {
141
145
  expect(createdConfigs[0].headers).toBeUndefined();
142
146
  });
143
147
  });
148
+ describe('createClientCache — scheduler concurrency (audit B6)', () => {
149
+ it('forwards maxConcurrentSolves as the scheduler maxConcurrent in queue mode', async () => {
150
+ const cache = createClientCache({ ...baseConfig(), maxConcurrentSolves: 7 });
151
+ await cache.getClient(server('s1'));
152
+ expect(createdSchedulerOptions).toHaveLength(1);
153
+ // Queue mode with NO maxConcurrent defaults to 1 in the scheduler,
154
+ // serializing every solve on the server — the option must always be set.
155
+ expect(createdSchedulerOptions[0].mode).toBe('queue');
156
+ expect(createdSchedulerOptions[0].maxConcurrent).toBe(7);
157
+ });
158
+ });
159
+ describe('createClientCache — per-request telemetry sequence counters', () => {
160
+ it('onServerTiming bumps rhinoTiming.seq on every write', async () => {
161
+ const cache = createClientCache(baseConfig());
162
+ const entry = await cache.getClient(server('s1'));
163
+ expect(entry.rhinoTiming).toEqual({ last: null, seq: 0 });
164
+ createdConfigs[0].onServerTiming({ decode: 5, solve: 100, encode: 3 });
165
+ expect(entry.rhinoTiming.seq).toBe(1);
166
+ expect(entry.rhinoTiming.last).toEqual({ decode: 5, solve: 100, encode: 3 });
167
+ createdConfigs[0].onServerTiming({ decode: 1, solve: 2, encode: 3 });
168
+ expect(entry.rhinoTiming.seq).toBe(2);
169
+ expect(entry.rhinoTiming.last).toEqual({ decode: 1, solve: 2, encode: 3 });
170
+ });
171
+ it('onSettle bumps solveMeta.seq on every settle but writes last only on success', async () => {
172
+ const cache = createClientCache(baseConfig());
173
+ const entry = await cache.getClient(server('s1'));
174
+ const onSettle = createdSchedulerOptions[0].onSettle;
175
+ expect(entry.solveMeta).toEqual({ last: null, seq: 0 });
176
+ onSettle({ key: 'k' }, {
177
+ status: 'success',
178
+ fromCache: false,
179
+ definitionReuploaded: false,
180
+ durationMs: 10,
181
+ response: {}
182
+ });
183
+ expect(entry.solveMeta.seq).toBe(1);
184
+ expect(entry.solveMeta.last).toEqual({ fromCache: false, definitionReuploaded: false });
185
+ // An error settle must still count toward seq (the attribution guard in
186
+ // the pipeline needs to see ALL concurrent activity) without clobbering last.
187
+ onSettle({ key: 'k' }, { status: 'error', error: new Error('x'), durationMs: 5 });
188
+ expect(entry.solveMeta.seq).toBe(2);
189
+ expect(entry.solveMeta.last).toEqual({ fromCache: false, definitionReuploaded: false });
190
+ onSettle({ key: 'k' }, { status: 'success', fromCache: true, durationMs: 0, response: {} });
191
+ expect(entry.solveMeta.seq).toBe(3);
192
+ expect(entry.solveMeta.last).toEqual({ fromCache: true, definitionReuploaded: undefined });
193
+ });
194
+ });
144
195
  describe('serverIdentity', () => {
145
196
  it('derives identity from the id', () => {
146
197
  expect(serverIdentity({ id: 'abc' })).toBe('abc');
@@ -1 +1 @@
1
- {"version":3,"file":"client-cache.test.js","sourceRoot":"","sources":["../../../src/compute/__tests__/client-cache.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client-cache.test.js","sourceRoot":"","sources":["../../../src/compute/__tests__/client-cache.test.ts"],"names":[],"mappings":"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"}
@@ -58,5 +58,41 @@ describe('createComputeRateLimiter', () => {
58
58
  // A different limiter instance is unaffected.
59
59
  expect(other.check('user:alice').allowed).toBe(true);
60
60
  });
61
+ // peek + clear support failure-counting flows (login limiting): peek gates
62
+ // the attempt without spending budget, check records a failure, clear
63
+ // forgives on success.
64
+ describe('peek / clear', () => {
65
+ it('peek never increments the counter', () => {
66
+ for (let i = 0; i < 1000; i++)
67
+ expect(limiter.peek('ip:1.2.3.4').allowed).toBe(true);
68
+ // Full budget still available after all those peeks.
69
+ for (let i = 0; i < MAX_PER_WINDOW; i++) {
70
+ expect(limiter.check('ip:1.2.3.4').allowed).toBe(true);
71
+ }
72
+ });
73
+ it('peek reports a full bucket with retryAfter, still without mutating it', () => {
74
+ for (let i = 0; i < MAX_PER_WINDOW; i++)
75
+ limiter.check('ip:1.2.3.4');
76
+ const result = limiter.peek('ip:1.2.3.4');
77
+ expect(result.allowed).toBe(false);
78
+ expect(result.retryAfter).toBeGreaterThan(0);
79
+ });
80
+ it('peek allows again once the window expires', () => {
81
+ for (let i = 0; i < MAX_PER_WINDOW; i++)
82
+ limiter.check('ip:1.2.3.4');
83
+ expect(limiter.peek('ip:1.2.3.4').allowed).toBe(false);
84
+ vi.advanceTimersByTime(WINDOW_MS + 1);
85
+ expect(limiter.peek('ip:1.2.3.4').allowed).toBe(true);
86
+ });
87
+ it('clear drops only the given key', () => {
88
+ for (let i = 0; i < MAX_PER_WINDOW; i++) {
89
+ limiter.check('ip:1.2.3.4');
90
+ limiter.check('ip:5.6.7.8');
91
+ }
92
+ limiter.clear('ip:1.2.3.4');
93
+ expect(limiter.peek('ip:1.2.3.4').allowed).toBe(true);
94
+ expect(limiter.peek('ip:5.6.7.8').allowed).toBe(false);
95
+ });
96
+ });
61
97
  });
62
98
  //# sourceMappingURL=rate-limit.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.test.js","sourceRoot":"","sources":["../../../src/compute/__tests__/rate-limit.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAA2B,MAAM,kBAAkB,CAAC;AAErF,MAAM,SAAS,GAAG,OAAO,CAAC;AAC1B,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACzC,IAAI,OAA2B,CAAC;IAEhC,UAAU,CAAC,GAAG,EAAE;QACf,OAAO,GAAG,wBAAwB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC,CAAC;QAC1F,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,EAAE,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC3D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC5D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE;YAAE,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACrE,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC3C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,mBAAmB,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE;YAAE,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACrE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC3E,MAAM,EAAE,GAAG,sCAAsC,CAAC;QAClD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE;YAAE,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QACtE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE;YAAE,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACrE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAExD,EAAE,CAAC,mBAAmB,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;QAEtC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC7D,qEAAqE;QACrE,oEAAoE;QACpE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACtD,MAAM,KAAK,GAAG,wBAAwB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC,CAAC;QAC9F,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE;YAAE,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACrE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxD,8CAA8C;QAC9C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"rate-limit.test.js","sourceRoot":"","sources":["../../../src/compute/__tests__/rate-limit.test.ts"],"names":[],"mappings":"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"}
@@ -26,8 +26,8 @@ function fakeClient(solve) {
26
26
  return {
27
27
  client: {},
28
28
  scheduler: { solve },
29
- rhinoTiming: { last: null },
30
- solveMeta: { last: null }
29
+ rhinoTiming: { last: null, seq: 0 },
30
+ solveMeta: { last: null, seq: 0 }
31
31
  };
32
32
  }
33
33
  function baseArgs(over = {}) {
@@ -195,11 +195,15 @@ describe('runSolvePipeline — Server-Timing', () => {
195
195
  expect(st).toContain('p_client;dur=3.1');
196
196
  });
197
197
  it('splits solve into rhino_* + compute_link when the client reported server timing', async () => {
198
- // The real onServerTiming callback fires DURING the solve, after the
199
- // pipeline has reset the holder — so populate it inside the solve stub, not
200
- // before the call (which the pipeline's pre-solve reset would wipe).
198
+ // The real callbacks fire DURING the solve: onServerTiming per compute
199
+ // call, onSettle per settle — each bumps its seq. Mimic both so the
200
+ // pipeline's attribution guard (exactly one write since the snapshot,
201
+ // settle not fromCache) sees this request's own telemetry.
201
202
  const client = fakeClient(async () => {
203
+ client.rhinoTiming.seq += 1;
202
204
  client.rhinoTiming.last = { decode: 5, solve: 20, encode: 3 };
205
+ client.solveMeta.seq += 1;
206
+ client.solveMeta.last = { fromCache: false, definitionReuploaded: false };
203
207
  return { errors: [], warnings: [] };
204
208
  });
205
209
  const outcome = await runSolvePipeline(baseArgs({ client }));
@@ -213,9 +217,8 @@ describe('runSolvePipeline — Server-Timing', () => {
213
217
  expect(st).toContain('compute_link;dur=');
214
218
  });
215
219
  it('surfaces selva_cache + def_reupload verdicts as 0/1 flags', async () => {
216
- // Populate during the solve (the onSettle callback's real timing), not
217
- // before — the pipeline resets solveMeta before it runs.
218
220
  const client = fakeClient(async () => {
221
+ client.solveMeta.seq += 1;
219
222
  client.solveMeta.last = { fromCache: true, definitionReuploaded: false };
220
223
  return { errors: [], warnings: [] };
221
224
  });
@@ -227,19 +230,63 @@ describe('runSolvePipeline — Server-Timing', () => {
227
230
  expect(st).toContain('selva_cache;dur=1');
228
231
  expect(st).toContain('def_reupload;dur=0');
229
232
  });
230
- it('resets stale telemetry holders before solving', async () => {
231
- // A warm client carrying a previous request's timing must not leak it into
232
- // this solve's Server-Timing when the current solve reports nothing.
233
+ it('does not attribute stale telemetry a previous request left on the warm client', async () => {
234
+ // The holders are shared per-server. Data written before this solve's
235
+ // snapshot (seq unchanged during the solve) must not leak into this
236
+ // request's Server-Timing.
233
237
  const client = fakeClient(async () => ({ errors: [], warnings: [] }));
234
- client.rhinoTiming.last = { decode: 99, solve: 99, encode: 99 };
235
- client.solveMeta.last = { fromCache: true };
238
+ client.rhinoTiming = { last: { decode: 99, solve: 99, encode: 99 }, seq: 3 };
239
+ client.solveMeta = { last: { fromCache: true }, seq: 5 };
236
240
  const outcome = await runSolvePipeline(baseArgs({ client }));
237
241
  expect(outcome.kind).toBe('ok');
238
242
  if (outcome.kind !== 'ok')
239
243
  return;
240
- // Holders were reset to null before solve; the stub never repopulated them.
241
244
  expect(outcome.envelope.headers['Server-Timing']).not.toContain('rhino_decode');
242
245
  expect(outcome.envelope.headers['Server-Timing']).not.toContain('selva_cache');
243
246
  });
247
+ it('drops telemetry when a concurrent request also settled in the window (audit B6)', async () => {
248
+ // With maxConcurrentSolves > 1 two solves can interleave. More than one
249
+ // settle since this request's snapshot makes the shared slots ambiguous —
250
+ // the pipeline must omit the segments rather than report another
251
+ // request's cache verdict / rhino timing as ours.
252
+ const client = fakeClient(async () => {
253
+ // Our own solve's callbacks…
254
+ client.rhinoTiming.seq += 1;
255
+ client.rhinoTiming.last = { decode: 5, solve: 20, encode: 3 };
256
+ client.solveMeta.seq += 1;
257
+ client.solveMeta.last = { fromCache: false, definitionReuploaded: false };
258
+ // …then a concurrent request settles before we read.
259
+ client.rhinoTiming.seq += 1;
260
+ client.rhinoTiming.last = { decode: 1, solve: 1, encode: 1 };
261
+ client.solveMeta.seq += 1;
262
+ client.solveMeta.last = { fromCache: true };
263
+ return { errors: [], warnings: [] };
264
+ });
265
+ const outcome = await runSolvePipeline(baseArgs({ client }));
266
+ expect(outcome.kind).toBe('ok');
267
+ if (outcome.kind !== 'ok')
268
+ return;
269
+ const st = outcome.envelope.headers['Server-Timing'];
270
+ expect(st).not.toContain('selva_cache');
271
+ expect(st).not.toContain('rhino_decode');
272
+ });
273
+ it('never attributes rhino timing to a Selva-cache hit (no compute call of ours)', async () => {
274
+ // Our request was served from the in-process cache while a concurrent
275
+ // request's compute call produced the only rhino timing in the window.
276
+ const client = fakeClient(async () => {
277
+ client.rhinoTiming.seq += 1; // the OTHER request's compute call
278
+ client.rhinoTiming.last = { decode: 7, solve: 70, encode: 7 };
279
+ client.solveMeta.seq += 1; // our own settle — a cache hit
280
+ client.solveMeta.last = { fromCache: true };
281
+ return { errors: [], warnings: [] };
282
+ });
283
+ const outcome = await runSolvePipeline(baseArgs({ client }));
284
+ expect(outcome.kind).toBe('ok');
285
+ if (outcome.kind !== 'ok')
286
+ return;
287
+ const st = outcome.envelope.headers['Server-Timing'];
288
+ expect(st).toContain('selva_cache;dur=1');
289
+ expect(st).not.toContain('rhino_decode');
290
+ });
244
291
  });
245
292
  //# sourceMappingURL=solve-pipeline.test.js.map