@peers-app/peers-sdk 0.27.4 → 0.28.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 (92) hide show
  1. package/dist/context/user-context.d.ts +7 -1
  2. package/dist/context/user-context.js +4 -9
  3. package/dist/contracts/contract-proxy.d.ts +5 -0
  4. package/dist/contracts/contract-proxy.js +2 -2
  5. package/dist/contracts/contract-resolvers.d.ts +2 -2
  6. package/dist/contracts/contract-resolvers.js +14 -15
  7. package/dist/contracts/contract-transport.d.ts +2 -1
  8. package/dist/contracts/contract-transport.js +24 -2
  9. package/dist/contracts/system/network-contracts.d.ts +1 -1
  10. package/dist/contracts/tool-catalog-service.js +1 -1
  11. package/dist/contracts/tool-catalog.js +6 -2
  12. package/dist/data/assistants.d.ts +3 -3
  13. package/dist/data/authority-write-checks.d.ts +42 -0
  14. package/dist/data/authority-write-checks.js +324 -0
  15. package/dist/data/change-tracking.d.ts +4 -4
  16. package/dist/data/create-group.js +1 -0
  17. package/dist/data/devices.d.ts +27 -4
  18. package/dist/data/devices.js +31 -0
  19. package/dist/data/group-authority.d.ts +111 -0
  20. package/dist/data/group-authority.js +281 -0
  21. package/dist/data/group-members.d.ts +8 -2
  22. package/dist/data/group-members.js +41 -18
  23. package/dist/data/group-permissions.d.ts +15 -31
  24. package/dist/data/group-permissions.js +25 -95
  25. package/dist/data/group-resign.d.ts +38 -0
  26. package/dist/data/group-resign.js +71 -0
  27. package/dist/data/groups.d.ts +11 -0
  28. package/dist/data/groups.js +31 -13
  29. package/dist/data/index.d.ts +3 -0
  30. package/dist/data/index.js +3 -0
  31. package/dist/data/invites.d.ts +14 -0
  32. package/dist/data/orm/index.d.ts +1 -0
  33. package/dist/data/orm/index.js +1 -0
  34. package/dist/data/orm/sql.data-source.d.ts +2 -2
  35. package/dist/data/orm/sql.data-source.js +8 -9
  36. package/dist/data/orm/table-container.js +14 -8
  37. package/dist/data/orm/write-checks.d.ts +89 -0
  38. package/dist/data/orm/write-checks.js +115 -0
  39. package/dist/data/package-table-identity.d.ts +6 -0
  40. package/dist/data/package-table-identity.js +51 -0
  41. package/dist/data/package-version-resolver.d.ts +15 -0
  42. package/dist/data/package-version-resolver.js +58 -3
  43. package/dist/data/package-versions.d.ts +20 -14
  44. package/dist/data/package-versions.js +15 -9
  45. package/dist/data/packages.d.ts +6 -1
  46. package/dist/data/packages.js +15 -20
  47. package/dist/data/persistent-vars.js +2 -39
  48. package/dist/data/sync-recovery-queue.d.ts +4 -4
  49. package/dist/data/sync-recovery-queue.js +7 -1
  50. package/dist/data/tool-access-approvals.d.ts +3 -3
  51. package/dist/data/user-permissions.d.ts +33 -2
  52. package/dist/data/user-permissions.js +87 -10
  53. package/dist/data/users.d.ts +49 -12
  54. package/dist/data/users.js +21 -1
  55. package/dist/data/workflows.d.ts +1 -1
  56. package/dist/device/binary-peer-connection-v2.d.ts +8 -16
  57. package/dist/device/binary-peer-connection-v2.js +15 -31
  58. package/dist/device/connection.d.ts +35 -44
  59. package/dist/device/connection.js +188 -186
  60. package/dist/device/device.d.ts +1 -8
  61. package/dist/device/device.js +1 -30
  62. package/dist/device/frame-cipher.d.ts +1 -1
  63. package/dist/device/frame-cipher.js +1 -1
  64. package/dist/device/get-trust-level-fn.js +3 -6
  65. package/dist/device/socket.type.d.ts +6 -12
  66. package/dist/device-pairing/types.d.ts +26 -26
  67. package/dist/identity/user-row-change.d.ts +3 -1
  68. package/dist/identity/user-row-change.js +33 -18
  69. package/dist/invites/group-approval.js +23 -24
  70. package/dist/invites/invite-messages.d.ts +250 -28
  71. package/dist/invites/invite-token.d.ts +18 -0
  72. package/dist/invites/invite-token.js +2 -0
  73. package/dist/keys.d.ts +7 -0
  74. package/dist/keys.js +27 -0
  75. package/dist/package-installer/package-installer.d.ts +1 -1
  76. package/dist/package-installer/package-installer.js +8 -3
  77. package/dist/package-installer/package-propagation.d.ts +2 -0
  78. package/dist/package-installer/package-propagation.js +20 -38
  79. package/dist/package-installer/package-remote-checker.js +15 -3
  80. package/dist/package-loader/isolated-artifact.js +0 -2
  81. package/dist/package-loader/package-loader.js +6 -0
  82. package/dist/types/peer-device.d.ts +2 -0
  83. package/dist/types/workflow.d.ts +2 -2
  84. package/package.json +1 -1
  85. package/dist/data/package-permissions.d.ts +0 -14
  86. package/dist/data/package-permissions.js +0 -26
  87. package/dist/data/package-version-permissions.d.ts +0 -14
  88. package/dist/data/package-version-permissions.js +0 -30
  89. package/dist/device/binary-peer-connection.d.ts +0 -58
  90. package/dist/device/binary-peer-connection.js +0 -516
  91. package/dist/device/streamed-socket.d.ts +0 -23
  92. package/dist/device/streamed-socket.js +0 -255
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.packageApprovalTables = exports.WRITE_NOT_AUTHORIZED = void 0;
4
+ exports.registerTableWriteCheck = registerTableWriteCheck;
5
+ exports.getTableWriteCheck = getTableWriteCheck;
6
+ exports.checkLocalSave = checkLocalSave;
7
+ exports.signingStamp = signingStamp;
8
+ exports.deletedSignedAt = deletedSignedAt;
9
+ exports.writeNotAuthorized = writeNotAuthorized;
10
+ /**
11
+ * Prefix of every refusal from a {@link TableWriteCheck}. Sync recovery parks a change refused
12
+ * with it, and checks it again only from local history, when the group's authority changes.
13
+ */
14
+ exports.WRITE_NOT_AUTHORIZED = "Write not authorized";
15
+ const writeChecks = new Map();
16
+ /**
17
+ * Register the {@link TableWriteCheck} for a table name. One per table; a second registration
18
+ * replaces the first.
19
+ *
20
+ * @param tableName - Table the check guards.
21
+ * @param check - The rule.
22
+ */
23
+ function registerTableWriteCheck(tableName, check) {
24
+ writeChecks.set(tableName, check);
25
+ }
26
+ /**
27
+ * The {@link TableWriteCheck} registered for a table, if any.
28
+ *
29
+ * @param tableName - Table name.
30
+ */
31
+ function getTableWriteCheck(tableName) {
32
+ return writeChecks.get(tableName);
33
+ }
34
+ /**
35
+ * Run `check` on a local save to `table`, as the host runs it on every synced row, so a local
36
+ * write and a synced write are judged by the same code. Nothing is checked in the personal
37
+ * context: only the user's own devices write it.
38
+ *
39
+ * @param table - The table being saved to.
40
+ * @param row - The row about to be stored.
41
+ * @param check - The table's rule.
42
+ */
43
+ async function checkLocalSave(table, row, check) {
44
+ if (!table.groupId)
45
+ return;
46
+ const { getUserContext } = await Promise.resolve().then(() => require("../../context/user-context-singleton"));
47
+ const recordId = row[table.primaryKeyName];
48
+ await check(await table.get(recordId), row, {
49
+ dataContext: (await getUserContext()).getDataContext(table.groupId),
50
+ recordId,
51
+ });
52
+ }
53
+ /**
54
+ * Tables whose approvals are bound to a context (`signedContextId`) and whose deletes are open,
55
+ * so a deleted row's signing time is retained ({@link deletedSignedAt}).
56
+ */
57
+ exports.packageApprovalTables = new Set(["Packages", "PackageVersions"]);
58
+ /**
59
+ * What a table's `signAndSave` (and a re-sign) puts under the signature: `signedAt`, now or
60
+ * just after the row on record when that was signed by a clock running ahead, so the new
61
+ * signature counts as the later one (and, for a package table, after the deleted row's
62
+ * retained stamp); for a package table also the context the approval is for.
63
+ *
64
+ * @param table - The table being signed for.
65
+ * @param row - The row being signed.
66
+ * @param current - A row already read from storage; membership repair may supply another id's row.
67
+ */
68
+ async function signingStamp(table, row, current) {
69
+ const { tableName, groupId, primaryKeyName } = table;
70
+ const recordId = row[primaryKeyName];
71
+ current ??= await table.get(recordId);
72
+ const packageTable = exports.packageApprovalTables.has(tableName);
73
+ let floor = current?.signedAt ?? 0;
74
+ if (packageTable && groupId && !current) {
75
+ const { getUserContext } = await Promise.resolve().then(() => require("../../context/user-context-singleton"));
76
+ const dataContext = (await getUserContext()).getDataContext(groupId);
77
+ floor = await deletedSignedAt(tableName, { dataContext, recordId });
78
+ }
79
+ return {
80
+ signedAt: Math.max(Date.now(), floor + 1),
81
+ ...(packageTable ? { signedContextId: groupId } : {}),
82
+ };
83
+ }
84
+ /**
85
+ * Read a deleted authority row's last signing time, optionally retaining the stored row's
86
+ * stamp before deletion. Device-local personal storage survives deletion and log compaction.
87
+ * Only pass `existing` from storage, never an unverified incoming candidate.
88
+ */
89
+ async function deletedSignedAt(tableName, context, existing) {
90
+ if (!context.dataContext.groupId)
91
+ return 0;
92
+ const { PersistentVars } = await Promise.resolve().then(() => require("../persistent-vars"));
93
+ const vars = PersistentVars(context.dataContext.userContext.userDataContext);
94
+ const name = `authorityOrder:${context.dataContext.groupId}:${tableName}:${context.recordId}`;
95
+ const remembered = await vars.findOne({ name, scope: "device" });
96
+ const floor = Math.max(remembered?.value?.value ?? 0, existing?.signedAt ?? 0);
97
+ if (floor > (remembered?.value?.value ?? 0)) {
98
+ await vars.save({
99
+ persistentVarId: remembered?.persistentVarId ?? "",
100
+ name,
101
+ scope: "device",
102
+ value: { value: floor },
103
+ });
104
+ }
105
+ return floor;
106
+ }
107
+ /**
108
+ * An error refusing a write, carrying the {@link WRITE_NOT_AUTHORIZED} prefix.
109
+ *
110
+ * @param tableName - Table the write was for.
111
+ * @param reason - Why, for logs and the sync recovery queue.
112
+ */
113
+ function writeNotAuthorized(tableName, reason) {
114
+ return new Error(`${exports.WRITE_NOT_AUTHORIZED}: ${tableName}: ${reason}`);
115
+ }
@@ -1,3 +1,4 @@
1
+ import type { ITableDefinitionRecord } from "./table-definitions-table";
1
2
  /**
2
3
  * Validate a guest-supplied isolated-table or field identifier before it can
3
4
  * reach ORM-generated SQL.
@@ -44,3 +45,8 @@ export declare function packageScopedTableName(packageId: string, logicalName: s
44
45
  * @throws When package or table identity is invalid.
45
46
  */
46
47
  export declare function packageScopedTableId(packageId: string, logicalName: string): string;
48
+ /**
49
+ * Validate synced schema metadata before it can name a table, column, or index in SQL.
50
+ * A schema cannot impersonate a system table or supply SQL expressions as identifiers.
51
+ */
52
+ export declare function validateTableDefinitionRecord(record: ITableDefinitionRecord): void;
@@ -4,6 +4,7 @@ exports.validateIsolatedSqlIdentifier = validateIsolatedSqlIdentifier;
4
4
  exports.validatePackageTableName = validatePackageTableName;
5
5
  exports.packageScopedTableName = packageScopedTableName;
6
6
  exports.packageScopedTableId = packageScopedTableId;
7
+ exports.validateTableDefinitionRecord = validateTableDefinitionRecord;
7
8
  const utils_1 = require("../utils");
8
9
  const ISOLATED_SQL_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
9
10
  /**
@@ -69,3 +70,53 @@ function packageScopedTableName(packageId, logicalName) {
69
70
  function packageScopedTableId(packageId, logicalName) {
70
71
  return (0, utils_1.deterministicTableId)(packageScopedTableName(packageId, logicalName));
71
72
  }
73
+ /**
74
+ * Validate synced schema metadata before it can name a table, column, or index in SQL.
75
+ * A schema cannot impersonate a system table or supply SQL expressions as identifiers.
76
+ */
77
+ function validateTableDefinitionRecord(record) {
78
+ const meta = record.metaData;
79
+ if (!meta ||
80
+ !(0, utils_1.isid)(record.tableId) ||
81
+ meta.tableId !== record.tableId ||
82
+ (meta.packageId !== undefined && !(0, utils_1.isid)(meta.packageId))) {
83
+ throw new Error("Invalid table definition identity");
84
+ }
85
+ validateIsolatedSqlIdentifier(meta.name, "Table name");
86
+ validateIsolatedSqlIdentifier(meta.primaryKeyName, "Primary key");
87
+ if (record.name !== (0, utils_1.getFullTableName)(meta))
88
+ throw new Error("Table definition name mismatch");
89
+ if (!Number.isSafeInteger(record.versionNumber) ||
90
+ record.versionNumber < 0 ||
91
+ record.versionNumber !== (meta.versionNumber ?? 0)) {
92
+ throw new Error("Invalid table definition version");
93
+ }
94
+ if (meta.localOnly !== undefined && typeof meta.localOnly !== "boolean") {
95
+ throw new Error("Invalid table localOnly policy");
96
+ }
97
+ if (!Array.isArray(meta.fields) || !meta.fields.length)
98
+ throw new Error("Missing table fields");
99
+ const names = new Set();
100
+ for (const field of meta.fields) {
101
+ validateIsolatedSqlIdentifier(field.name, "Column name");
102
+ if (names.has(field.name))
103
+ throw new Error("Duplicate table column");
104
+ names.add(field.name);
105
+ }
106
+ if (!names.has(meta.primaryKeyName))
107
+ throw new Error("Missing primary key column");
108
+ if (meta.indexes !== undefined && !Array.isArray(meta.indexes))
109
+ throw new Error("Invalid indexes");
110
+ for (const index of meta.indexes ?? []) {
111
+ if (!Array.isArray(index.fields) || !index.fields.length)
112
+ throw new Error("Missing index fields");
113
+ for (const field of index.fields) {
114
+ const name = typeof field === "string" ? field : field.name;
115
+ if (!names.has(name))
116
+ throw new Error("Index must name a declared column");
117
+ if (typeof field !== "string" && field.order !== "ASC" && field.order !== "DESC") {
118
+ throw new Error("Invalid index order");
119
+ }
120
+ }
121
+ }
122
+ }
@@ -18,6 +18,8 @@ import { type PersistentVar } from "./persistent-vars";
18
18
  export interface IDevicePackagePrefs {
19
19
  /** Which PV this device currently runs */
20
20
  activePackageVersionId?: string;
21
+ /** Tag when this device explicitly selected the version; a later retag cannot opt it into dev. */
22
+ activePackageVersionTag?: string;
21
23
  /** Newer selected PV temporarily bypassed because this host could not run it. */
22
24
  compatibilityFallbackForPackageVersionId?: string;
23
25
  /** If true, never auto-update this package */
@@ -53,6 +55,19 @@ export declare function packagePrefsVar(packageId: string, dataContext?: DataCon
53
55
  * omitted the var tracks the user's default group context.
54
56
  */
55
57
  export declare function updatePackagePrefs(packageId: string, prefs: Partial<IDevicePackagePrefs>, dataContext?: DataContext): Promise<void>;
58
+ /**
59
+ * Check a selected version before evaluating any of its runtime, route or UI bundles.
60
+ * Group releases and shared selections must still have authorized signatures even when
61
+ * already on disk. Dev builds require an explicit device selection made while tagged dev.
62
+ *
63
+ * @param localDev - A host explicitly loading a local development checkout.
64
+ */
65
+ export declare function assertPackageVersionRunnable(pkg: IPackage, pv: IPackageVersion, dataContext: DataContext, localDev?: boolean): Promise<void>;
66
+ /**
67
+ * Verify fetched bundle bytes against the hash in the authorized version. Files rows may be
68
+ * changed by group members, so their own chunk hashes alone cannot authorize executable code.
69
+ */
70
+ export declare function assertPackageBundleIntegrity(pv: IPackageVersion, kind: "package" | "routes" | "ui", contents: string | Uint8Array): void;
56
71
  /**
57
72
  * Effective device-local preferences for a package after merging the device's
58
73
  * raw prefs with the group-level `IPackage` defaults. Every field is resolved
@@ -10,12 +10,15 @@
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.packagePrefsVar = packagePrefsVar;
12
12
  exports.updatePackagePrefs = updatePackagePrefs;
13
+ exports.assertPackageVersionRunnable = assertPackageVersionRunnable;
14
+ exports.assertPackageBundleIntegrity = assertPackageBundleIntegrity;
13
15
  exports.getEffectivePackagePrefs = getEffectivePackagePrefs;
14
16
  exports.resolveDevicePackageVersion = resolveDevicePackageVersion;
15
17
  exports.hasDeviceFollowOverride = hasDeviceFollowOverride;
16
18
  exports.readStoredPackageLocalPath = readStoredPackageLocalPath;
17
19
  const user_context_singleton_1 = require("../context/user-context-singleton");
18
20
  const assistants_1 = require("./assistants");
21
+ const file_types_1 = require("./files/file.types");
19
22
  const group_member_roles_1 = require("./group-member-roles");
20
23
  const group_permissions_1 = require("./group-permissions");
21
24
  const package_versions_1 = require("./package-versions");
@@ -62,6 +65,13 @@ async function updatePackagePrefs(packageId, prefs, dataContext) {
62
65
  effectivePrefs = rest;
63
66
  }
64
67
  const next = { ...existing, ...effectivePrefs };
68
+ if ("activePackageVersionId" in effectivePrefs) {
69
+ const dc = dataContext ?? (await (0, user_context_singleton_1.getUserContext)()).defaultDataContext();
70
+ const selected = effectivePrefs.activePackageVersionId
71
+ ? await (0, package_versions_1.PackageVersions)(dc).get(effectivePrefs.activePackageVersionId)
72
+ : undefined;
73
+ next.activePackageVersionTag = selected?.versionTag;
74
+ }
65
75
  for (const key of Object.keys(next)) {
66
76
  if (next[key] === undefined) {
67
77
  delete next[key];
@@ -69,6 +79,47 @@ async function updatePackagePrefs(packageId, prefs, dataContext) {
69
79
  }
70
80
  pvar(next);
71
81
  }
82
+ /**
83
+ * Check a selected version before evaluating any of its runtime, route or UI bundles.
84
+ * Group releases and shared selections must still have authorized signatures even when
85
+ * already on disk. Dev builds require an explicit device selection made while tagged dev.
86
+ *
87
+ * @param localDev - A host explicitly loading a local development checkout.
88
+ */
89
+ async function assertPackageVersionRunnable(pkg, pv, dataContext, localDev = false) {
90
+ if (!dataContext.groupId)
91
+ return;
92
+ if (pv.packageId !== pkg.packageId)
93
+ throw new Error("Package version belongs to another package");
94
+ const { isPackageRowAdminSigned } = await Promise.resolve().then(() => require("./authority-write-checks"));
95
+ const { getTableWriteCheck } = await Promise.resolve().then(() => require("./orm/write-checks"));
96
+ await getTableWriteCheck("PackageVersions")?.(undefined, pv, {
97
+ dataContext,
98
+ recordId: pv.packageVersionId,
99
+ });
100
+ if (!localDev) {
101
+ const prefs = packagePrefsVar(pkg.packageId, dataContext);
102
+ await prefs.loadingPromise;
103
+ const deviceSelected = prefs()?.activePackageVersionId === pv.packageVersionId;
104
+ if (!deviceSelected && !(await isPackageRowAdminSigned(pkg, dataContext))) {
105
+ throw new Error("A group package selection needs an Admin's signature before it can run");
106
+ }
107
+ if (pv.versionTag === "dev" &&
108
+ (!deviceSelected || prefs()?.activePackageVersionTag !== "dev")) {
109
+ throw new Error("Activate this dev version on this device before running it");
110
+ }
111
+ }
112
+ }
113
+ /**
114
+ * Verify fetched bundle bytes against the hash in the authorized version. Files rows may be
115
+ * changed by group members, so their own chunk hashes alone cannot authorize executable code.
116
+ */
117
+ function assertPackageBundleIntegrity(pv, kind, contents) {
118
+ const expected = pv[`${kind}BundleFileHash`];
119
+ if (!expected || (0, file_types_1.computeFileHash)(contents) !== expected) {
120
+ throw new Error(`Package ${kind} bundle hash does not match the authorized version`);
121
+ }
122
+ }
72
123
  /**
73
124
  * Pure function: merge raw device prefs with the group-level `IPackage`
74
125
  * defaults to produce fully-resolved effective preferences. Used by the
@@ -221,7 +272,7 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
221
272
  const allVersions = await (0, package_versions_1.PackageVersions)(dataContext).list({ packageId: pkg.packageId });
222
273
  let bestPv = activePv;
223
274
  for (const candidate of allVersions) {
224
- if (candidate.packageVersionId === effectiveActivePvId)
275
+ if (candidate.packageVersionId === effectiveActivePvId || candidate.versionTag === "dev")
225
276
  continue;
226
277
  const tagOk = (0, package_versions_1.doesTagMatch)(activePv?.versionTag, candidate.versionTag, followTags, undefined);
227
278
  if (!tagOk)
@@ -297,7 +348,10 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
297
348
  const role = await (0, group_permissions_1.getUserRole)(groupId, userCtx.userId);
298
349
  if (role >= group_member_roles_1.GroupMemberRole.Admin) {
299
350
  const freshPkg = await (0, packages_1.Packages)(dataContext).get(pkg.packageId);
300
- if (freshPkg) {
351
+ const { isPackageRowAdminSigned } = await Promise.resolve().then(() => require("./authority-write-checks"));
352
+ // Loading can span a sync that deletes and re-adds this package. Advancing a release
353
+ // must not endorse a Writer's replacement publisher key, URL or follow policy.
354
+ if (freshPkg && (await isPackageRowAdminSigned(freshPkg, dataContext))) {
301
355
  if (freshPkg.activePackageVersionId !== bestPv.packageVersionId) {
302
356
  freshPkg.activePackageVersionId = bestPv.packageVersionId;
303
357
  await (0, packages_1.Packages)(dataContext).signAndSave(freshPkg);
@@ -326,7 +380,8 @@ async function loadOlderCompatiblePackageVersion(pkg, dataContext, unavailableVe
326
380
  const versions = await (0, package_versions_1.PackageVersions)(dataContext).list({ packageId: pkg.packageId });
327
381
  const candidates = versions
328
382
  .filter((candidate) => {
329
- if (candidate.packageVersionId === unavailableVersion.packageVersionId) {
383
+ if (candidate.packageVersionId === unavailableVersion.packageVersionId ||
384
+ candidate.versionTag === "dev") {
330
385
  return false;
331
386
  }
332
387
  if ((0, package_versions_1.isNewerVersion)(unavailableVersion.version, candidate.version)) {
@@ -48,63 +48,69 @@ declare const schema: z.ZodObject<{
48
48
  by: string;
49
49
  }>, "many">>;
50
50
  signature: z.ZodString;
51
+ signedContextId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
52
+ signedAt: z.ZodOptional<z.ZodNumber>;
51
53
  createdBy: z.ZodEffects<z.ZodString, string, string>;
52
54
  createdAt: z.ZodString;
53
55
  }, "strip", z.ZodTypeAny, {
54
56
  version: string;
55
57
  signature: string;
56
58
  packageId: string;
57
- createdAt: string;
58
- createdBy: string;
59
59
  packageVersionId: string;
60
60
  packageVersionHash: string;
61
61
  packageBundleFileId: string;
62
62
  packageBundleFileHash: string;
63
+ createdBy: string;
64
+ createdAt: string;
63
65
  history?: {
64
66
  at: string;
65
67
  action: string;
66
68
  signature: string;
67
69
  by: string;
68
70
  }[] | undefined;
71
+ signedAt?: number | undefined;
72
+ signedContextId?: string | undefined;
73
+ versionTag?: string | undefined;
74
+ routesBundleFileId?: string | undefined;
75
+ routesBundleFileHash?: string | undefined;
76
+ uiBundleFileId?: string | undefined;
77
+ uiBundleFileHash?: string | undefined;
69
78
  appNavs?: {
70
79
  name: string;
71
80
  iconClassName: string;
72
81
  navigationPath: string;
73
82
  displayName?: string | undefined;
74
83
  }[] | undefined;
75
- versionTag?: string | undefined;
76
- routesBundleFileId?: string | undefined;
77
- routesBundleFileHash?: string | undefined;
78
- uiBundleFileId?: string | undefined;
79
- uiBundleFileHash?: string | undefined;
80
84
  packageAuthorSignature?: string | undefined;
81
85
  }, {
82
86
  version: string;
83
87
  signature: string;
84
88
  packageId: string;
85
- createdAt: string;
86
- createdBy: string;
87
89
  packageVersionId: string;
88
90
  packageVersionHash: string;
89
91
  packageBundleFileId: string;
90
92
  packageBundleFileHash: string;
93
+ createdBy: string;
94
+ createdAt: string;
91
95
  history?: {
92
96
  at: string;
93
97
  action: string;
94
98
  signature: string;
95
99
  by: string;
96
100
  }[] | undefined;
101
+ signedAt?: number | undefined;
102
+ signedContextId?: string | undefined;
103
+ versionTag?: string | undefined;
104
+ routesBundleFileId?: string | undefined;
105
+ routesBundleFileHash?: string | undefined;
106
+ uiBundleFileId?: string | undefined;
107
+ uiBundleFileHash?: string | undefined;
97
108
  appNavs?: {
98
109
  name: string;
99
110
  iconClassName: string;
100
111
  navigationPath: string;
101
112
  displayName?: string | undefined;
102
113
  }[] | undefined;
103
- versionTag?: string | undefined;
104
- routesBundleFileId?: string | undefined;
105
- routesBundleFileHash?: string | undefined;
106
- uiBundleFileId?: string | undefined;
107
- uiBundleFileHash?: string | undefined;
108
114
  packageAuthorSignature?: string | undefined;
109
115
  }>;
110
116
  /** A signed package build and its bundle hashes, tag, and lifecycle history. */
@@ -45,11 +45,12 @@ const context_1 = require("../context");
45
45
  const keys_1 = require("../keys");
46
46
  const app_nav_1 = require("../types/app-nav");
47
47
  const zod_types_1 = require("../types/zod-types");
48
+ const authority_write_checks_1 = require("./authority-write-checks");
48
49
  const decorators_1 = require("./orm/decorators");
49
50
  const table_1 = require("./orm/table");
50
51
  const table_definitions_system_1 = require("./orm/table-definitions.system");
51
52
  const types_1 = require("./orm/types");
52
- const package_version_permissions_1 = require("./package-version-permissions");
53
+ const write_checks_1 = require("./orm/write-checks");
53
54
  const schema = zod_1.z.object({
54
55
  packageVersionId: zod_types_1.zodPeerId,
55
56
  packageId: zod_types_1.zodPeerId.describe("The package this version belongs to"),
@@ -81,6 +82,13 @@ const schema = zod_1.z.object({
81
82
  .optional()
82
83
  .describe("Audit trail of lifecycle events for this version"),
83
84
  signature: zod_1.z.string().describe("The signed hash of this data excluding the signature itself"),
85
+ signedContextId: zod_types_1.zodPeerId
86
+ .optional()
87
+ .describe("The context this version approval was signed for"),
88
+ signedAt: zod_1.z
89
+ .number()
90
+ .optional()
91
+ .describe("When the signer signed this row (ms since epoch), under the signature: a copy of an earlier row can't replace a later one"),
84
92
  createdBy: zod_types_1.zodPeerId.describe("The user who created this version"),
85
93
  createdAt: zod_1.z.string().describe("ISO timestamp of when this version was created"),
86
94
  });
@@ -115,14 +123,9 @@ let PackageVersionsTable = (() => {
115
123
  return this.save(packageVersion, opts);
116
124
  }
117
125
  async save(packageVersion, opts) {
118
- if (PackageVersionsTable.isPassthrough) {
119
- return super.save(packageVersion, opts);
120
- }
121
- if (!this.groupId) {
122
- // users can do whatever they want to package versions in their personal space
123
- return super.save(packageVersion, opts);
126
+ if (!PackageVersionsTable.isPassthrough) {
127
+ await (0, write_checks_1.checkLocalSave)(this, packageVersion, authority_write_checks_1.checkPackageVersion);
124
128
  }
125
- await (0, package_version_permissions_1.verifyPackageVersionSignature)(packageVersion, this.groupId);
126
129
  return super.save(packageVersion, opts);
127
130
  }
128
131
  async signAndSave(packageVersion, opts) {
@@ -130,7 +133,10 @@ let PackageVersionsTable = (() => {
130
133
  if (!sign) {
131
134
  throw new Error("Package version signing is not enabled. Call PackageVersionsTable.enablePackageVersionSigning(fn) to enable it.");
132
135
  }
133
- packageVersion = await sign.call(PackageVersionsTable, packageVersion);
136
+ packageVersion = await sign.call(PackageVersionsTable, {
137
+ ...packageVersion,
138
+ ...(await (0, write_checks_1.signingStamp)(this, packageVersion)),
139
+ });
134
140
  return this.save(packageVersion, opts);
135
141
  }
136
142
  /** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
@@ -15,6 +15,8 @@ declare const schema: z.ZodObject<{
15
15
  updateUrl: z.ZodOptional<z.ZodString>;
16
16
  publishPublicKey: z.ZodString;
17
17
  signature: z.ZodString;
18
+ signedContextId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
19
+ signedAt: z.ZodOptional<z.ZodNumber>;
18
20
  }, "strip", z.ZodTypeAny, {
19
21
  name: string;
20
22
  description: string;
@@ -23,6 +25,8 @@ declare const schema: z.ZodObject<{
23
25
  createdBy: string;
24
26
  publishPublicKey: string;
25
27
  disabled?: boolean | undefined;
28
+ signedAt?: number | undefined;
29
+ signedContextId?: string | undefined;
26
30
  remoteRepo?: string | undefined;
27
31
  activePackageVersionId?: string | undefined;
28
32
  versionFollowRange?: "pinned" | "patch" | "minor" | "latest" | undefined;
@@ -36,6 +40,8 @@ declare const schema: z.ZodObject<{
36
40
  createdBy: string;
37
41
  publishPublicKey: string;
38
42
  disabled?: boolean | undefined;
43
+ signedAt?: number | undefined;
44
+ signedContextId?: string | undefined;
39
45
  remoteRepo?: string | undefined;
40
46
  activePackageVersionId?: string | undefined;
41
47
  versionFollowRange?: "pinned" | "patch" | "minor" | "latest" | undefined;
@@ -53,7 +59,6 @@ export declare class PackagesTable extends Table<IPackage> {
53
59
  signAndSave(packageObj: IPackage, opts?: ISaveOptions): Promise<IPackage>;
54
60
  /** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
55
61
  static enablePackageSigning(fn: (packageObj: IPackage) => IPackage): void;
56
- delete(packageId: string): Promise<void>;
57
62
  }
58
63
  /** Returns the Packages table for the given context or the current default context. */
59
64
  export declare function Packages(dataContext?: DataContext): PackagesTable;
@@ -39,11 +39,12 @@ exports.Packages = Packages;
39
39
  const zod_1 = require("zod");
40
40
  const context_1 = require("../context");
41
41
  const zod_types_1 = require("../types/zod-types");
42
+ const authority_write_checks_1 = require("./authority-write-checks");
42
43
  const decorators_1 = require("./orm/decorators");
43
44
  const table_1 = require("./orm/table");
44
45
  const table_definitions_system_1 = require("./orm/table-definitions.system");
45
46
  const types_1 = require("./orm/types");
46
- const package_permissions_1 = require("./package-permissions");
47
+ const write_checks_1 = require("./orm/write-checks");
47
48
  const persistent_vars_1 = require("./persistent-vars");
48
49
  const schema = zod_1.z.object({
49
50
  packageId: zod_types_1.zodPeerId,
@@ -75,6 +76,13 @@ const schema = zod_1.z.object({
75
76
  .describe("Ed25519 public key (base64url) of the package publisher. " +
76
77
  "Set at package creation; used as TOFU anchor for verifying packageAuthorSignature on versions."),
77
78
  signature: zod_1.z.string().describe("The signed hash of this data excluding the signature itself"),
79
+ signedContextId: zod_types_1.zodPeerId
80
+ .optional()
81
+ .describe("The context this package approval was signed for"),
82
+ signedAt: zod_1.z
83
+ .number()
84
+ .optional()
85
+ .describe("When the signer signed this row (ms since epoch), under the signature: a copy of an earlier row can't replace a later one"),
78
86
  });
79
87
  const metaData = {
80
88
  name: "Packages",
@@ -106,14 +114,8 @@ let PackagesTable = (() => {
106
114
  return this.save(packageObj, opts);
107
115
  }
108
116
  async save(packageObj, opts) {
109
- if (PackagesTable.isPassthrough) {
110
- return super.save(packageObj, opts);
111
- }
112
- if (!this.groupId) {
113
- // users can do whatever they want to packages in their personal space
114
- return super.save(packageObj, opts);
115
- }
116
- await (0, package_permissions_1.verifyPackageSignature)(packageObj, this.groupId);
117
+ if (!PackagesTable.isPassthrough)
118
+ await (0, write_checks_1.checkLocalSave)(this, packageObj, authority_write_checks_1.checkPackageWrite);
117
119
  return super.save(packageObj, opts);
118
120
  }
119
121
  async signAndSave(packageObj, opts) {
@@ -121,23 +123,16 @@ let PackagesTable = (() => {
121
123
  if (!sign) {
122
124
  throw new Error("Package signing is not enabled. Call PackagesTable.enablePackageSigning(fn) to enable it.");
123
125
  }
124
- packageObj = await sign.call(PackagesTable, packageObj);
126
+ packageObj = await sign.call(PackagesTable, {
127
+ ...packageObj,
128
+ ...(await (0, write_checks_1.signingStamp)(this, packageObj)),
129
+ });
125
130
  return this.save(packageObj, opts);
126
131
  }
127
132
  /** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
128
133
  static enablePackageSigning(fn) {
129
134
  signingGlobals[signingKey] = fn;
130
135
  }
131
- async delete(packageId) {
132
- // anyone (with write permissions) can delete packages since that isn't a security risk (unless something else is relying on a package to be present?)
133
- return super.delete(packageId);
134
- // const userContext = await getUserContext();
135
- // const userContextPackages = Packages(userContext.userDataContext);
136
- // if (userContextPackages === this) {
137
- // return super.delete(packageId);
138
- // }
139
- // throw new Error('Only deleting packages from the user context Packages table is allowed');
140
- }
141
136
  constructor() {
142
137
  super(...arguments);
143
138
  __runInitializers(this, _instanceExtraInitializers);
@@ -373,54 +373,17 @@ function persistentVarFactory(name, opts) {
373
373
  }
374
374
  return userContext.userDataContext;
375
375
  }
376
- /**
377
- * Before storage was unified in the personal DB, a `groupDevice` /
378
- * `groupUser` var created with an explicit group `dataContext` was
379
- * written to that group's DB. Return that legacy location so an
380
- * existing value can be migrated forward, or `undefined` when there is
381
- * no distinct legacy location for this variable.
382
- */
383
- function getLegacyStorageDataContext() {
384
- if (scope !== "groupDevice" && scope !== "groupUser") {
385
- return undefined;
386
- }
387
- const pinned = opts?.dataContext;
388
- if (!pinned || pinned.dataContextId === userContext.userDataContext.dataContextId) {
389
- return undefined;
390
- }
391
- return pinned;
392
- }
393
376
  function getVarNameInDb() {
394
377
  const dc = opts?.dataContext || userContext.defaultDataContext();
395
378
  return (0, persistent_var_identity_1.persistentVarStorageName)(name, scope, dc?.dataContextId);
396
379
  }
397
- /**
398
- * Copy a record left in a group DB by the pre-unification storage rule
399
- * into the personal DB. Secrets are skipped because their ciphertext is
400
- * bound to the group key and cannot be moved verbatim. The legacy row is
401
- * left in place: older builds on other devices may still read it, and
402
- * the personal copy is authoritative from here on.
403
- */
404
- async function migrateLegacyRec(table, name) {
405
- const legacyContext = getLegacyStorageDataContext();
406
- if (!legacyContext) {
407
- return undefined;
408
- }
409
- const legacyRec = await PersistentVars(legacyContext).findOne({ name });
410
- if (!legacyRec || legacyRec.scope !== scope || legacyRec.isSecret) {
411
- return undefined;
412
- }
413
- const { persistentVarId: _legacyId, ...rest } = legacyRec;
414
- return table.save({ ...rest, persistentVarId: "" }, { weakInsert: true });
415
- }
416
380
  async function loadRecFromDb() {
417
381
  const dc = getStorageDataContext();
418
382
  const table = PersistentVars(dc);
419
383
  const name = getVarNameInDb();
420
384
  let dbRec = await table.findOne({ name });
421
- if (!dbRec) {
422
- dbRec = await migrateLegacyRec(table, name);
423
- }
385
+ // Group rows are member-writable. They cannot bootstrap personal/device settings,
386
+ // including explicit permission to run a package's dev version on this device.
424
387
  // Preserve an existing secret unless the caller explicitly changes that setting.
425
388
  if (dbRec?.isSecret !== undefined && opts.isSecret === undefined) {
426
389
  isSecret = dbRec.isSecret;