@peers-app/peers-sdk 0.26.6 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +3 -1
  2. package/bin/peers-package.js +13 -0
  3. package/define/package.json +4 -0
  4. package/dist/contracts/__tests__/action-dispatch.test.js +56 -76
  5. package/dist/contracts/__tests__/also-implements.test.d.ts +1 -0
  6. package/dist/contracts/__tests__/also-implements.test.js +110 -0
  7. package/dist/contracts/__tests__/builder.test.js +45 -0
  8. package/dist/contracts/__tests__/contract-resolvers.test.js +134 -4
  9. package/dist/contracts/__tests__/frozen-contracts.test.d.ts +1 -0
  10. package/dist/contracts/__tests__/frozen-contracts.test.js +191 -0
  11. package/dist/contracts/__tests__/generic-tool-dispatch.test.js +13 -56
  12. package/dist/contracts/__tests__/host-contract-client.test.d.ts +1 -0
  13. package/dist/contracts/__tests__/host-contract-client.test.js +793 -0
  14. package/dist/contracts/__tests__/host-contract-transport.test.js +15 -0
  15. package/dist/contracts/__tests__/registry.test.js +68 -0
  16. package/dist/contracts/__tests__/target-dispatch.d.ts +11 -0
  17. package/dist/contracts/__tests__/target-dispatch.js +24 -0
  18. package/dist/contracts/__tests__/tool-catalog-service.test.js +37 -1
  19. package/dist/contracts/__tests__/tool-catalog.test.js +12 -3
  20. package/dist/contracts/__tests__/validate.test.js +177 -7
  21. package/dist/contracts/__tests__/workflow-context.test.js +0 -11
  22. package/dist/contracts/builder.d.ts +16 -2
  23. package/dist/contracts/builder.js +44 -3
  24. package/dist/contracts/contract-definition-schema.d.ts +251 -0
  25. package/dist/contracts/contract-definition-schema.js +48 -0
  26. package/dist/contracts/contract-provider-router.d.ts +2 -0
  27. package/dist/contracts/contract-provider-router.js +5 -2
  28. package/dist/contracts/contract-proxy.d.ts +52 -18
  29. package/dist/contracts/contract-proxy.js +144 -203
  30. package/dist/contracts/contract-resolvers.d.ts +6 -4
  31. package/dist/contracts/contract-resolvers.js +32 -9
  32. package/dist/contracts/frozen-contracts.d.ts +59 -0
  33. package/dist/contracts/frozen-contracts.js +86 -0
  34. package/dist/contracts/host-contract-client.d.ts +134 -0
  35. package/dist/contracts/host-contract-client.js +396 -0
  36. package/dist/contracts/host-contract-transport.d.ts +2 -0
  37. package/dist/contracts/host-contract-transport.js +8 -0
  38. package/dist/contracts/index.d.ts +6 -4
  39. package/dist/contracts/index.js +11 -5
  40. package/dist/contracts/registry.d.ts +31 -1
  41. package/dist/contracts/registry.js +100 -6
  42. package/dist/contracts/system/contract-catalog-contract.d.ts +61 -6
  43. package/dist/contracts/system/contract-catalog-contract.js +17 -5
  44. package/dist/contracts/tool-catalog-service.js +26 -6
  45. package/dist/contracts/tool-catalog.d.ts +1 -1
  46. package/dist/contracts/tool-catalog.js +4 -1
  47. package/dist/contracts/types.d.ts +32 -2
  48. package/dist/contracts/validate.d.ts +16 -1
  49. package/dist/contracts/validate.js +139 -25
  50. package/dist/data/change-tracking.d.ts +17 -5
  51. package/dist/data/change-tracking.js +22 -6
  52. package/dist/data/change-tracking.test.d.ts +1 -0
  53. package/dist/data/change-tracking.test.js +62 -0
  54. package/dist/data/orm/sql.data-source.d.ts +9 -0
  55. package/dist/data/orm/sql.data-source.js +47 -2
  56. package/dist/data/orm/sql.data-source.test.js +142 -0
  57. package/dist/data/orm/table-container.js +9 -9
  58. package/dist/data/orm/table-container.test.js +44 -0
  59. package/dist/data/orm/types.d.ts +41 -1
  60. package/dist/data/orm/types.js +159 -28
  61. package/dist/data/orm/types.test.js +129 -0
  62. package/dist/data/peer-types.d.ts +8 -62
  63. package/dist/data/persistent-vars.d.ts +4 -1
  64. package/dist/data/persistent-vars.js +7 -2
  65. package/dist/data/tools.d.ts +29 -221
  66. package/dist/define/also-implements.test.d.ts +1 -0
  67. package/dist/define/also-implements.test.js +123 -0
  68. package/dist/define/context.d.ts +77 -0
  69. package/dist/define/context.js +2 -0
  70. package/dist/define/contract-ref.d.ts +177 -0
  71. package/dist/define/contract-ref.js +67 -0
  72. package/dist/define/declare-contract.d.ts +167 -0
  73. package/dist/define/declare-contract.js +220 -0
  74. package/dist/define/declare-contract.test.d.ts +1 -0
  75. package/dist/define/declare-contract.test.js +573 -0
  76. package/dist/define/define-package.d.ts +203 -0
  77. package/dist/define/define-package.js +289 -0
  78. package/dist/define/define.test.d.ts +1 -0
  79. package/dist/define/define.test.js +1010 -0
  80. package/dist/define/empty-fields.d.ts +15 -0
  81. package/dist/define/empty-fields.js +134 -0
  82. package/dist/define/empty-fields.test.d.ts +1 -0
  83. package/dist/define/empty-fields.test.js +55 -0
  84. package/dist/define/events-observables.test.d.ts +1 -0
  85. package/dist/define/events-observables.test.js +1013 -0
  86. package/dist/define/events.d.ts +81 -0
  87. package/dist/define/events.js +81 -0
  88. package/dist/define/generated-field.d.ts +20 -0
  89. package/dist/define/generated-field.js +25 -0
  90. package/dist/define/index.d.ts +28 -0
  91. package/dist/define/index.js +62 -0
  92. package/dist/define/lazy-members.d.ts +8 -0
  93. package/dist/define/lazy-members.js +38 -0
  94. package/dist/define/manifest.d.ts +63 -0
  95. package/dist/define/manifest.js +653 -0
  96. package/dist/define/observables.d.ts +146 -0
  97. package/dist/define/observables.js +187 -0
  98. package/dist/define/serve-members.d.ts +21 -0
  99. package/dist/define/serve-members.js +152 -0
  100. package/dist/define/serve-members.test.d.ts +1 -0
  101. package/dist/define/serve-members.test.js +630 -0
  102. package/dist/define/serve.d.ts +22 -0
  103. package/dist/define/serve.js +58 -0
  104. package/dist/define/system-contracts.d.ts +41 -0
  105. package/dist/define/system-contracts.js +21 -0
  106. package/dist/define/table-handle.d.ts +13 -0
  107. package/dist/define/table-handle.js +36 -0
  108. package/dist/define/table.d.ts +128 -0
  109. package/dist/define/table.js +122 -0
  110. package/dist/define/tool.d.ts +129 -0
  111. package/dist/define/tool.js +91 -0
  112. package/dist/define/vars.d.ts +82 -0
  113. package/dist/define/vars.js +256 -0
  114. package/dist/define/vars.test.d.ts +1 -0
  115. package/dist/define/vars.test.js +39 -0
  116. package/dist/define/wire-value.d.ts +12 -0
  117. package/dist/define/wire-value.js +76 -0
  118. package/dist/define/worker-state.d.ts +15 -0
  119. package/dist/define/worker-state.js +45 -0
  120. package/dist/device/tx-encoding.test.js +10 -0
  121. package/dist/index.d.ts +3 -0
  122. package/dist/index.js +5 -1
  123. package/dist/observable.d.ts +42 -2
  124. package/dist/observable.js +25 -7
  125. package/dist/package-build/build-package.d.ts +60 -0
  126. package/dist/package-build/build-package.js +312 -0
  127. package/dist/package-build/build-package.test.d.ts +1 -0
  128. package/dist/package-build/build-package.test.js +204 -0
  129. package/dist/package-build/cli.d.ts +7 -0
  130. package/dist/package-build/cli.js +218 -0
  131. package/dist/package-build/contract-add.d.ts +35 -0
  132. package/dist/package-build/contract-add.js +97 -0
  133. package/dist/package-build/contract-add.test.d.ts +1 -0
  134. package/dist/package-build/contract-add.test.js +467 -0
  135. package/dist/package-build/contract-source.d.ts +59 -0
  136. package/dist/package-build/contract-source.js +471 -0
  137. package/dist/package-build/freeze-contracts.d.ts +56 -0
  138. package/dist/package-build/freeze-contracts.js +198 -0
  139. package/dist/package-build/freeze-contracts.test.d.ts +1 -0
  140. package/dist/package-build/freeze-contracts.test.js +355 -0
  141. package/dist/package-installer/package-creator.d.ts +4 -0
  142. package/dist/package-installer/package-creator.js +57 -16
  143. package/dist/package-installer/package-creator.test.js +86 -0
  144. package/dist/package-loader/contract-members.d.ts +53 -0
  145. package/dist/package-loader/contract-members.js +31 -0
  146. package/dist/package-loader/contract-package-loader.js +5 -1
  147. package/dist/package-loader/contract-package-loader.test.js +11 -0
  148. package/dist/package-loader/host-authority-loading.test.js +9 -5
  149. package/dist/package-loader/index.d.ts +1 -0
  150. package/dist/package-loader/index.js +1 -0
  151. package/dist/package-loader/isolated-artifact.d.ts +80 -17
  152. package/dist/package-loader/isolated-artifact.js +257 -51
  153. package/dist/package-loader/isolated-artifact.test.js +294 -21
  154. package/dist/package-loader/isolated-runtime.d.ts +22 -7
  155. package/dist/package-loader/isolated-table-gateway.js +30 -11
  156. package/dist/package-loader/isolated-table-gateway.test.js +243 -0
  157. package/dist/package-loader/package-loader.d.ts +8 -1
  158. package/dist/package-loader/package-loader.js +39 -20
  159. package/dist/package-loader/package-loader.test.js +47 -0
  160. package/dist/peer-id.d.ts +25 -0
  161. package/dist/peer-id.js +99 -0
  162. package/dist/serial-json.compat.test.d.ts +1 -0
  163. package/dist/serial-json.compat.test.js +369 -0
  164. package/dist/serial-json.d.ts +41 -5
  165. package/dist/serial-json.js +216 -157
  166. package/dist/serial-json.oracle.d.ts +21 -0
  167. package/dist/serial-json.oracle.js +222 -0
  168. package/dist/serial-json.test.js +315 -0
  169. package/dist/testing/index.d.ts +4 -0
  170. package/dist/testing/index.js +8 -0
  171. package/dist/testing/mock-host-contract.d.ts +123 -0
  172. package/dist/testing/mock-host-contract.js +434 -0
  173. package/dist/testing/mock-host-contract.test.d.ts +1 -0
  174. package/dist/testing/mock-host-contract.test.js +782 -0
  175. package/dist/types/field-type.d.ts +35 -18
  176. package/dist/types/field-type.js +4 -1
  177. package/dist/types/zod-types.d.ts +9 -0
  178. package/dist/types/zod-types.js +8 -2
  179. package/dist/utils.d.ts +1 -25
  180. package/dist/utils.js +10 -90
  181. package/package.json +9 -2
  182. package/testing/package.json +4 -0
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ISOLATED_EVENT_HOST_MEMBER = exports.ISOLATED_PERSISTENT_VAR_HOST_MEMBER = exports.ISOLATED_TABLE_HOST_MEMBER = exports.ISOLATED_CONSUME_HOST_MEMBER = exports.ISOLATED_TOOL_TIMEOUT_MAX_MS = exports.ISOLATED_PACKAGE_MAGIC = void 0;
3
+ exports.ISOLATED_MAX_FIELD_DEPTH = exports.ISOLATED_OBSERVABLE_HOST_MEMBER = exports.ISOLATED_EVENT_HOST_MEMBER = exports.ISOLATED_PERSISTENT_VAR_HOST_MEMBER = exports.ISOLATED_TABLE_HOST_MEMBER = exports.ISOLATED_CONSUME_HOST_MEMBER = exports.ISOLATED_TOOL_TIMEOUT_MAX_MS = exports.ISOLATED_GUEST_API_VERSION = exports.ISOLATED_PACKAGE_MAGIC = void 0;
4
+ exports.servesContractMembers = servesContractMembers;
5
+ exports.enumValueFits = enumValueFits;
4
6
  exports.looksLikeIsolatedPackageArtifact = looksLikeIsolatedPackageArtifact;
5
7
  exports.validateIsolatedPackageArtifact = validateIsolatedPackageArtifact;
6
8
  exports.parseIsolatedPackageArtifact = parseIsolatedPackageArtifact;
@@ -15,6 +17,7 @@ exports.packageProvidesEvent = packageProvidesEvent;
15
17
  exports.packageProvidesTable = packageProvidesTable;
16
18
  exports.findPackageTable = findPackageTable;
17
19
  exports.toConsumedContracts = toConsumedContracts;
20
+ const contract_definition_schema_1 = require("../contracts/contract-definition-schema");
18
21
  const access_level_1 = require("../data/access-level");
19
22
  const persistent_var_identity_1 = require("../data/persistent-var-identity");
20
23
  const field_type_1 = require("../types/field-type");
@@ -25,6 +28,30 @@ const utils_1 = require("../utils");
25
28
  * opt-in format without evaluating source.
26
29
  */
27
30
  exports.ISOLATED_PACKAGE_MAGIC = "PEERS_ISOLATED_PACKAGE_V1";
31
+ /**
32
+ * The latest guest API version an isolated manifest targets.
33
+ *
34
+ * - `3`: as `2`, and the package serves its own contract members. The host sends consumers'
35
+ * table calls, observable reads and writes, and subscriptions to the provider's worker
36
+ * (`contractMemberHandlerName`), and passes every table change, event, and observable
37
+ * value bound for a subscriber through it first (`contractDeliveryHandlerName`).
38
+ * - `2`: handlers receive `(args, ctx)` and calls run concurrently; the package orders any
39
+ * state it shares between calls itself. The host serves contract tables and stored
40
+ * observables from storage, without the worker.
41
+ * - Omitted: the legacy global helpers, with one call at a time.
42
+ *
43
+ * Hosts that predate a version reject it.
44
+ */
45
+ exports.ISOLATED_GUEST_API_VERSION = 3;
46
+ /**
47
+ * True when the package serves its contract members itself (guest API 3): every consumer call
48
+ * and outgoing message goes through its worker.
49
+ *
50
+ * @param manifest - The package's manifest.
51
+ */
52
+ function servesContractMembers(manifest) {
53
+ return manifest.guestApi === 3;
54
+ }
28
55
  /** Longest per-tool invoke deadline an isolated manifest may declare. */
29
56
  exports.ISOLATED_TOOL_TIMEOUT_MAX_MS = 120_000;
30
57
  /** Host-contract member guests use to request a consumed-contract tool call. */
@@ -35,6 +62,8 @@ exports.ISOLATED_TABLE_HOST_MEMBER = "package.table";
35
62
  exports.ISOLATED_PERSISTENT_VAR_HOST_MEMBER = "package.pvar";
36
63
  /** Host-contract member guests use to emit a provided-contract event. */
37
64
  exports.ISOLATED_EVENT_HOST_MEMBER = "contract.emit";
65
+ /** Host-contract member guests use to publish an in-memory provided-contract observable. */
66
+ exports.ISOLATED_OBSERVABLE_HOST_MEMBER = "package.observable";
38
67
  const FIELD_TYPES = new Set(Object.values(field_type_1.FieldType));
39
68
  const OBSERVABLE_FIELD_TYPES = new Set([
40
69
  field_type_1.FieldType.id,
@@ -62,35 +91,139 @@ function assertContractVersion(version, label) {
62
91
  throw new Error(`${label} must be a positive integer, got ${String(version)}`);
63
92
  }
64
93
  }
65
- function validateFields(fields, label) {
94
+ /**
95
+ * How deep field lists may nest through object `fields`: a tool's inputs, say, are level 1.
96
+ * Deeper nesting is refused, so a hostile manifest can't exhaust the validator's stack.
97
+ */
98
+ exports.ISOLATED_MAX_FIELD_DEPTH = 16;
99
+ /** Field keys this SDK knows. Hosts drop any other key; the build refuses it (likely a typo). */
100
+ const FIELD_KEYS = new Set([
101
+ "name",
102
+ "description",
103
+ "type",
104
+ "defaultValue",
105
+ "optional",
106
+ "isArray",
107
+ "subType",
108
+ "fields",
109
+ "enumValues",
110
+ ]);
111
+ /**
112
+ * True when `value` may appear in `enumValues` on a field of `type`: a string, number, or boolean
113
+ * of that type, or any of them on `any`. Shared with the manifest builder; not a public SDK export.
114
+ *
115
+ * @internal
116
+ * @param value - A candidate enum value.
117
+ * @param type - The field's type.
118
+ */
119
+ function enumValueFits(value, type) {
120
+ switch (type) {
121
+ case field_type_1.FieldType.string:
122
+ return typeof value === "string";
123
+ case field_type_1.FieldType.number:
124
+ return typeof value === "number" && Number.isFinite(value);
125
+ case field_type_1.FieldType.boolean:
126
+ return typeof value === "boolean";
127
+ case field_type_1.FieldType.any:
128
+ return (typeof value === "string" ||
129
+ typeof value === "boolean" ||
130
+ (typeof value === "number" && Number.isFinite(value)));
131
+ default:
132
+ return false;
133
+ }
134
+ }
135
+ /**
136
+ * Validate one field and rebuild it from the keys this SDK knows. A key it doesn't know can only
137
+ * describe the value further (a key the host must honor comes with a new guest API version, which
138
+ * older hosts refuse), so it is dropped, or refused when `strict`.
139
+ */
140
+ function validateField(field, label, strict, depth, named = true) {
141
+ if (!field || typeof field !== "object" || Array.isArray(field)) {
142
+ throw new Error(`${label} must be an object`);
143
+ }
144
+ const record = field;
145
+ if (strict) {
146
+ const unsupported = Object.keys(record).find((key) => !FIELD_KEYS.has(key) || (!named && key === "name"));
147
+ if (unsupported) {
148
+ throw new Error(`${label}.${unsupported} is not supported by isolated package artifacts`);
149
+ }
150
+ }
151
+ if (named && (typeof record.name !== "string" || record.name.trim() === "")) {
152
+ throw new Error(`${label}.name is required`);
153
+ }
154
+ if (typeof record.type !== "string" || !FIELD_TYPES.has(record.type)) {
155
+ throw new Error(`${label}.type must be a FieldType`);
156
+ }
157
+ const type = record.type;
158
+ if (record.enumValues !== undefined) {
159
+ if (!Array.isArray(record.enumValues) || record.enumValues.length === 0) {
160
+ throw new Error(`${label}.enumValues must be a non-empty array`);
161
+ }
162
+ if (!record.enumValues.every((value) => enumValueFits(value, type))) {
163
+ throw new Error(`${label}.enumValues must all be ${type === field_type_1.FieldType.any ? "strings, numbers, or booleans" : `${type} values`}`);
164
+ }
165
+ }
166
+ let fields;
167
+ if (record.fields !== undefined) {
168
+ if (type !== field_type_1.FieldType.object) {
169
+ throw new Error(`${label}.fields is only allowed on object fields`);
170
+ }
171
+ if (depth >= exports.ISOLATED_MAX_FIELD_DEPTH) {
172
+ throw new Error(`${label}.fields nests more than ${exports.ISOLATED_MAX_FIELD_DEPTH} levels deep`);
173
+ }
174
+ fields = validateFields(record.fields, `${label}.fields`, strict, depth + 1);
175
+ }
176
+ return {
177
+ ...(named ? { name: record.name } : {}),
178
+ type,
179
+ ...(typeof record.description === "string" ? { description: record.description } : {}),
180
+ ...(record.optional === true ? { optional: true } : {}),
181
+ ...(record.isArray === true ? { isArray: true } : {}),
182
+ ...(typeof record.subType === "string" ? { subType: record.subType } : {}),
183
+ ...(record.defaultValue !== undefined ? { defaultValue: record.defaultValue } : {}),
184
+ ...(record.enumValues !== undefined ? { enumValues: [...record.enumValues] } : {}),
185
+ ...(fields ? { fields } : {}),
186
+ };
187
+ }
188
+ function validateFields(fields, label, strict, depth = 1) {
66
189
  if (fields === undefined) {
67
190
  return [];
68
191
  }
69
192
  if (!Array.isArray(fields)) {
70
193
  throw new Error(`${label} must be an array`);
71
194
  }
72
- return fields.map((field, index) => {
73
- if (!field || typeof field !== "object" || Array.isArray(field)) {
74
- throw new Error(`${label}[${index}] must be an object`);
75
- }
76
- const record = field;
77
- assertOnlyKeys(record, ["name", "description", "type", "defaultValue", "optional", "isArray", "subType"], `${label}[${index}]`);
78
- if (typeof record.name !== "string" || record.name.trim() === "") {
79
- throw new Error(`${label}[${index}].name is required`);
80
- }
81
- if (typeof record.type !== "string" || !FIELD_TYPES.has(record.type)) {
82
- throw new Error(`${label}[${index}].type must be a FieldType`);
195
+ return fields.map((field, index) => validateField(field, `${label}[${index}]`, strict, depth));
196
+ }
197
+ /** Check carried fields before the recursive schema parse, including the host's depth bound. */
198
+ function validateCarriedFields(definition, label, strict) {
199
+ if (!definition || typeof definition !== "object" || Array.isArray(definition))
200
+ return;
201
+ const record = definition;
202
+ for (const [kind, fieldLists] of [
203
+ ["tables", ["fields"]],
204
+ ["tools", ["inputFields", "outputFields"]],
205
+ ["events", ["payloadFields"]],
206
+ ["observables", ["value"]],
207
+ ]) {
208
+ const members = record[kind];
209
+ if (!Array.isArray(members))
210
+ continue;
211
+ for (const [index, member] of members.entries()) {
212
+ if (!member || typeof member !== "object" || Array.isArray(member))
213
+ continue;
214
+ for (const fields of fieldLists) {
215
+ const value = member[fields];
216
+ const fieldLabel = `${label}.${kind}[${index}].${fields}`;
217
+ if (fields === "value") {
218
+ if (value !== undefined)
219
+ validateField(value, fieldLabel, strict, 1, false);
220
+ }
221
+ else {
222
+ validateFields(value, fieldLabel, strict);
223
+ }
224
+ }
83
225
  }
84
- return {
85
- name: record.name,
86
- type: record.type,
87
- ...(typeof record.description === "string" ? { description: record.description } : {}),
88
- ...(record.optional === true ? { optional: true } : {}),
89
- ...(record.isArray === true ? { isArray: true } : {}),
90
- ...(typeof record.subType === "string" ? { subType: record.subType } : {}),
91
- ...(record.defaultValue !== undefined ? { defaultValue: record.defaultValue } : {}),
92
- };
93
- });
226
+ }
94
227
  }
95
228
  function assertJsonCompatible(value, label, seen = new WeakSet()) {
96
229
  if (value === null ||
@@ -142,7 +275,7 @@ function validateObservableDefault(value, type, label) {
142
275
  throw new Error(`${label} must match observable valueType ${type}`);
143
276
  }
144
277
  }
145
- function validatePackageTables(manifest) {
278
+ function validatePackageTables(manifest, strict) {
146
279
  if (manifest.tables === undefined) {
147
280
  manifest.tables = [];
148
281
  return;
@@ -167,13 +300,16 @@ function validatePackageTables(manifest) {
167
300
  if (forbiddenIdentity) {
168
301
  throw new Error(`${tableLabel} must not declare ${forbiddenIdentity}`);
169
302
  }
170
- assertOnlyKeys(table, ["name", "description", "primaryKeyName", "fields", "versionNumber"], tableLabel);
303
+ assertOnlyKeys(table, ["name", "description", "primaryKeyName", "fields", "versionNumber", "localOnly"], tableLabel);
171
304
  if (typeof table.name !== "string" || table.name.trim() === "") {
172
305
  throw new Error(`${tableLabel}.name is required`);
173
306
  }
174
307
  if (table.description !== undefined && typeof table.description !== "string") {
175
308
  throw new Error(`${tableLabel}.description must be a string`);
176
309
  }
310
+ if (table.localOnly !== undefined && typeof table.localOnly !== "boolean") {
311
+ throw new Error(`${tableLabel}.localOnly must be a boolean`);
312
+ }
177
313
  if (tableNames.has(table.name)) {
178
314
  throw new Error(`manifest declares duplicate table "${table.name}"`);
179
315
  }
@@ -181,7 +317,7 @@ function validatePackageTables(manifest) {
181
317
  if (typeof table.primaryKeyName !== "string" || table.primaryKeyName.trim() === "") {
182
318
  throw new Error(`${tableLabel}.primaryKeyName is required`);
183
319
  }
184
- table.fields = validateFields(table.fields, `${tableLabel}.fields`);
320
+ table.fields = validateFields(table.fields, `${tableLabel}.fields`, strict);
185
321
  if (table.fields.length === 0) {
186
322
  throw new Error(`${tableLabel}.fields must not be empty`);
187
323
  }
@@ -202,7 +338,7 @@ function validatePackageTables(manifest) {
202
338
  }
203
339
  }
204
340
  }
205
- function validateProvidedContract(contract, index, packageTables) {
341
+ function validateProvidedContract(contract, index, packageTables, guestApi, strict) {
206
342
  const label = `provides[${index}]`;
207
343
  if (!contract || typeof contract !== "object" || Array.isArray(contract)) {
208
344
  throw new Error(`${label} must be an object`);
@@ -217,6 +353,7 @@ function validateProvidedContract(contract, index, packageTables) {
217
353
  "observables",
218
354
  "events",
219
355
  "alsoImplements",
356
+ "exposes",
220
357
  ], label);
221
358
  assertPeerId(contract.contractId, `${label}.contractId`);
222
359
  assertContractVersion(contract.version, `${label}.version`);
@@ -256,8 +393,8 @@ function validateProvidedContract(contract, index, packageTables) {
256
393
  if (typeof tool.usageDescription !== "string") {
257
394
  throw new Error(`${toolLabel}.usageDescription is required`);
258
395
  }
259
- tool.inputFields = validateFields(tool.inputFields, `${toolLabel}.inputFields`);
260
- tool.outputFields = validateFields(tool.outputFields, `${toolLabel}.outputFields`);
396
+ tool.inputFields = validateFields(tool.inputFields, `${toolLabel}.inputFields`, strict);
397
+ tool.outputFields = validateFields(tool.outputFields, `${toolLabel}.outputFields`, strict);
261
398
  if (tool.authorityPropagation !== undefined &&
262
399
  tool.authorityPropagation !== "cap" &&
263
400
  tool.authorityPropagation !== "preserve")
@@ -326,7 +463,7 @@ function validateProvidedContract(contract, index, packageTables) {
326
463
  if (!observable || typeof observable !== "object" || Array.isArray(observable)) {
327
464
  throw new Error(`${observableLabel} must be an object`);
328
465
  }
329
- assertOnlyKeys(observable, ["name", "description", "valueType", "writable", "persistentVar"], observableLabel);
466
+ assertOnlyKeys(observable, ["name", "description", "valueType", "value", "writable", "persistentVar"], observableLabel);
330
467
  if (typeof observable.name !== "string" || observable.name.trim() === "") {
331
468
  throw new Error(`${observableLabel}.name is required`);
332
469
  }
@@ -341,12 +478,23 @@ function validateProvidedContract(contract, index, packageTables) {
341
478
  !OBSERVABLE_FIELD_TYPES.has(observable.valueType)) {
342
479
  throw new Error(`${observableLabel}.valueType must be a JSON-compatible FieldType`);
343
480
  }
481
+ if (observable.value !== undefined) {
482
+ const { name: _name, ...value } = validateField(observable.value, `${observableLabel}.value`, strict, 1, false);
483
+ observable.value = value;
484
+ }
344
485
  if (typeof observable.writable !== "boolean") {
345
486
  throw new Error(`${observableLabel}.writable must be a boolean`);
346
487
  }
347
488
  const binding = observable.persistentVar;
489
+ if (binding === undefined) {
490
+ // Held by the worker, which publishes it: that needs guest API 2 or later.
491
+ if (guestApi === undefined) {
492
+ throw new Error(`${observableLabel}.persistentVar is required unless the package uses guestApi 2 or later`);
493
+ }
494
+ continue;
495
+ }
348
496
  if (!binding || typeof binding !== "object" || Array.isArray(binding)) {
349
- throw new Error(`${observableLabel}.persistentVar is required`);
497
+ throw new Error(`${observableLabel}.persistentVar must be an object`);
350
498
  }
351
499
  assertOnlyKeys(binding, ["scope", "name", "defaultValue"], `${observableLabel}.persistentVar`);
352
500
  if (typeof binding.scope !== "string" || !PERSISTENT_VAR_SCOPE_SET.has(binding.scope)) {
@@ -385,7 +533,7 @@ function validateProvidedContract(contract, index, packageTables) {
385
533
  if (typeof event.description !== "string") {
386
534
  throw new Error(`${eventLabel}.description is required`);
387
535
  }
388
- event.payloadFields = validateFields(event.payloadFields, `${eventLabel}.payloadFields`);
536
+ event.payloadFields = validateFields(event.payloadFields, `${eventLabel}.payloadFields`, strict);
389
537
  }
390
538
  if (contract.tools.length +
391
539
  contract.tables.length +
@@ -405,8 +553,23 @@ function validateProvidedContract(contract, index, packageTables) {
405
553
  if (!declaration || typeof declaration !== "object" || Array.isArray(declaration)) {
406
554
  throw new Error(`${declarationLabel} must be an object`);
407
555
  }
408
- assertOnlyKeys(declaration, ["contractId", "version"], declarationLabel);
556
+ assertOnlyKeys(declaration, ["contractId", "version", "definition"], declarationLabel);
409
557
  assertPeerId(declaration.contractId, `${declarationLabel}.contractId`);
558
+ if (declaration.definition !== undefined) {
559
+ // The target's definition, carried so the host can register it on a cold start.
560
+ if (typeof declaration.version !== "number") {
561
+ throw new Error(`${declarationLabel}.definition needs a single version`);
562
+ }
563
+ validateCarriedFields(declaration.definition, `${declarationLabel}.definition`, strict);
564
+ const checked = contract_definition_schema_1.contractDefinitionSchema.safeParse(declaration.definition);
565
+ if (!checked.success) {
566
+ throw new Error(`${declarationLabel}.definition is invalid: ${checked.error.message}`);
567
+ }
568
+ if (declaration.definition.contractId !== declaration.contractId ||
569
+ declaration.definition.version !== declaration.version) {
570
+ throw new Error(`${declarationLabel}.definition must be the declared contract version`);
571
+ }
572
+ }
410
573
  if (typeof declaration.version === "number") {
411
574
  assertContractVersion(declaration.version, `${declarationLabel}.version`);
412
575
  continue;
@@ -423,6 +586,34 @@ function validateProvidedContract(contract, index, packageTables) {
423
586
  throw new Error(`${declarationLabel}.version.from must not exceed .to`);
424
587
  }
425
588
  }
589
+ validateExposes(contract, label);
590
+ }
591
+ /** Check a provided contract's `exposes` list; the manifest checks that it names provided contracts. */
592
+ function validateExposes(contract, label) {
593
+ if (contract.exposes === undefined) {
594
+ return;
595
+ }
596
+ if (!Array.isArray(contract.exposes)) {
597
+ throw new Error(`${label}.exposes must be an array`);
598
+ }
599
+ const seen = new Set();
600
+ for (const [index, exposed] of contract.exposes.entries()) {
601
+ const exposedLabel = `${label}.exposes[${index}]`;
602
+ if (!exposed || typeof exposed !== "object" || Array.isArray(exposed)) {
603
+ throw new Error(`${exposedLabel} must be an object`);
604
+ }
605
+ assertOnlyKeys(exposed, ["contractId", "version"], exposedLabel);
606
+ assertPeerId(exposed.contractId, `${exposedLabel}.contractId`);
607
+ assertContractVersion(exposed.version, `${exposedLabel}.version`);
608
+ const key = `${exposed.contractId}@${exposed.version}`;
609
+ if (key === `${contract.contractId}@${contract.version}`) {
610
+ throw new Error(`${exposedLabel}: a contract can't expose itself`);
611
+ }
612
+ if (seen.has(key)) {
613
+ throw new Error(`${exposedLabel}: ${key} is exposed twice`);
614
+ }
615
+ seen.add(key);
616
+ }
426
617
  }
427
618
  function validateConsumedContract(dep, index) {
428
619
  const label = `consumes[${index}]`;
@@ -565,11 +756,17 @@ function looksLikeIsolatedPackageArtifact(bundle) {
565
756
  * Validate a wire-safe isolated package artifact. Does not evaluate
566
757
  * `providerSource`.
567
758
  *
759
+ * Fields keep only the keys this SDK knows: a host drops any other field key, since a newer
760
+ * SDK's keys only describe values further.
761
+ *
568
762
  * @param artifact - Parsed artifact object.
763
+ * @param options - `strict` refuses field keys this SDK doesn't know instead; the package build
764
+ * uses it, where an unknown key is a mistake.
569
765
  * @returns The same manifest after in-place field normalization.
570
766
  * @throws If identity, contracts, tools, or source are invalid.
571
767
  */
572
- function validateIsolatedPackageArtifact(artifact) {
768
+ function validateIsolatedPackageArtifact(artifact, options = {}) {
769
+ const strict = options.strict === true;
573
770
  if (!artifact || typeof artifact !== "object" || Array.isArray(artifact)) {
574
771
  throw new Error("Package artifact must be an object");
575
772
  }
@@ -586,8 +783,10 @@ function validateIsolatedPackageArtifact(artifact) {
586
783
  "tables",
587
784
  "network",
588
785
  "appNavs",
786
+ // Voice Skill v1's retired action aliases: an older manifest may carry them; ignored.
589
787
  "actionAliases",
590
788
  "executionMode",
789
+ "guestApi",
591
790
  ], "manifest");
592
791
  if (typeof providerSource !== "string" || providerSource.trim() === "") {
593
792
  throw new Error("Package artifact.providerSource must be a non-empty string");
@@ -597,6 +796,9 @@ function validateIsolatedPackageArtifact(artifact) {
597
796
  manifest.executionMode !== "privileged") {
598
797
  throw new Error('manifest.executionMode must be "sandbox" or "privileged"');
599
798
  }
799
+ if (manifest.guestApi !== undefined && manifest.guestApi !== 2 && manifest.guestApi !== 3) {
800
+ throw new Error("manifest.guestApi must be 2 or 3 when set");
801
+ }
600
802
  assertPeerId(manifest.packageId, "packageId");
601
803
  if (typeof manifest.version !== "string" || manifest.version.trim() === "") {
602
804
  throw new Error("package version is required");
@@ -607,36 +809,39 @@ function validateIsolatedPackageArtifact(artifact) {
607
809
  if (!Array.isArray(manifest.consumes)) {
608
810
  throw new Error("package.consumes must be an array");
609
811
  }
610
- validatePackageTables(manifest);
812
+ validatePackageTables(manifest, strict);
611
813
  const packageTables = manifest.tables ?? [];
612
814
  const providedKeys = new Set();
613
815
  for (const [index, contract] of manifest.provides.entries()) {
614
- validateProvidedContract(contract, index, packageTables);
816
+ validateProvidedContract(contract, index, packageTables, manifest.guestApi, strict);
615
817
  const key = `${contract.contractId}@${contract.version}`;
616
818
  if (providedKeys.has(key)) {
617
819
  throw new Error(`Duplicate provided contract ${key}`);
618
820
  }
619
821
  providedKeys.add(key);
620
822
  }
621
- for (const [index, dep] of manifest.consumes.entries()) {
622
- validateConsumedContract(dep, index);
623
- }
624
- if (manifest.actionAliases !== undefined) {
625
- if (!Array.isArray(manifest.actionAliases))
626
- throw new Error("actionAliases must be an array");
627
- const names = new Set();
628
- for (const alias of manifest.actionAliases) {
629
- if (!alias || typeof alias.name !== "string" || !alias.name || names.has(alias.name)) {
630
- throw new Error("Invalid or duplicate action alias");
823
+ for (const [index, contract] of manifest.provides.entries()) {
824
+ for (const target of contract.alsoImplements ?? []) {
825
+ // Legacy ranges may cover an explicitly provided version. Its own handlers take
826
+ // precedence in the registry; retain that behavior for existing hand-written manifests.
827
+ // A carried definition already requires a single version during contract validation.
828
+ if (typeof target.version !== "number")
829
+ continue;
830
+ const key = `${target.contractId}@${target.version}`;
831
+ if (providedKeys.has(key)) {
832
+ throw new Error(`provides[${index}] also implements ${key}, which the package provides explicitly`);
631
833
  }
632
- names.add(alias.name);
633
- const target = alias.target;
634
- if (!target ||
635
- !packageProvidesTool(manifest.provides, target.contractId, target.version, target.toolName)) {
636
- throw new Error(`Action alias ${alias.name} has no registered target`);
834
+ }
835
+ for (const exposed of contract.exposes ?? []) {
836
+ const key = `${exposed.contractId}@${exposed.version}`;
837
+ if (!providedKeys.has(key)) {
838
+ throw new Error(`provides[${index}] exposes ${key}, which the package doesn't provide`);
637
839
  }
638
840
  }
639
841
  }
842
+ for (const [index, dep] of manifest.consumes.entries()) {
843
+ validateConsumedContract(dep, index);
844
+ }
640
845
  manifest.appNavs = validateAppNavs(manifest.appNavs);
641
846
  validatePackageNetwork(manifest);
642
847
  return manifest;
@@ -716,6 +921,7 @@ function isolatedContractToDefinition(contract, packageTables) {
716
921
  name: observable.name,
717
922
  description: observable.description,
718
923
  valueType: observable.valueType,
924
+ ...(observable.value ? { value: observable.value } : {}),
719
925
  writable: observable.writable,
720
926
  }));
721
927
  const events = (contract.events ?? []).map((event) => ({