@proveanything/smartlinks 1.15.2 → 1.15.4

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.
@@ -0,0 +1,459 @@
1
+ # Data Scoping for Products and Proofs
2
+
3
+ Canonical specification for how custom data is stored, who can read it, and who can write it — on both **products** and **proofs**.
4
+
5
+ Applies to the SmartLinks SDK, admin apps, public apps, and any consumer of the platform API.
6
+
7
+ ---
8
+
9
+ ## Design principles
10
+
11
+ 1. **Fixed named fields at the root; flexible data in JSON bags.** The root of a product or proof is reserved for typed, first-class columns (name, image, gtin, ownerId, claimable, …). Everything custom lives inside a JSON bag.
12
+ 2. **Every bag is defined by *who can write and read* it, and lives at the top level.** Write authority and read visibility are hard, server-enforced boundaries. Rather than hide them under sub-keys, each combination gets its own top-level bag. This matches the existing `product.admin` convention and keeps read-time filtering trivial: the server either returns a bag or it doesn't.
13
+ 3. **Public is the default; admin is opt-in.** Anything in `data` is world-readable. Anything private to the business goes in `admin`, which the server never returns to a non-admin caller.
14
+ 4. **Same shape everywhere.** Products and proofs share the same rules for the business-writable bags (`data`, `admin`). Proofs add a third top-level bag — `values` — for owner-writable and per-user data, because proofs have an owner and products don't.
15
+
16
+ ---
17
+
18
+ ## The schema
19
+
20
+ ### Product
21
+
22
+ ```
23
+ product.<rootColumns> // typed columns: name, description, image, gtin, …
24
+
25
+ product.data = { … } // PUBLIC → read: everyone, write: business
26
+ product.admin = { … } // BUSINESS-ONLY → read: business, write: business
27
+ ```
28
+
29
+ ### Proof
30
+
31
+ ```
32
+ proof.<rootColumns> // typed columns: id, productId, ownerId, claimable, …
33
+
34
+ proof.data = { … } // PUBLIC → read: everyone, write: business
35
+ proof.admin = { … } // BUSINESS-ONLY → read: business, write: business
36
+
37
+ proof.values = { // consumer bag — owner + business-writable
38
+ <anyKey>: …, // PUBLIC → read: everyone,
39
+ // write: business + current owner
40
+ owner: { … }, // OWNER-SCOPED → read: business + current owner,
41
+ // write: business + current owner,
42
+ // transfers with ownership
43
+ personal: { // PER-USER → read/write: only the specific user,
44
+ [userId]: { … }, // not visible to the next owner
45
+ },
46
+ }
47
+ ```
48
+
49
+ Products intentionally have no `values` bag — there is no owner to write it. If a product ever needs owner-like data, that data belongs on the proof.
50
+
51
+ ---
52
+
53
+ ## Reserved sub-key names
54
+
55
+ Inside `proof.values`, these keys are reserved and MUST NOT be used as free-form public field names:
56
+
57
+ | Bag | Reserved keys |
58
+ | ------------- | --------------------------- |
59
+ | `proof.values`| `owner`, `personal` |
60
+
61
+ `data` and `admin` bags have no reserved sub-keys — everything inside them is flat.
62
+
63
+ The field-config editor and SDK write helpers MUST reject attempts to create top-level public fields in `proof.values` named `owner` or `personal`.
64
+
65
+ ---
66
+
67
+ ## Who reads what (visibility matrix)
68
+
69
+ | Location | Public / Anonymous | Authenticated (non-owner) | Current Owner | Business Admin |
70
+ | --------------------------------- | :-----------------: | :------------------------: | :------------: | :--------------: |
71
+ | `product.<root>` | ✅ | ✅ | ✅ | ✅ |
72
+ | `product.data.*` | ✅ | ✅ | ✅ | ✅ |
73
+ | `product.admin.*` | ❌ | ❌ | ❌ | ✅ |
74
+ | `proof.<root>` | ✅ | ✅ | ✅ | ✅ |
75
+ | `proof.data.*` | ✅ | ✅ | ✅ | ✅ |
76
+ | `proof.admin.*` | ❌ | ❌ | ❌ | ✅ |
77
+ | `proof.values.<publicKey>` | ✅ | ✅ | ✅ | ✅ |
78
+ | `proof.values.owner.*` | ❌ | ❌ | ✅ | ✅ |
79
+ | `proof.values.personal[me].*` | ❌ | ✅ (own slot only) | ✅ (own slot) | ❌ (see note) |
80
+ | `proof.values.personal[other].*` | ❌ | ❌ | ❌ | ❌ (see note) |
81
+
82
+ > **Note on `personal`:** the default rule is that `personal` slots are readable *only* by the user whose `userId` matches the slot key — not even business admins. If the platform ever needs an admin-visible variant, it should be a separate mechanism, not a relaxation of this rule.
83
+
84
+ ## Who writes what (authority matrix)
85
+
86
+ | Location | Public / Anonymous | Authenticated (non-owner) | Current Owner | Business Admin |
87
+ | --------------------------------- | :-----------------: | :------------------------: | :------------: | :--------------: |
88
+ | `product.<root>` | ❌ | ❌ | ❌ | ✅ |
89
+ | `product.data.*` | ❌ | ❌ | ❌ | ✅ |
90
+ | `product.admin.*` | ❌ | ❌ | ❌ | ✅ |
91
+ | `proof.<root>` (spec) | ❌ | ❌ | ❌ | ✅ |
92
+ | `proof.data.*` | ❌ | ❌ | ❌ | ✅ |
93
+ | `proof.admin.*` | ❌ | ❌ | ❌ | ✅ |
94
+ | `proof.values.<publicKey>` | ❌ | ❌ | ✅ | ✅ |
95
+ | `proof.values.owner.*` | ❌ | ❌ | ✅ | ✅ |
96
+ | `proof.values.personal[me].*` | ❌ | ✅ (own slot only) | ✅ (own slot) | ❌ |
97
+
98
+ All read and write rules are **server-enforced**. Clients MUST NOT rely on UI hiding alone.
99
+
100
+ ---
101
+
102
+ ## Worked examples
103
+
104
+ ### A bicycle
105
+
106
+ ```jsonc
107
+ // product (the model)
108
+ {
109
+ "id": "prod_road_2024",
110
+ "name": "Aero Road 2024",
111
+ "image": "…",
112
+ "data": {
113
+ "frameMaterial": "carbon", // public
114
+ "wheelSize": "700c", // public
115
+ "purchaseLink": "https://…" // public
116
+ },
117
+ "admin": {
118
+ "internalSku": "AR24-CARB-M", // business-only
119
+ "unitCost": 1420, // business-only
120
+ "manufacturingSite": "Taichung" // business-only
121
+ }
122
+ }
123
+
124
+ // proof (a specific bike)
125
+ {
126
+ "id": "proof_abc123",
127
+ "productId": "prod_road_2024",
128
+ "ownerId": "user_42",
129
+ "claimable": false,
130
+ "data": {
131
+ "serial": "SN-000123", // public spec, business-set
132
+ "colour": "matte black", // public spec, business-set
133
+ "year": 2024 // public spec, business-set
134
+ },
135
+ "admin": {
136
+ "warrantyExpiry": "2027-06-01", // business-only
137
+ "batchId": "B-2024-Q2-17" // business-only
138
+ },
139
+ "values": {
140
+ "nickname": "Midnight", // public, owner-editable
141
+ "displayNote": "For sale — DM me", // public, owner-editable
142
+ "owner": {
143
+ "purchasedAt": "Halfords Oxford", // business + current owner, transfers
144
+ "serviceHistory": [ /* … */ ] // with ownership
145
+ },
146
+ "personal": {
147
+ "user_42": {
148
+ "combinationLock": "17-04-88", // only user_42 ever sees this
149
+ "privateNotes": "…"
150
+ }
151
+ }
152
+ }
153
+ }
154
+ ```
155
+
156
+ ### Liquid template ergonomics
157
+
158
+ ```liquid
159
+ {{ product.name }} {# root column #}
160
+ {{ product.data.purchaseLink }} {# public #}
161
+ {{ product.admin.internalSku }} {# admin surfaces only #}
162
+
163
+ {{ proof.data.colour }} {# public spec #}
164
+ {{ proof.admin.warrantyExpiry }} {# admin surfaces only #}
165
+ {{ proof.values.nickname }} {# public, owner-editable #}
166
+ {{ proof.values.owner.purchasedAt }} {# owner-scoped #}
167
+ {{ proof.values.personal[currentUserId].combinationLock }}
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Legacy compatibility
173
+
174
+ The new shape is **strictly additive**. No existing data needs to move.
175
+
176
+ - **`product.data.<key>`** — legacy flat keys continue to mean "public". No migration.
177
+ - **`product.admin.<key>`** — already exists today; formalised here as the canonical private-business bag.
178
+ - **`proof.values.<key>`** — legacy flat keys continue to mean "public, owner-editable". No migration.
179
+ - **`proof.data`** — new bucket, added alongside the existing `proof.values`, for public business-owned spec that isn't consumer-editable.
180
+ - **`proof.admin`** — new bucket, mirroring `product.admin`.
181
+ - **`owner` / `personal`** — reserved going forward inside `proof.values`. Any pre-existing custom field literally named `owner` or `personal` should be renamed on a case-by-case basis. New writes MUST reject these as free-form key names.
182
+
183
+ Legacy apps that only read the flat public shape continue to work unchanged. New apps that need scoped data opt in by reading and writing the new bags and reserved sub-keys.
184
+
185
+ ---
186
+
187
+ ## SDK guidance
188
+
189
+ - **Reads** return whatever bags the caller is entitled to. Callers MUST NOT assume `admin`, `values.owner`, or `values.personal` are present.
190
+ - **Writes** to `admin` bags, `proof.data`, and `proof.values.owner` require the appropriate role; the server rejects unauthorised writes with a permission error rather than silently dropping keys.
191
+ - **Field-config editors** (custom-fields UIs on collection settings) should:
192
+ - Offer a **scope** dropdown per field. Products: `Public` (→ `product.data`), `Admin` (→ `product.admin`). Proofs: `Public` (→ `proof.values.<key>` — owner-editable), `Owner` (→ `proof.values.owner`), `Personal` (→ `proof.values.personal[userId]`), `Admin` (→ `proof.admin`).
193
+ - Reject reserved names (`owner`, `personal`) as free-form keys in `proof.values`.
194
+ - Persist enough metadata for renderers to know which bag a field lives in and to gate its display by the current caller's role.
195
+ - **Server** performs both read-scoping (strip bags the caller can't see) and write authorisation (reject writes the caller isn't allowed to make). The client is a convenience layer, never the source of truth for either.
196
+
197
+ ---
198
+
199
+ ## Summary
200
+
201
+ ```
202
+ product.data = { … } // public business
203
+ product.admin = { … } // admin-only business
204
+
205
+ proof.data = { … } // public business (spec)
206
+ proof.admin = { … } // admin-only business
207
+ proof.values = { …public, owner?, personal? } // consumer
208
+ ```
209
+
210
+ Reserved sub-keys: **`owner`** and **`personal`** inside `proof.values`. Everything else in every bag is a free-form key.
211
+
212
+ ---
213
+
214
+ ## Field-definition schemas (collection settings)
215
+
216
+ The custom-field definitions that drive the editors above are stored on the
217
+ **collection** in named settings groups. Both are read/written via the standard
218
+ settings API:
219
+
220
+ ```ts
221
+ // Read (admin: true required for admin-scoped fields to be returned)
222
+ const productFields = await SL.collection.getSettings(collectionId, 'productFields', true);
223
+ const proofFields = await SL.collection.getSettings(collectionId, 'proofFields', true);
224
+
225
+ // Write
226
+ await SL.collection.updateSettings(collectionId, 'productFields', productFieldsConfig);
227
+ await SL.collection.updateSettings(collectionId, 'proofFields', proofFieldsConfig);
228
+ ```
229
+
230
+ Third-party apps (CRM, warranty, provenance, etc.) that render a product or
231
+ proof for a user should fetch these once per collection to know which
232
+ configurable fields exist, their labels, types, and — crucially — which bag to
233
+ read the value from.
234
+
235
+ ### Settings group names
236
+
237
+ | Group key | Shape | Applies to |
238
+ | ----------------- | --------------------- | ---------------------------------------------------------------- |
239
+ | `productFields` | `ProductFieldsConfig`| Custom fields on `product.data` / `product.admin` |
240
+ | `proofFields` | `ProofFieldsConfig` | Custom fields on `proof.values` / `proof.data` / `proof.admin`|
241
+
242
+ ### Shared field-def shape
243
+
244
+ Product and proof field defs share the same base shape. The only difference is
245
+ the allowed `scope` values.
246
+
247
+ ```ts
248
+ type FieldType =
249
+ | 'text'
250
+ | 'textarea'
251
+ | 'number'
252
+ | 'date'
253
+ | 'select'
254
+ | 'switch'
255
+ | 'url';
256
+
257
+ interface FieldOption {
258
+ value: string;
259
+ label: string;
260
+ }
261
+
262
+ interface FieldDef {
263
+ /** Stable key. Written into the resolved bag at this name. */
264
+ key: string;
265
+ /** Human label for editors and read-only renderers. */
266
+ label: string;
267
+ type: FieldType;
268
+ required?: boolean;
269
+ placeholder?: string;
270
+ help?: string;
271
+ /** Required when type === 'select'. */
272
+ options?: FieldOption[];
273
+ /** Default value applied when creating a new record. */
274
+ defaultValue?: unknown;
275
+ /** Show as a column in the default items table. */
276
+ showInTable?: boolean;
277
+ /** Show on the inline detail / edit view. */
278
+ showInDetail?: boolean;
279
+ /**
280
+ * Storage scope. When omitted the field is legacy public and written flat
281
+ * (product.data.<key> or proof.values.<key>). New fields opt into a scope.
282
+ */
283
+ scope?: FieldScope;
284
+ }
285
+ ```
286
+
287
+ ### `productFields` — `ProductFieldsConfig`
288
+
289
+ ```ts
290
+ type ProductFieldScope = 'public' | 'admin';
291
+
292
+ type ProductFieldDef = FieldDef & { scope?: ProductFieldScope };
293
+
294
+ interface ProductFieldsConfig {
295
+ fields: ProductFieldDef[];
296
+ }
297
+ ```
298
+
299
+ **Value resolution** for a given `ProductFieldDef` `f`:
300
+
301
+ | `f.scope` | Read from |
302
+ | ---------------------- | ---------------------------------------------- |
303
+ | `'public'` / omitted | `product.data[f.key]` |
304
+ | `'admin'` | `product.admin?.[f.key]` (admin only) |
305
+
306
+ ### `proofFields` — `ProofFieldsConfig`
307
+
308
+ ```ts
309
+ type ProofFieldScope = 'public' | 'owner' | 'personal' | 'admin';
310
+
311
+ type ProofFieldDef = FieldDef & { scope?: ProofFieldScope };
312
+
313
+ interface ProofFieldsConfig {
314
+ fields: ProofFieldDef[];
315
+ }
316
+ ```
317
+
318
+ **Value resolution** for a given `ProofFieldDef` `f`, current user id `uid`:
319
+
320
+ | `f.scope` | Read from |
321
+ | ---------------------- | -------------------------------------------------- |
322
+ | `'public'` / omitted | `proof.values[f.key]` |
323
+ | `'owner'` | `proof.values.owner?.[f.key]` |
324
+ | `'personal'` | `proof.values.personal?.[uid]?.[f.key]` |
325
+ | `'admin'` | `proof.admin?.[f.key]` |
326
+
327
+ If the caller isn't entitled to the scope, the server strips the bag; the
328
+ field def still exists but the value comes back `undefined`. Clients should
329
+ treat that as "not visible to me" rather than "unset".
330
+
331
+ > **Note:** there is currently no `scope` value that maps to `proof.data`
332
+ > (public business spec). If/when the editor UX wants to distinguish
333
+ > "public, business-only-writable" from "public, owner-editable" (the current
334
+ > unscoped `proof.values.<key>`), add a `'spec'` scope that resolves to
335
+ > `proof.data[f.key]`. Until then, treat `proof.data` as hand-authored /
336
+ > server-set and out of scope for the collection-settings field editor.
337
+
338
+ ### JSON Schema — `productFields`
339
+
340
+ ```json
341
+ {
342
+ "$schema": "http://json-schema.org/draft-07/schema#",
343
+ "$id": "https://smartlinks.app/schemas/collection-settings/productFields.json",
344
+ "title": "Collection Settings: productFields",
345
+ "type": "object",
346
+ "additionalProperties": false,
347
+ "required": ["fields"],
348
+ "properties": {
349
+ "fields": {
350
+ "type": "array",
351
+ "items": { "$ref": "#/$defs/productFieldDef" }
352
+ }
353
+ },
354
+ "$defs": {
355
+ "productFieldDef": {
356
+ "type": "object",
357
+ "additionalProperties": false,
358
+ "required": ["key", "label", "type"],
359
+ "properties": {
360
+ "key": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
361
+ "label": { "type": "string" },
362
+ "type": { "enum": ["text","textarea","number","date","select","switch","url"] },
363
+ "required": { "type": "boolean" },
364
+ "placeholder": { "type": "string" },
365
+ "help": { "type": "string" },
366
+ "options": {
367
+ "type": "array",
368
+ "items": {
369
+ "type": "object",
370
+ "additionalProperties": false,
371
+ "required": ["value", "label"],
372
+ "properties": {
373
+ "value": { "type": "string" },
374
+ "label": { "type": "string" }
375
+ }
376
+ }
377
+ },
378
+ "defaultValue": {},
379
+ "showInTable": { "type": "boolean" },
380
+ "showInDetail": { "type": "boolean" },
381
+ "scope": { "enum": ["public", "admin"] }
382
+ }
383
+ }
384
+ }
385
+ }
386
+ ```
387
+
388
+ ### JSON Schema — `proofFields`
389
+
390
+ ```json
391
+ {
392
+ "$schema": "http://json-schema.org/draft-07/schema#",
393
+ "$id": "https://smartlinks.app/schemas/collection-settings/proofFields.json",
394
+ "title": "Collection Settings: proofFields",
395
+ "type": "object",
396
+ "additionalProperties": false,
397
+ "required": ["fields"],
398
+ "properties": {
399
+ "fields": {
400
+ "type": "array",
401
+ "items": { "$ref": "#/$defs/proofFieldDef" }
402
+ }
403
+ },
404
+ "$defs": {
405
+ "proofFieldDef": {
406
+ "type": "object",
407
+ "additionalProperties": false,
408
+ "required": ["key", "label", "type"],
409
+ "properties": {
410
+ "key": { "type": "string", "pattern": "^(?!owner$|personal$)[A-Za-z_][A-Za-z0-9_]*$" },
411
+ "label": { "type": "string" },
412
+ "type": { "enum": ["text","textarea","number","date","select","switch","url"] },
413
+ "required": { "type": "boolean" },
414
+ "placeholder": { "type": "string" },
415
+ "help": { "type": "string" },
416
+ "options": {
417
+ "type": "array",
418
+ "items": {
419
+ "type": "object",
420
+ "additionalProperties": false,
421
+ "required": ["value", "label"],
422
+ "properties": {
423
+ "value": { "type": "string" },
424
+ "label": { "type": "string" }
425
+ }
426
+ }
427
+ },
428
+ "defaultValue": {},
429
+ "showInTable": { "type": "boolean" },
430
+ "showInDetail": { "type": "boolean" },
431
+ "scope": { "enum": ["public", "owner", "personal", "admin"] }
432
+ }
433
+ }
434
+ }
435
+ }
436
+ ```
437
+
438
+ ### Worked example — CRM app resolving a proof
439
+
440
+ ```ts
441
+ const { fields } = await SL.collection.getSettings(collectionId, 'proofFields');
442
+ const uid = (await SL.auth.getAccount())?.id;
443
+
444
+ for (const f of fields) {
445
+ const value =
446
+ !f.scope || f.scope === 'public' ? proof.values?.[f.key] :
447
+ f.scope === 'owner' ? proof.values?.owner?.[f.key] :
448
+ f.scope === 'personal' ? proof.values?.personal?.[uid!]?.[f.key] :
449
+ f.scope === 'admin' ? proof.admin?.[f.key] :
450
+ undefined;
451
+
452
+ render({ label: f.label, type: f.type, value });
453
+ }
454
+ ```
455
+
456
+ The same loop, swapping `proofFields` → `productFields` and the resolution
457
+ table for the product one (`public` → `product.data`, `admin` → `product.admin`),
458
+ gives a CRM app a complete configurable read of a product. No app-specific
459
+ knowledge required beyond "fetch the field defs from the collection".
package/dist/openapi.yaml CHANGED
@@ -13964,6 +13964,16 @@ components:
13964
13964
  parameters:
13965
13965
  type: object
13966
13966
  additionalProperties: true
13967
+ output_schema:
13968
+ type: object
13969
+ additionalProperties: true
13970
+ allowed_callers:
13971
+ type: array
13972
+ items:
13973
+ type: string
13974
+ enum:
13975
+ - assistant
13976
+ - programmatic
13967
13977
  required:
13968
13978
  - type
13969
13979
  ResponseInputItem:
@@ -14052,6 +14062,22 @@ components:
14052
14062
  type: string
14053
14063
  prompt_cache_key:
14054
14064
  type: string
14065
+ multi_agent:
14066
+ type: object
14067
+ additionalProperties: true
14068
+ enabled:
14069
+ type: boolean
14070
+ max_concurrent_subagents:
14071
+ type: number
14072
+ service_tier:
14073
+ type: string
14074
+ enum:
14075
+ - auto
14076
+ - standard
14077
+ - flex
14078
+ - priority
14079
+ required:
14080
+ - enabled
14055
14081
  ResponsesResult:
14056
14082
  type: object
14057
14083
  properties:
@@ -24058,6 +24084,67 @@ components:
24058
24084
  $ref: "#/components/schemas/JsonValue"
24059
24085
  required:
24060
24086
  - claimId
24087
+ ScopedFieldOption:
24088
+ type: object
24089
+ properties:
24090
+ value:
24091
+ type: string
24092
+ label:
24093
+ type: string
24094
+ required:
24095
+ - value
24096
+ - label
24097
+ ScopedFieldDef:
24098
+ type: object
24099
+ properties:
24100
+ key:
24101
+ type: string
24102
+ label:
24103
+ type: string
24104
+ type:
24105
+ $ref: "#/components/schemas/ScopedFieldType"
24106
+ required:
24107
+ type: boolean
24108
+ placeholder:
24109
+ type: string
24110
+ help:
24111
+ type: string
24112
+ options:
24113
+ type: array
24114
+ items:
24115
+ $ref: "#/components/schemas/ScopedFieldOption"
24116
+ defaultValue:
24117
+ $ref: "#/components/schemas/JsonValue"
24118
+ showInTable:
24119
+ type: boolean
24120
+ showInDetail:
24121
+ type: boolean
24122
+ required:
24123
+ - key
24124
+ - label
24125
+ - type
24126
+ ProductFieldsConfig:
24127
+ type: object
24128
+ properties:
24129
+ fields:
24130
+ type: array
24131
+ items:
24132
+ $ref: "#/components/schemas/ProductFieldDef"
24133
+ required:
24134
+ - fields
24135
+ ProofValues:
24136
+ type: object
24137
+ properties:
24138
+ owner:
24139
+ type: object
24140
+ additionalProperties:
24141
+ $ref: "#/components/schemas/JsonValue"
24142
+ personal:
24143
+ type: object
24144
+ additionalProperties:
24145
+ type: object
24146
+ additionalProperties:
24147
+ $ref: "#/components/schemas/JsonValue"
24061
24148
  Proof:
24062
24149
  type: object
24063
24150
  properties:
@@ -24077,9 +24164,16 @@ components:
24077
24164
  type: boolean
24078
24165
  virtual:
24079
24166
  type: boolean
24080
- values:
24167
+ data:
24081
24168
  type: object
24082
- additionalProperties: true
24169
+ additionalProperties:
24170
+ $ref: "#/components/schemas/JsonValue"
24171
+ admin:
24172
+ type: object
24173
+ additionalProperties:
24174
+ $ref: "#/components/schemas/JsonValue"
24175
+ values:
24176
+ $ref: "#/components/schemas/ProofValues"
24083
24177
  required:
24084
24178
  - collectionId
24085
24179
  - createdAt
@@ -24092,14 +24186,30 @@ components:
24092
24186
  type: object
24093
24187
  properties:
24094
24188
  values:
24189
+ $ref: "#/components/schemas/ProofValues"
24190
+ data:
24095
24191
  type: object
24096
- additionalProperties: true
24192
+ additionalProperties:
24193
+ $ref: "#/components/schemas/JsonValue"
24194
+ admin:
24195
+ type: object
24196
+ additionalProperties:
24197
+ $ref: "#/components/schemas/JsonValue"
24097
24198
  claimable:
24098
24199
  type: boolean
24099
24200
  virtual:
24100
24201
  type: boolean
24101
24202
  required:
24102
24203
  - values
24204
+ ProofFieldsConfig:
24205
+ type: object
24206
+ properties:
24207
+ fields:
24208
+ type: array
24209
+ items:
24210
+ $ref: "#/components/schemas/ProofFieldDef"
24211
+ required:
24212
+ - fields
24103
24213
  ProofResponse:
24104
24214
  type: object
24105
24215
  additionalProperties: true
@@ -56,6 +56,10 @@ export interface ResponseTool {
56
56
  name?: string;
57
57
  description?: string;
58
58
  parameters?: Record<string, any>;
59
+ /** Expected shape of this tool's output, used by Programmatic Tool Calling. */
60
+ output_schema?: Record<string, any>;
61
+ /** Restricts which callers may invoke this tool (e.g. `['programmatic']` for Programmatic Tool Calling). */
62
+ allowed_callers?: Array<'assistant' | 'programmatic'>;
59
63
  [key: string]: any;
60
64
  }
61
65
  /** Structured input item accepted by the Responses API. */
@@ -98,6 +102,13 @@ export interface ResponsesRequest {
98
102
  text?: Record<string, any>;
99
103
  metadata?: Record<string, string>;
100
104
  prompt_cache_key?: string;
105
+ /** Enables multi-agent (subagent) orchestration. Not supported together with `stream: true`. */
106
+ multi_agent?: {
107
+ enabled: boolean;
108
+ max_concurrent_subagents?: number;
109
+ };
110
+ /** `'flex'` is ~50% cheaper at Batch-API rates but slower; reserve for non-interactive/background work. */
111
+ service_tier?: 'auto' | 'standard' | 'flex' | 'priority';
101
112
  }
102
113
  /** Response from the Responses API. */
103
114
  export interface ResponsesResult {
@@ -85,7 +85,9 @@ export interface Product extends ProductKey {
85
85
  facets?: ProductFacetMap;
86
86
  /** Tag keys where value is true */
87
87
  tags?: Record<string, boolean>;
88
+ /** Public custom data — readable by everyone, writable by business admins only. */
88
89
  data?: Record<string, JsonValue>;
90
+ /** Business-only custom data — stripped from public/non-admin reads. See docs/proof-product-data-scoping.md. */
89
91
  admin?: Record<string, JsonValue>;
90
92
  extra?: Record<string, JsonValue>;
91
93
  validCollections?: string[] | null;
@@ -161,3 +163,38 @@ export type ProductClaimCreateRequestBody = Omit<ProductClaimCreateInput, 'colle
161
163
  export type ProductResponse = Product;
162
164
  export type ProductCreateRequest = ProductWriteInput;
163
165
  export type ProductUpdateRequest = Partial<Omit<ProductWriteInput, 'id'>>;
166
+ export type ScopedFieldType = 'text' | 'textarea' | 'number' | 'date' | 'select' | 'switch' | 'url';
167
+ export interface ScopedFieldOption {
168
+ value: string;
169
+ label: string;
170
+ }
171
+ export interface ScopedFieldDef {
172
+ /** Stable key. Written into the resolved bag at this name. */
173
+ key: string;
174
+ /** Human label for editors and read-only renderers. */
175
+ label: string;
176
+ type: ScopedFieldType;
177
+ required?: boolean;
178
+ placeholder?: string;
179
+ help?: string;
180
+ /** Required when type === 'select'. */
181
+ options?: ScopedFieldOption[];
182
+ /** Default value applied when creating a new record. */
183
+ defaultValue?: JsonValue;
184
+ /** Show as a column in the default items table. */
185
+ showInTable?: boolean;
186
+ /** Show on the inline detail / edit view. */
187
+ showInDetail?: boolean;
188
+ }
189
+ /**
190
+ * `'public'` (default, omitted) reads/writes `product.data[key]`.
191
+ * `'admin'` reads/writes `product.admin[key]` (admin only).
192
+ */
193
+ export type ProductFieldScope = 'public' | 'admin';
194
+ export type ProductFieldDef = ScopedFieldDef & {
195
+ scope?: ProductFieldScope;
196
+ };
197
+ /** Shape of the `productFields` collection settings group. */
198
+ export interface ProductFieldsConfig {
199
+ fields: ProductFieldDef[];
200
+ }