@mastra/spanner 1.2.1-alpha.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,149 @@
1
1
  # @mastra/spanner
2
2
 
3
+ ## 1.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Added multi-tenant scoping columns (`organizationId`, `projectId`) to the experiments domain so experiment records and per-item results inherit the tenancy bucket of their parent dataset. ([#18388](https://github.com/mastra-ai/mastra/pull/18388))
8
+
9
+ `Experiment`, `ExperimentResult`, `CreateExperimentInput`, and `AddExperimentResultInput` now carry optional `organizationId` / `projectId` fields. `ListExperimentsInput` and `ListExperimentResultsInput` gain a `filters: ExperimentTenancyFilters` block (mirrors `DatasetTenancyFilters`) for scoping queries within a `(organizationId, projectId)` bucket. Tenancy is hydrated from the parent dataset on `createExperiment` and denormalized onto each `ExperimentResult` for efficient tenancy-scoped queries.
10
+
11
+ The corresponding columns are also added to the `mastra_experiments` and `mastra_experiment_results` table schemas. Existing rows backfill to `null`, matching the rest of the dataset-tenancy surface.
12
+
13
+ This release also clarifies the `targetType` contract via JSDoc:
14
+ - `CreateDatasetInput.targetType` remains optional. Datasets without a `TargetType` are **not experiment-eligible** — the experiment runner requires a non-null `CreateExperimentInput.targetType` to resolve an executor.
15
+ - `Experiment.targetType` / `CreateExperimentInput.targetType` stay required. An experiment by definition replays inputs against a specific target.
16
+
17
+ No behavior change for existing OSS-created experiments; the new fields are additive and optional.
18
+
19
+ Example:
20
+
21
+ ```ts
22
+ // Create an experiment scoped to a tenancy bucket. When the parent dataset
23
+ // already carries `organizationId` / `projectId`, `runExperiment` hydrates
24
+ // these fields automatically from the dataset record.
25
+ const experiment = await storage.createExperiment({
26
+ name: 'qa-regression',
27
+ datasetId: 'ds_123',
28
+ datasetVersion: 1,
29
+ targetType: 'agent',
30
+ targetId: 'agent_qa',
31
+ totalItems: 10,
32
+ organizationId: 'org_123',
33
+ projectId: 'proj_123',
34
+ });
35
+
36
+ // List experiments within a tenancy bucket.
37
+ const experiments = await storage.listExperiments({
38
+ pagination: { page: 0, perPage: 20 },
39
+ filters: { organizationId: 'org_123', projectId: 'proj_123' },
40
+ });
41
+
42
+ // List per-item results within the same bucket.
43
+ const results = await storage.listExperimentResults({
44
+ experimentId: experiment.id,
45
+ pagination: { page: 0, perPage: 50 },
46
+ filters: { organizationId: 'org_123', projectId: 'proj_123' },
47
+ });
48
+ ```
49
+
50
+ - Persist and filter dataset tenancy + candidate identity in storage adapters. ([#18314](https://github.com/mastra-ai/mastra/pull/18314))
51
+
52
+ `createDataset` now persists `organizationId`, `projectId`, `candidateKey`, and `candidateId`. `listDatasets` and `listItems` accept matching tenancy filters. Dataset items inherit `organizationId` / `projectId` from their parent dataset on insert, update, delete, and batch insert/delete — items are never settable per call (item tenancy follows dataset tenancy).
53
+
54
+ All new columns are nullable and added retroactively via each adapter's existing column-migration path; no breaking DDL. Existing rows continue to read and write fine; new writes can choose to stamp tenancy.
55
+
56
+ ```ts
57
+ await storage.createDataset({
58
+ name: 'candidates/missing-tool-call/incident-123',
59
+ organizationId: 'org_abc',
60
+ projectId: 'project_xyz',
61
+ candidateKey: 'missing-tool-call',
62
+ candidateId: 'incident-123',
63
+ });
64
+
65
+ await storage.listDatasets({
66
+ pagination: { page: 0, perPage: 20 },
67
+ filters: { organizationId: 'org_abc', projectId: 'project_xyz' },
68
+ });
69
+ ```
70
+
71
+ - Added storage for item-level tool mocks. Dataset items persist their `toolMocks` and experiment results persist their `toolMockReport`, so mocks and run diagnostics survive across sessions. ([#18036](https://github.com/mastra-ai/mastra/pull/18036))
72
+
73
+ - Updated dependencies [[`5bd72d2`](https://github.com/mastra-ai/mastra/commit/5bd72d255f45b5ea8ab342643bd463814a980a24), [`1cc9ee1`](https://github.com/mastra-ai/mastra/commit/1cc9ee1ba51db53020a735626d33017a60b4b5b3), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`65f255a`](https://github.com/mastra-ai/mastra/commit/65f255a38667beb6ceeadabfa9eb5059bfec8298), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`30ebaf0`](https://github.com/mastra-ai/mastra/commit/30ebaf07bed5f4d30f2f257836c15d1bf7e40aae), [`5704634`](https://github.com/mastra-ai/mastra/commit/5704634b22133167dea337a942a34f57aaa3fa14), [`5c4e9a4`](https://github.com/mastra-ai/mastra/commit/5c4e9a4cfb2216bb3ea7f8988ad3727f3b92bb3a), [`4a88c6e`](https://github.com/mastra-ai/mastra/commit/4a88c6e2bdce316f8d7551b4ec3449b0b06fc71c), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`25961e3`](https://github.com/mastra-ai/mastra/commit/25961e3260ff3b1464637af8fcdb36210551c39f), [`6a1428a`](https://github.com/mastra-ai/mastra/commit/6a1428a23133fc070fc6c1caa08d28f3ba4fe5ff), [`87a17ef`](https://github.com/mastra-ai/mastra/commit/87a17efbd725aca6639febdc5e69e2abb3048689), [`e11ff30`](https://github.com/mastra-ai/mastra/commit/e11ff301408bf1731dca2fb7fbfcd8c819500a35), [`7794d71`](https://github.com/mastra-ai/mastra/commit/7794d71872c68733a30e028dfb7b1705daf6c5d2), [`9d2c946`](https://github.com/mastra-ai/mastra/commit/9d2c946d0859e90ae4bcec5beeb1da7398d2ad1e), [`c0eda2b`](https://github.com/mastra-ai/mastra/commit/c0eda2bcd91a228427314b12c91d8b147f3a739f), [`7b29f33`](https://github.com/mastra-ai/mastra/commit/7b29f332a357a83e555f29e718e5f2fab9979943), [`c0eda2b`](https://github.com/mastra-ai/mastra/commit/c0eda2bcd91a228427314b12c91d8b147f3a739f), [`b13925b`](https://github.com/mastra-ai/mastra/commit/b13925bfa91aa8700f56fa54a9ce707ee7e4ba62), [`f1ec385`](https://github.com/mastra-ai/mastra/commit/f1ec385386f62b1a0847ec5353ae2bb169d1c3d9), [`e14986f`](https://github.com/mastra-ai/mastra/commit/e14986f6e5478d6384d04ff9a7f9a79a46a8b529), [`24912b1`](https://github.com/mastra-ai/mastra/commit/24912b1f855d29ec36af4ef4bde1f7417e20cdf5), [`bf94ec6`](https://github.com/mastra-ai/mastra/commit/bf94ec68192d9f16e46ef7e5ac36370aeeddf35d), [`a29f371`](https://github.com/mastra-ai/mastra/commit/a29f371aef629ac8562661524a497127e93b5131), [`7686216`](https://github.com/mastra-ai/mastra/commit/7686216f37e74568feddec17cef3c3d24e10e60a), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`073f910`](https://github.com/mastra-ai/mastra/commit/073f910481e7d94b95ba3830f96531774ae95d33), [`0be490f`](https://github.com/mastra-ai/mastra/commit/0be490fabb538c5a7de796ea0aff7d04a0bea1f3), [`0be490f`](https://github.com/mastra-ai/mastra/commit/0be490fabb538c5a7de796ea0aff7d04a0bea1f3), [`ebbe1d3`](https://github.com/mastra-ai/mastra/commit/ebbe1d31a965a3adb0e728758f326b8122b4b55f), [`974f614`](https://github.com/mastra-ai/mastra/commit/974f614e083bd68278536f94453f7b320b86a3c7), [`3818814`](https://github.com/mastra-ai/mastra/commit/38188149ce454c4403fe9fcbdf73b735c68d36be), [`975c59a`](https://github.com/mastra-ai/mastra/commit/975c59ae363ee275fc55062392e1ffd2cbccbd53), [`1f97ce5`](https://github.com/mastra-ai/mastra/commit/1f97ce5695463bebb4eaacf501da6fb403e20885), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`7f51548`](https://github.com/mastra-ai/mastra/commit/7f515481213780be7047cef00640b9d35f3d545c), [`64f58c0`](https://github.com/mastra-ai/mastra/commit/64f58c04e78b40137497d47f781e897e416f22a5), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`ebbe1d3`](https://github.com/mastra-ai/mastra/commit/ebbe1d31a965a3adb0e728758f326b8122b4b55f), [`d95f394`](https://github.com/mastra-ai/mastra/commit/d95f394fd24c8411886930d727679c4d5252aa26), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`8e25a78`](https://github.com/mastra-ai/mastra/commit/8e25a78e0597575f0b0729bae8c5e190c84869b5), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`f3f0c9d`](https://github.com/mastra-ai/mastra/commit/f3f0c9d7c878db5a13177871ce3523a14f14b311), [`a5b22d3`](https://github.com/mastra-ai/mastra/commit/a5b22d314d62a68d801886a8d3d0eb6c089473db), [`31be1cf`](https://github.com/mastra-ai/mastra/commit/31be1cf5f2a7b5eef12f6123a40653b4d8115c16), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858)]:
74
+ - @mastra/core@1.46.0
75
+
76
+ ## 1.2.1-alpha.1
77
+
78
+ ### Patch Changes
79
+
80
+ - Added multi-tenant scoping columns (`organizationId`, `projectId`) to the experiments domain so experiment records and per-item results inherit the tenancy bucket of their parent dataset. ([#18388](https://github.com/mastra-ai/mastra/pull/18388))
81
+
82
+ `Experiment`, `ExperimentResult`, `CreateExperimentInput`, and `AddExperimentResultInput` now carry optional `organizationId` / `projectId` fields. `ListExperimentsInput` and `ListExperimentResultsInput` gain a `filters: ExperimentTenancyFilters` block (mirrors `DatasetTenancyFilters`) for scoping queries within a `(organizationId, projectId)` bucket. Tenancy is hydrated from the parent dataset on `createExperiment` and denormalized onto each `ExperimentResult` for efficient tenancy-scoped queries.
83
+
84
+ The corresponding columns are also added to the `mastra_experiments` and `mastra_experiment_results` table schemas. Existing rows backfill to `null`, matching the rest of the dataset-tenancy surface.
85
+
86
+ This release also clarifies the `targetType` contract via JSDoc:
87
+ - `CreateDatasetInput.targetType` remains optional. Datasets without a `TargetType` are **not experiment-eligible** — the experiment runner requires a non-null `CreateExperimentInput.targetType` to resolve an executor.
88
+ - `Experiment.targetType` / `CreateExperimentInput.targetType` stay required. An experiment by definition replays inputs against a specific target.
89
+
90
+ No behavior change for existing OSS-created experiments; the new fields are additive and optional.
91
+
92
+ Example:
93
+
94
+ ```ts
95
+ // Create an experiment scoped to a tenancy bucket. When the parent dataset
96
+ // already carries `organizationId` / `projectId`, `runExperiment` hydrates
97
+ // these fields automatically from the dataset record.
98
+ const experiment = await storage.createExperiment({
99
+ name: 'qa-regression',
100
+ datasetId: 'ds_123',
101
+ datasetVersion: 1,
102
+ targetType: 'agent',
103
+ targetId: 'agent_qa',
104
+ totalItems: 10,
105
+ organizationId: 'org_123',
106
+ projectId: 'proj_123',
107
+ });
108
+
109
+ // List experiments within a tenancy bucket.
110
+ const experiments = await storage.listExperiments({
111
+ pagination: { page: 0, perPage: 20 },
112
+ filters: { organizationId: 'org_123', projectId: 'proj_123' },
113
+ });
114
+
115
+ // List per-item results within the same bucket.
116
+ const results = await storage.listExperimentResults({
117
+ experimentId: experiment.id,
118
+ pagination: { page: 0, perPage: 50 },
119
+ filters: { organizationId: 'org_123', projectId: 'proj_123' },
120
+ });
121
+ ```
122
+
123
+ - Persist and filter dataset tenancy + candidate identity in storage adapters. ([#18314](https://github.com/mastra-ai/mastra/pull/18314))
124
+
125
+ `createDataset` now persists `organizationId`, `projectId`, `candidateKey`, and `candidateId`. `listDatasets` and `listItems` accept matching tenancy filters. Dataset items inherit `organizationId` / `projectId` from their parent dataset on insert, update, delete, and batch insert/delete — items are never settable per call (item tenancy follows dataset tenancy).
126
+
127
+ All new columns are nullable and added retroactively via each adapter's existing column-migration path; no breaking DDL. Existing rows continue to read and write fine; new writes can choose to stamp tenancy.
128
+
129
+ ```ts
130
+ await storage.createDataset({
131
+ name: 'candidates/missing-tool-call/incident-123',
132
+ organizationId: 'org_abc',
133
+ projectId: 'project_xyz',
134
+ candidateKey: 'missing-tool-call',
135
+ candidateId: 'incident-123',
136
+ });
137
+
138
+ await storage.listDatasets({
139
+ pagination: { page: 0, perPage: 20 },
140
+ filters: { organizationId: 'org_abc', projectId: 'project_xyz' },
141
+ });
142
+ ```
143
+
144
+ - Updated dependencies [[`5c4e9a4`](https://github.com/mastra-ai/mastra/commit/5c4e9a4cfb2216bb3ea7f8988ad3727f3b92bb3a), [`25961e3`](https://github.com/mastra-ai/mastra/commit/25961e3260ff3b1464637af8fcdb36210551c39f), [`7b29f33`](https://github.com/mastra-ai/mastra/commit/7b29f332a357a83e555f29e718e5f2fab9979943), [`24912b1`](https://github.com/mastra-ai/mastra/commit/24912b1f855d29ec36af4ef4bde1f7417e20cdf5), [`7686216`](https://github.com/mastra-ai/mastra/commit/7686216f37e74568feddec17cef3c3d24e10e60a), [`975c59a`](https://github.com/mastra-ai/mastra/commit/975c59ae363ee275fc55062392e1ffd2cbccbd53), [`d95f394`](https://github.com/mastra-ai/mastra/commit/d95f394fd24c8411886930d727679c4d5252aa26), [`f3f0c9d`](https://github.com/mastra-ai/mastra/commit/f3f0c9d7c878db5a13177871ce3523a14f14b311)]:
145
+ - @mastra/core@1.46.0-alpha.4
146
+
3
147
  ## 1.2.1-alpha.0
4
148
 
5
149
  ### Patch Changes
@@ -3,7 +3,7 @@ name: mastra-spanner
3
3
  description: Documentation for @mastra/spanner. Use when working with @mastra/spanner APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/spanner"
6
- version: "1.2.1-alpha.0"
6
+ version: "1.2.1"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.2.1-alpha.0",
2
+ "version": "1.2.1",
3
3
  "package": "@mastra/spanner",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -2969,6 +2969,10 @@ function rowToDataset(row) {
2969
2969
  targetIds: t.targetIds ?? null,
2970
2970
  scorerIds: t.scorerIds ?? null,
2971
2971
  version: Number(t.version ?? 0),
2972
+ organizationId: t.organizationId ?? null,
2973
+ projectId: t.projectId ?? null,
2974
+ candidateKey: t.candidateKey ?? null,
2975
+ candidateId: t.candidateId ?? null,
2972
2976
  createdAt: toDate(t.createdAt),
2973
2977
  updatedAt: toDate(t.updatedAt)
2974
2978
  };
@@ -2979,6 +2983,8 @@ function rowToItem(row) {
2979
2983
  id: String(t.id),
2980
2984
  datasetId: String(t.datasetId),
2981
2985
  datasetVersion: Number(t.datasetVersion),
2986
+ organizationId: t.organizationId ?? null,
2987
+ projectId: t.projectId ?? null,
2982
2988
  input: t.input,
2983
2989
  groundTruth: t.groundTruth ?? void 0,
2984
2990
  expectedTrajectory: t.expectedTrajectory ?? void 0,
@@ -3025,6 +3031,16 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3025
3031
  await this.db.createTable({ tableName: storage.TABLE_DATASETS, schema: storage.TABLE_SCHEMAS[storage.TABLE_DATASETS] });
3026
3032
  await this.db.createTable({ tableName: storage.TABLE_DATASET_ITEMS, schema: storage.TABLE_SCHEMAS[storage.TABLE_DATASET_ITEMS] });
3027
3033
  await this.db.createTable({ tableName: storage.TABLE_DATASET_VERSIONS, schema: storage.TABLE_SCHEMAS[storage.TABLE_DATASET_VERSIONS] });
3034
+ await this.db.alterTable({
3035
+ tableName: storage.TABLE_DATASETS,
3036
+ schema: storage.TABLE_SCHEMAS[storage.TABLE_DATASETS],
3037
+ ifNotExists: ["organizationId", "projectId", "candidateKey", "candidateId"]
3038
+ });
3039
+ await this.db.alterTable({
3040
+ tableName: storage.TABLE_DATASET_ITEMS,
3041
+ schema: storage.TABLE_SCHEMAS[storage.TABLE_DATASET_ITEMS],
3042
+ ifNotExists: ["organizationId", "projectId"]
3043
+ });
3028
3044
  await this.createDefaultIndexes();
3029
3045
  await this.createCustomIndexes();
3030
3046
  }
@@ -3085,6 +3101,10 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3085
3101
  targetIds: input.targetIds ?? null,
3086
3102
  scorerIds: input.scorerIds ?? null,
3087
3103
  version: 0,
3104
+ organizationId: input.organizationId ?? null,
3105
+ projectId: input.projectId ?? null,
3106
+ candidateKey: input.candidateKey ?? null,
3107
+ candidateId: input.candidateId ?? null,
3088
3108
  createdAt: now,
3089
3109
  updatedAt: now
3090
3110
  };
@@ -3103,6 +3123,10 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3103
3123
  targetIds: record.targetIds,
3104
3124
  scorerIds: record.scorerIds,
3105
3125
  version: 0,
3126
+ organizationId: record.organizationId,
3127
+ projectId: record.projectId,
3128
+ candidateKey: record.candidateKey,
3129
+ candidateId: record.candidateId,
3106
3130
  createdAt: now,
3107
3131
  updatedAt: now
3108
3132
  }
@@ -3232,8 +3256,28 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3232
3256
  const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
3233
3257
  try {
3234
3258
  const tableName = quoteIdent(storage.TABLE_DATASETS, "table name");
3259
+ const filterConditions = [];
3260
+ const filterParams = {};
3261
+ if (args.filters?.organizationId !== void 0) {
3262
+ filterConditions.push(`${quoteIdent("organizationId", "column name")} = @organizationId`);
3263
+ filterParams.organizationId = args.filters.organizationId;
3264
+ }
3265
+ if (args.filters?.projectId !== void 0) {
3266
+ filterConditions.push(`${quoteIdent("projectId", "column name")} = @projectId`);
3267
+ filterParams.projectId = args.filters.projectId;
3268
+ }
3269
+ if (args.filters?.candidateKey !== void 0) {
3270
+ filterConditions.push(`${quoteIdent("candidateKey", "column name")} = @candidateKey`);
3271
+ filterParams.candidateKey = args.filters.candidateKey;
3272
+ }
3273
+ if (args.filters?.candidateId !== void 0) {
3274
+ filterConditions.push(`${quoteIdent("candidateId", "column name")} = @candidateId`);
3275
+ filterParams.candidateId = args.filters.candidateId;
3276
+ }
3277
+ const whereClause = filterConditions.length > 0 ? `WHERE ${filterConditions.join(" AND ")}` : "";
3235
3278
  const [countRows] = await this.database.run({
3236
- sql: `SELECT COUNT(*) AS count FROM ${tableName}`,
3279
+ sql: `SELECT COUNT(*) AS count FROM ${tableName} ${whereClause}`,
3280
+ params: filterParams,
3237
3281
  json: true
3238
3282
  });
3239
3283
  const total = Number(countRows[0]?.count ?? 0);
@@ -3242,10 +3286,10 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3242
3286
  }
3243
3287
  const limit = perPageInput === false ? total : perPage;
3244
3288
  const [rows] = await this.database.run({
3245
- sql: `SELECT * FROM ${tableName}
3289
+ sql: `SELECT * FROM ${tableName} ${whereClause}
3246
3290
  ORDER BY ${quoteIdent("createdAt", "column name")} DESC, ${quoteIdent("id", "column name")} ASC
3247
3291
  LIMIT @limit OFFSET @offset`,
3248
- params: { limit, offset },
3292
+ params: { ...filterParams, limit, offset },
3249
3293
  json: true
3250
3294
  });
3251
3295
  const datasets = rows.map(rowToDataset);
@@ -3272,10 +3316,13 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3272
3316
  // ==========================================================================
3273
3317
  // SCD-2 helpers
3274
3318
  // ==========================================================================
3275
- /** Reads and increments the dataset version inside `tx`; throws if missing. */
3319
+ /** Reads and increments the dataset version inside `tx`; throws if missing. Also returns parent tenancy. */
3276
3320
  async bumpVersion(tx, datasetId, now) {
3277
3321
  const [rows] = await tx.run({
3278
- sql: `SELECT ${quoteIdent("version", "column name")} AS version FROM ${quoteIdent(storage.TABLE_DATASETS, "table name")}
3322
+ sql: `SELECT ${quoteIdent("version", "column name")} AS version,
3323
+ ${quoteIdent("organizationId", "column name")} AS organizationId,
3324
+ ${quoteIdent("projectId", "column name")} AS projectId
3325
+ FROM ${quoteIdent(storage.TABLE_DATASETS, "table name")}
3279
3326
  WHERE ${quoteIdent("id", "column name")} = @id LIMIT 1`,
3280
3327
  params: { id: datasetId },
3281
3328
  json: true
@@ -3299,7 +3346,7 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3299
3346
  params: { newVersion, now: now.toISOString(), id: datasetId },
3300
3347
  types: { now: "timestamp" }
3301
3348
  });
3302
- return newVersion;
3349
+ return { version: newVersion, organizationId: row.organizationId ?? null, projectId: row.projectId ?? null };
3303
3350
  }
3304
3351
  /** Inserts a dataset version snapshot row inside `tx`. */
3305
3352
  async insertVersionRow(tx, datasetId, version, now) {
@@ -3344,13 +3391,15 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3344
3391
  await this.db.runWithAbortRetry(
3345
3392
  () => this.database.runTransactionAsync(async (tx) => {
3346
3393
  try {
3347
- const newVersion = await this.bumpVersion(tx, args.datasetId, now);
3394
+ const { version: newVersion, organizationId, projectId } = await this.bumpVersion(tx, args.datasetId, now);
3348
3395
  await this.db.insert({
3349
3396
  tableName: storage.TABLE_DATASET_ITEMS,
3350
3397
  record: {
3351
3398
  id: itemId,
3352
3399
  datasetId: args.datasetId,
3353
3400
  datasetVersion: newVersion,
3401
+ organizationId,
3402
+ projectId,
3354
3403
  validTo: null,
3355
3404
  isDeleted: false,
3356
3405
  input: args.input,
@@ -3371,6 +3420,8 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3371
3420
  id: itemId,
3372
3421
  datasetId: args.datasetId,
3373
3422
  datasetVersion: newVersion,
3423
+ organizationId,
3424
+ projectId,
3374
3425
  input: args.input,
3375
3426
  groundTruth: args.groundTruth,
3376
3427
  expectedTrajectory: args.expectedTrajectory,
@@ -3437,7 +3488,7 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3437
3488
  await this.db.runWithAbortRetry(
3438
3489
  () => this.database.runTransactionAsync(async (tx) => {
3439
3490
  try {
3440
- const newVersion = await this.bumpVersion(tx, args.datasetId, now);
3491
+ const { version: newVersion, organizationId, projectId } = await this.bumpVersion(tx, args.datasetId, now);
3441
3492
  await this.closeCurrentRow(tx, args.id, newVersion);
3442
3493
  await this.db.insert({
3443
3494
  tableName: storage.TABLE_DATASET_ITEMS,
@@ -3445,6 +3496,8 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3445
3496
  id: args.id,
3446
3497
  datasetId: args.datasetId,
3447
3498
  datasetVersion: newVersion,
3499
+ organizationId,
3500
+ projectId,
3448
3501
  validTo: null,
3449
3502
  isDeleted: false,
3450
3503
  input: merged.input,
@@ -3465,6 +3518,8 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3465
3518
  id: args.id,
3466
3519
  datasetId: args.datasetId,
3467
3520
  datasetVersion: newVersion,
3521
+ organizationId,
3522
+ projectId,
3468
3523
  input: merged.input,
3469
3524
  groundTruth: merged.groundTruth,
3470
3525
  expectedTrajectory: merged.expectedTrajectory,
@@ -3513,7 +3568,7 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3513
3568
  await this.db.runWithAbortRetry(
3514
3569
  () => this.database.runTransactionAsync(async (tx) => {
3515
3570
  try {
3516
- const newVersion = await this.bumpVersion(tx, args.datasetId, now);
3571
+ const { version: newVersion, organizationId, projectId } = await this.bumpVersion(tx, args.datasetId, now);
3517
3572
  await this.closeCurrentRow(tx, args.id, newVersion);
3518
3573
  await this.db.insert({
3519
3574
  tableName: storage.TABLE_DATASET_ITEMS,
@@ -3521,6 +3576,8 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3521
3576
  id: args.id,
3522
3577
  datasetId: args.datasetId,
3523
3578
  datasetVersion: newVersion,
3579
+ organizationId,
3580
+ projectId,
3524
3581
  validTo: null,
3525
3582
  isDeleted: true,
3526
3583
  input: existing.input,
@@ -3564,6 +3621,14 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3564
3621
  try {
3565
3622
  const conditions = [`${quoteIdent("datasetId", "column name")} = @datasetId`];
3566
3623
  const params = { datasetId: args.datasetId };
3624
+ if (args.filters?.organizationId !== void 0) {
3625
+ conditions.push(`${quoteIdent("organizationId", "column name")} = @organizationId`);
3626
+ params.organizationId = args.filters.organizationId;
3627
+ }
3628
+ if (args.filters?.projectId !== void 0) {
3629
+ conditions.push(`${quoteIdent("projectId", "column name")} = @projectId`);
3630
+ params.projectId = args.filters.projectId;
3631
+ }
3567
3632
  if (args.version !== void 0) {
3568
3633
  conditions.push(`${quoteIdent("datasetVersion", "column name")} <= @version`);
3569
3634
  conditions.push(
@@ -3785,7 +3850,7 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3785
3850
  await this.db.runWithAbortRetry(
3786
3851
  () => this.database.runTransactionAsync(async (tx) => {
3787
3852
  try {
3788
- const newVersion = await this.bumpVersion(tx, input.datasetId, now);
3853
+ const { version: newVersion, organizationId, projectId } = await this.bumpVersion(tx, input.datasetId, now);
3789
3854
  for (const { id, item } of prepared) {
3790
3855
  await this.db.insert({
3791
3856
  tableName: storage.TABLE_DATASET_ITEMS,
@@ -3793,6 +3858,8 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3793
3858
  id,
3794
3859
  datasetId: input.datasetId,
3795
3860
  datasetVersion: newVersion,
3861
+ organizationId,
3862
+ projectId,
3796
3863
  validTo: null,
3797
3864
  isDeleted: false,
3798
3865
  input: item.input,
@@ -3814,6 +3881,8 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3814
3881
  id,
3815
3882
  datasetId: input.datasetId,
3816
3883
  datasetVersion: newVersion,
3884
+ organizationId,
3885
+ projectId,
3817
3886
  input: item.input,
3818
3887
  groundTruth: item.groundTruth,
3819
3888
  expectedTrajectory: item.expectedTrajectory,
@@ -3857,7 +3926,7 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3857
3926
  await this.db.runWithAbortRetry(
3858
3927
  () => this.database.runTransactionAsync(async (tx) => {
3859
3928
  try {
3860
- const newVersion = await this.bumpVersion(tx, input.datasetId, now);
3929
+ const { version: newVersion, organizationId, projectId } = await this.bumpVersion(tx, input.datasetId, now);
3861
3930
  for (const existing of current) {
3862
3931
  await this.closeCurrentRow(tx, existing.id, newVersion);
3863
3932
  await this.db.insert({
@@ -3866,6 +3935,8 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
3866
3935
  id: existing.id,
3867
3936
  datasetId: input.datasetId,
3868
3937
  datasetVersion: newVersion,
3938
+ organizationId,
3939
+ projectId,
3869
3940
  validTo: null,
3870
3941
  isDeleted: true,
3871
3942
  input: existing.input,
@@ -3916,6 +3987,8 @@ function rowToExperiment(row) {
3916
3987
  metadata: t.metadata ?? void 0,
3917
3988
  datasetId: t.datasetId ?? null,
3918
3989
  datasetVersion: t.datasetVersion == null ? null : Number(t.datasetVersion),
3990
+ organizationId: t.organizationId ?? null,
3991
+ projectId: t.projectId ?? null,
3919
3992
  targetType: t.targetType,
3920
3993
  targetId: String(t.targetId),
3921
3994
  status: t.status,
@@ -3937,6 +4010,8 @@ function rowToExperimentResult(row) {
3937
4010
  experimentId: String(t.experimentId),
3938
4011
  itemId: String(t.itemId),
3939
4012
  itemDatasetVersion: t.itemDatasetVersion == null ? null : Number(t.itemDatasetVersion),
4013
+ organizationId: t.organizationId ?? null,
4014
+ projectId: t.projectId ?? null,
3940
4015
  input: t.input ?? null,
3941
4016
  output: t.output ?? null,
3942
4017
  groundTruth: t.groundTruth ?? null,
@@ -3968,6 +4043,16 @@ var ExperimentsSpanner = class _ExperimentsSpanner extends storage.ExperimentsSt
3968
4043
  async init() {
3969
4044
  await this.db.createTable({ tableName: storage.TABLE_EXPERIMENTS, schema: storage.TABLE_SCHEMAS[storage.TABLE_EXPERIMENTS] });
3970
4045
  await this.db.createTable({ tableName: storage.TABLE_EXPERIMENT_RESULTS, schema: storage.TABLE_SCHEMAS[storage.TABLE_EXPERIMENT_RESULTS] });
4046
+ await this.db.alterTable({
4047
+ tableName: storage.TABLE_EXPERIMENTS,
4048
+ schema: storage.TABLE_SCHEMAS[storage.TABLE_EXPERIMENTS],
4049
+ ifNotExists: ["organizationId", "projectId"]
4050
+ });
4051
+ await this.db.alterTable({
4052
+ tableName: storage.TABLE_EXPERIMENT_RESULTS,
4053
+ schema: storage.TABLE_SCHEMAS[storage.TABLE_EXPERIMENT_RESULTS],
4054
+ ifNotExists: ["organizationId", "projectId"]
4055
+ });
3971
4056
  await this.createDefaultIndexes();
3972
4057
  await this.createCustomIndexes();
3973
4058
  }
@@ -3989,6 +4074,17 @@ var ExperimentsSpanner = class _ExperimentsSpanner extends storage.ExperimentsSt
3989
4074
  table: storage.TABLE_EXPERIMENT_RESULTS,
3990
4075
  columns: ["experimentId", "itemId"],
3991
4076
  unique: true
4077
+ },
4078
+ // Tenancy: leading-tenant indexes for multi-tenant scans (parity with datasets domain).
4079
+ {
4080
+ name: "mastra_experiments_org_project_idx",
4081
+ table: storage.TABLE_EXPERIMENTS,
4082
+ columns: ["organizationId", "projectId"]
4083
+ },
4084
+ {
4085
+ name: "mastra_experiment_results_org_project_idx",
4086
+ table: storage.TABLE_EXPERIMENT_RESULTS,
4087
+ columns: ["organizationId", "projectId"]
3992
4088
  }
3993
4089
  ];
3994
4090
  }
@@ -4015,6 +4111,8 @@ var ExperimentsSpanner = class _ExperimentsSpanner extends storage.ExperimentsSt
4015
4111
  metadata: input.metadata ?? void 0,
4016
4112
  datasetId: input.datasetId ?? null,
4017
4113
  datasetVersion: input.datasetVersion ?? null,
4114
+ organizationId: input.organizationId ?? null,
4115
+ projectId: input.projectId ?? null,
4018
4116
  targetType: input.targetType,
4019
4117
  targetId: input.targetId,
4020
4118
  status: "pending",
@@ -4037,6 +4135,8 @@ var ExperimentsSpanner = class _ExperimentsSpanner extends storage.ExperimentsSt
4037
4135
  metadata: experiment.metadata ?? null,
4038
4136
  datasetId: experiment.datasetId,
4039
4137
  datasetVersion: experiment.datasetVersion,
4138
+ organizationId: experiment.organizationId,
4139
+ projectId: experiment.projectId,
4040
4140
  targetType: experiment.targetType,
4041
4141
  targetId: experiment.targetId,
4042
4142
  status: experiment.status,
@@ -4155,6 +4255,17 @@ var ExperimentsSpanner = class _ExperimentsSpanner extends storage.ExperimentsSt
4155
4255
  conditions.push(`${quoteIdent("status", "column name")} = @status`);
4156
4256
  params.status = args.status;
4157
4257
  }
4258
+ if (args.filters) {
4259
+ const { organizationId, projectId } = args.filters;
4260
+ if (organizationId !== void 0) {
4261
+ conditions.push(`${quoteIdent("organizationId", "column name")} = @organizationId`);
4262
+ params.organizationId = organizationId;
4263
+ }
4264
+ if (projectId !== void 0) {
4265
+ conditions.push(`${quoteIdent("projectId", "column name")} = @projectId`);
4266
+ params.projectId = projectId;
4267
+ }
4268
+ }
4158
4269
  const whereSql = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
4159
4270
  const tableName = quoteIdent(storage.TABLE_EXPERIMENTS, "table name");
4160
4271
  const [countRows] = await this.database.run({
@@ -4239,6 +4350,8 @@ var ExperimentsSpanner = class _ExperimentsSpanner extends storage.ExperimentsSt
4239
4350
  experimentId: input.experimentId,
4240
4351
  itemId: input.itemId,
4241
4352
  itemDatasetVersion: input.itemDatasetVersion ?? null,
4353
+ organizationId: input.organizationId ?? null,
4354
+ projectId: input.projectId ?? null,
4242
4355
  input: input.input ?? null,
4243
4356
  output: input.output ?? null,
4244
4357
  groundTruth: input.groundTruth ?? null,
@@ -4259,6 +4372,8 @@ var ExperimentsSpanner = class _ExperimentsSpanner extends storage.ExperimentsSt
4259
4372
  experimentId: result.experimentId,
4260
4373
  itemId: result.itemId,
4261
4374
  itemDatasetVersion: result.itemDatasetVersion,
4375
+ organizationId: result.organizationId,
4376
+ projectId: result.projectId,
4262
4377
  input: result.input,
4263
4378
  output: result.output,
4264
4379
  groundTruth: result.groundTruth,
@@ -4392,6 +4507,17 @@ var ExperimentsSpanner = class _ExperimentsSpanner extends storage.ExperimentsSt
4392
4507
  conditions.push(`${quoteIdent("status", "column name")} = @status`);
4393
4508
  params.status = args.status;
4394
4509
  }
4510
+ if (args.filters) {
4511
+ const { organizationId, projectId } = args.filters;
4512
+ if (organizationId !== void 0) {
4513
+ conditions.push(`${quoteIdent("organizationId", "column name")} = @organizationId`);
4514
+ params.organizationId = organizationId;
4515
+ }
4516
+ if (projectId !== void 0) {
4517
+ conditions.push(`${quoteIdent("projectId", "column name")} = @projectId`);
4518
+ params.projectId = projectId;
4519
+ }
4520
+ }
4395
4521
  const whereSql = `WHERE ${conditions.join(" AND ")}`;
4396
4522
  const tableName = quoteIdent(storage.TABLE_EXPERIMENT_RESULTS, "table name");
4397
4523
  const [countRows] = await this.database.run({