@peers-app/peers-sdk 0.25.10 → 0.25.12

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.
@@ -15,7 +15,7 @@ export { createRemoteContractEndpoint, getRemoteContractTransportFactory, type I
15
15
  export { DEVICE_OPERATIONS_CONTRACT_VERSION, DEVICE_OPERATIONS_MAX_QUERY_LENGTH, DEVICE_OPERATIONS_MAX_RESULT_BYTES, DEVICE_OPERATIONS_MAX_ROWS, DEVICE_OPERATIONS_MAX_TOOLS, type DeviceOperationCapability, deviceOperationsProviderPackageId, deviceOperationsToolIds, getDeviceOperationsContractDefinition, type IDeviceDatabaseQueryInput, type IDeviceDatabaseQueryResult, type IDeviceOperationStatus, type IDeviceToolListInput, type IDeviceToolLookupInput, type IDeviceToolLookupResult, type IDeviceToolRunInput, } from "./system/device-operations-contract";
16
16
  export { getIdentityContractDefinition, IDENTITY_CONTRACT_VERSION, type IIdentityConsumer, type IIdentitySessionInfo, } from "./system/identity-contract";
17
17
  export { getInvitesContractDefinition, type IAcceptInviteResult, type ICreatedInvite, type IInvitePreview, type IInvitesConsumer, INVITES_CONTRACT_VERSION, } from "./system/invites-contract";
18
- export { getLocalFoldersContractDefinition, type ILocalFolderChange, type ILocalFolderChangedEvent, type ILocalFolderEntry, type ILocalFolderFileBlocks, type ILocalFolderFileBlocksInput, type ILocalFolderGrantInfo, type ILocalFolderIndexInput, type ILocalFolderJobFinishedEvent, type ILocalFolderJobHandle, type ILocalFolderJobInfo, type ILocalFolderJobProgressEvent, type ILocalFolderListInput, type ILocalFolderMoveInput, type ILocalFolderPathInput, type ILocalFolderPurgeTrashInput, type ILocalFolderReadRangeInput, type ILocalFolderReadRangeResult, type ILocalFolderRequestGrantInput, type ILocalFolderStatInput, LOCAL_FOLDERS_CHUNK_AVG, LOCAL_FOLDERS_CHUNK_MAX, LOCAL_FOLDERS_CHUNK_MIN, LOCAL_FOLDERS_CONTRACT_VERSION, LOCAL_FOLDERS_JOB_TTL_MS, LOCAL_FOLDERS_READ_RANGE_MAX, LOCAL_FOLDERS_RESERVED_DIR, type LocalFolderChangeKind, type LocalFolderEntryKind, type LocalFolderMode, localFoldersProviderPackageId, } from "./system/local-folders-contract";
18
+ export { getLocalFoldersContractDefinition, type ILocalFolderAssembleInput, type ILocalFolderAssembleResult, type ILocalFolderChange, type ILocalFolderChangedEvent, type ILocalFolderEntry, type ILocalFolderFileBlocks, type ILocalFolderFileBlocksInput, type ILocalFolderGrantInfo, type ILocalFolderIndexInput, type ILocalFolderIndexResult, type ILocalFolderJobFinishedEvent, type ILocalFolderJobHandle, type ILocalFolderJobInfo, type ILocalFolderJobProgressEvent, type ILocalFolderListInput, type ILocalFolderManifestBlock, type ILocalFolderMoveInput, type ILocalFolderPathInput, type ILocalFolderPurgeTrashInput, type ILocalFolderReadRangeInput, type ILocalFolderReadRangeResult, type ILocalFolderRequestGrantInput, type ILocalFolderStatInput, LOCAL_FOLDERS_ASSEMBLE_STALL_MS, LOCAL_FOLDERS_CHUNK_AVG, LOCAL_FOLDERS_CHUNK_MAX, LOCAL_FOLDERS_CHUNK_MIN, LOCAL_FOLDERS_CONTRACT_VERSION, LOCAL_FOLDERS_GETBLOCKS_MAX_BLOCKS, LOCAL_FOLDERS_GETBLOCKS_MAX_BYTES, LOCAL_FOLDERS_HAVE_BATCH, LOCAL_FOLDERS_JOB_TTL_MS, LOCAL_FOLDERS_READ_RANGE_MAX, LOCAL_FOLDERS_RESERVED_DIR, type LocalFolderChangeKind, type LocalFolderEntryKind, type LocalFolderJobKind, type LocalFolderMode, localFoldersProviderPackageId, } from "./system/local-folders-contract";
19
19
  export { getLogsContractDefinition, type ILogsConsumer, LOGS_CONTRACT_VERSION, resolveLogsContract, } from "./system/logs-contract";
20
20
  export * from "./system/network-contracts";
21
21
  export { getToolAccessControlContractDefinition, type IToolAccessControlConsumer, TOOL_ACCESS_CONTROL_CONTRACT_VERSION, } from "./system/tool-access-control-contract";
@@ -15,7 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.DEVICE_OPERATIONS_MAX_ROWS = exports.DEVICE_OPERATIONS_MAX_RESULT_BYTES = exports.DEVICE_OPERATIONS_MAX_QUERY_LENGTH = exports.DEVICE_OPERATIONS_CONTRACT_VERSION = exports.setRemoteContractTransportFactory = exports.resolveRemoteTargetEndpoint = exports.getRemoteContractTransportFactory = exports.createRemoteContractEndpoint = exports.ContractRegistry = exports.PersistentContractRegistry = exports.waitForHostContractTransport = exports.subscribeHostContractTransport = exports.registerHostContractTransport = exports.getHostContractTransport = exports.hostContractConsumer = exports.setSchemaToFieldsFn = exports.extractToolShape = exports.extractTableShape = exports.createLocalContractConsumer = exports.contractsSchema = exports.contractsMetaData = exports.ContractsTable = exports.Contracts = exports.inProcessTransportPair = exports.connectionContractTransport = exports.CONTRACT_REQUEST_CHANNEL = exports.CONTRACT_NOTIFY_CHANNEL = exports.verifyRemoteContractIdentity = exports.registerContractResolver = exports.isSelfTrustedContractCaller = exports.handleContractCall = exports.getContractResolver = exports.createUserContextContractProviderRouter = exports.createLocalUserContextContractProviderRouter = exports.authorizeRemoteContractCall = exports.sameUserContractPermissionCheck = exports.createRemoteContractConsumer = exports.createContractProviderSession = exports.createContractProviderEndpoint = exports.createContractProvider = exports.createContractConsumer = exports.CONTRACT_CALL_RPC = exports.contractProvidersSchema = exports.contractProvidersMetaData = exports.ContractProvidersTable = exports.ContractProviders = exports.createContractProviderRouter = exports.PackageBuilder = exports.definePackage = exports.ContractBuilder = void 0;
18
- exports.validateProviderSatisfiesContract = exports.validateImmutability = exports.validateAlsoImplements = exports.contractKey = exports.registerSystemContract = exports.listSystemContracts = exports.installSystemContracts = exports.TOOL_ACCESS_CONTROL_CONTRACT_VERSION = exports.getToolAccessControlContractDefinition = exports.resolveLogsContract = exports.LOGS_CONTRACT_VERSION = exports.getLogsContractDefinition = exports.localFoldersProviderPackageId = exports.LOCAL_FOLDERS_RESERVED_DIR = exports.LOCAL_FOLDERS_READ_RANGE_MAX = exports.LOCAL_FOLDERS_JOB_TTL_MS = exports.LOCAL_FOLDERS_CONTRACT_VERSION = exports.LOCAL_FOLDERS_CHUNK_MIN = exports.LOCAL_FOLDERS_CHUNK_MAX = exports.LOCAL_FOLDERS_CHUNK_AVG = exports.getLocalFoldersContractDefinition = exports.INVITES_CONTRACT_VERSION = exports.getInvitesContractDefinition = exports.IDENTITY_CONTRACT_VERSION = exports.getIdentityContractDefinition = exports.getDeviceOperationsContractDefinition = exports.deviceOperationsToolIds = exports.deviceOperationsProviderPackageId = exports.DEVICE_OPERATIONS_MAX_TOOLS = void 0;
18
+ exports.validateProviderSatisfiesContract = exports.validateImmutability = exports.validateAlsoImplements = exports.contractKey = exports.registerSystemContract = exports.listSystemContracts = exports.installSystemContracts = exports.TOOL_ACCESS_CONTROL_CONTRACT_VERSION = exports.getToolAccessControlContractDefinition = exports.resolveLogsContract = exports.LOGS_CONTRACT_VERSION = exports.getLogsContractDefinition = exports.localFoldersProviderPackageId = exports.LOCAL_FOLDERS_RESERVED_DIR = exports.LOCAL_FOLDERS_READ_RANGE_MAX = exports.LOCAL_FOLDERS_JOB_TTL_MS = exports.LOCAL_FOLDERS_HAVE_BATCH = exports.LOCAL_FOLDERS_GETBLOCKS_MAX_BYTES = exports.LOCAL_FOLDERS_GETBLOCKS_MAX_BLOCKS = exports.LOCAL_FOLDERS_CONTRACT_VERSION = exports.LOCAL_FOLDERS_CHUNK_MIN = exports.LOCAL_FOLDERS_CHUNK_MAX = exports.LOCAL_FOLDERS_CHUNK_AVG = exports.LOCAL_FOLDERS_ASSEMBLE_STALL_MS = exports.getLocalFoldersContractDefinition = exports.INVITES_CONTRACT_VERSION = exports.getInvitesContractDefinition = exports.IDENTITY_CONTRACT_VERSION = exports.getIdentityContractDefinition = exports.getDeviceOperationsContractDefinition = exports.deviceOperationsToolIds = exports.deviceOperationsProviderPackageId = exports.DEVICE_OPERATIONS_MAX_TOOLS = void 0;
19
19
  var builder_1 = require("./builder");
20
20
  Object.defineProperty(exports, "ContractBuilder", { enumerable: true, get: function () { return builder_1.ContractBuilder; } });
21
21
  Object.defineProperty(exports, "definePackage", { enumerable: true, get: function () { return builder_1.definePackage; } });
@@ -93,10 +93,14 @@ Object.defineProperty(exports, "getInvitesContractDefinition", { enumerable: tru
93
93
  Object.defineProperty(exports, "INVITES_CONTRACT_VERSION", { enumerable: true, get: function () { return invites_contract_1.INVITES_CONTRACT_VERSION; } });
94
94
  var local_folders_contract_1 = require("./system/local-folders-contract");
95
95
  Object.defineProperty(exports, "getLocalFoldersContractDefinition", { enumerable: true, get: function () { return local_folders_contract_1.getLocalFoldersContractDefinition; } });
96
+ Object.defineProperty(exports, "LOCAL_FOLDERS_ASSEMBLE_STALL_MS", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_ASSEMBLE_STALL_MS; } });
96
97
  Object.defineProperty(exports, "LOCAL_FOLDERS_CHUNK_AVG", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_CHUNK_AVG; } });
97
98
  Object.defineProperty(exports, "LOCAL_FOLDERS_CHUNK_MAX", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_CHUNK_MAX; } });
98
99
  Object.defineProperty(exports, "LOCAL_FOLDERS_CHUNK_MIN", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_CHUNK_MIN; } });
99
100
  Object.defineProperty(exports, "LOCAL_FOLDERS_CONTRACT_VERSION", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_CONTRACT_VERSION; } });
101
+ Object.defineProperty(exports, "LOCAL_FOLDERS_GETBLOCKS_MAX_BLOCKS", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_GETBLOCKS_MAX_BLOCKS; } });
102
+ Object.defineProperty(exports, "LOCAL_FOLDERS_GETBLOCKS_MAX_BYTES", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_GETBLOCKS_MAX_BYTES; } });
103
+ Object.defineProperty(exports, "LOCAL_FOLDERS_HAVE_BATCH", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_HAVE_BATCH; } });
100
104
  Object.defineProperty(exports, "LOCAL_FOLDERS_JOB_TTL_MS", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_JOB_TTL_MS; } });
101
105
  Object.defineProperty(exports, "LOCAL_FOLDERS_READ_RANGE_MAX", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_READ_RANGE_MAX; } });
102
106
  Object.defineProperty(exports, "LOCAL_FOLDERS_RESERVED_DIR", { enumerable: true, get: function () { return local_folders_contract_1.LOCAL_FOLDERS_RESERVED_DIR; } });
@@ -11,8 +11,20 @@ export declare const LOCAL_FOLDERS_CHUNK_AVG: number;
11
11
  export declare const LOCAL_FOLDERS_CHUNK_MAX: number;
12
12
  /** Directory name reserved inside every grant for trash and in-flight files. */
13
13
  export declare const LOCAL_FOLDERS_RESERVED_DIR = ".peers";
14
- /** How long finished index jobs stay readable via `getJob`. */
14
+ /** How long finished index and assemble jobs stay readable via `getJob`. */
15
15
  export declare const LOCAL_FOLDERS_JOB_TTL_MS: number;
16
+ /**
17
+ * How long an assemble job waits without receiving a block before it fails
18
+ * with `stalled`. A later assemble of the same block list resumes from the
19
+ * partial file.
20
+ */
21
+ export declare const LOCAL_FOLDERS_ASSEMBLE_STALL_MS: number;
22
+ /** Maximum hashes in one `haveBlocks` request on the block-exchange path. */
23
+ export declare const LOCAL_FOLDERS_HAVE_BATCH = 2000;
24
+ /** Maximum blocks in one `getBlocks` request. */
25
+ export declare const LOCAL_FOLDERS_GETBLOCKS_MAX_BLOCKS = 32;
26
+ /** Maximum total bytes in one `getBlocks` request. */
27
+ export declare const LOCAL_FOLDERS_GETBLOCKS_MAX_BYTES: number;
16
28
  /** Package ID used by live System Local Folders tool instances. */
17
29
  export declare const localFoldersProviderPackageId = "000peers0system0000000001";
18
30
  /** Grant mode accepted by `requestGrant`. */
@@ -101,20 +113,48 @@ export interface ILocalFolderFileBlocks {
101
113
  size: number;
102
114
  }[];
103
115
  }
104
- /** Index job snapshot returned by `getJob`. */
116
+ /** One content-defined block in an assemble manifest. */
117
+ export interface ILocalFolderManifestBlock {
118
+ hash: string;
119
+ size: number;
120
+ }
121
+ /** Input for `assemble`. `blocks` is the ordered manifest to write. */
122
+ export interface ILocalFolderAssembleInput {
123
+ grantId: string;
124
+ /** Relative file path to create or replace. */
125
+ relPath: string;
126
+ blocks: ILocalFolderManifestBlock[];
127
+ /** mtime applied to the finished file, in unix epoch milliseconds. */
128
+ mtimeMs?: number;
129
+ /** Unix permission bits. Only the low 9 bits are applied. */
130
+ mode?: number;
131
+ /** Device ids allowed to serve blocks. Omit to ask every eligible peer. */
132
+ sources?: string[];
133
+ }
134
+ /** Counts from a finished index job. */
135
+ export interface ILocalFolderIndexResult {
136
+ files: number;
137
+ rehashed: number;
138
+ skipped: number;
139
+ removed: number;
140
+ }
141
+ /** Counts from a finished assemble job. `sources` is how many peers sent bytes. */
142
+ export interface ILocalFolderAssembleResult {
143
+ bytesPulled: number;
144
+ bytesReused: number;
145
+ sources: number;
146
+ }
147
+ /** Host job kind reported by `getJob`. */
148
+ export type LocalFolderJobKind = "index" | "assemble";
149
+ /** Index or assemble job snapshot returned by `getJob`. */
105
150
  export interface ILocalFolderJobInfo {
106
151
  jobId: string;
107
- kind: "index";
152
+ kind: LocalFolderJobKind;
108
153
  status: "running" | "done" | "error" | "cancelled";
109
154
  done: number;
110
155
  total: number;
111
156
  error?: string;
112
- result?: {
113
- files: number;
114
- rehashed: number;
115
- skipped: number;
116
- removed: number;
117
- };
157
+ result?: ILocalFolderIndexResult | ILocalFolderAssembleResult;
118
158
  }
119
159
  /** Input for `move`. */
120
160
  export interface ILocalFolderMoveInput {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.localFoldersProviderPackageId = exports.LOCAL_FOLDERS_JOB_TTL_MS = exports.LOCAL_FOLDERS_RESERVED_DIR = exports.LOCAL_FOLDERS_CHUNK_MAX = exports.LOCAL_FOLDERS_CHUNK_AVG = exports.LOCAL_FOLDERS_CHUNK_MIN = exports.LOCAL_FOLDERS_READ_RANGE_MAX = exports.LOCAL_FOLDERS_CONTRACT_VERSION = void 0;
3
+ exports.localFoldersProviderPackageId = exports.LOCAL_FOLDERS_GETBLOCKS_MAX_BYTES = exports.LOCAL_FOLDERS_GETBLOCKS_MAX_BLOCKS = exports.LOCAL_FOLDERS_HAVE_BATCH = exports.LOCAL_FOLDERS_ASSEMBLE_STALL_MS = exports.LOCAL_FOLDERS_JOB_TTL_MS = exports.LOCAL_FOLDERS_RESERVED_DIR = exports.LOCAL_FOLDERS_CHUNK_MAX = exports.LOCAL_FOLDERS_CHUNK_AVG = exports.LOCAL_FOLDERS_CHUNK_MIN = exports.LOCAL_FOLDERS_READ_RANGE_MAX = exports.LOCAL_FOLDERS_CONTRACT_VERSION = void 0;
4
4
  exports.getLocalFoldersContractDefinition = getLocalFoldersContractDefinition;
5
5
  const system_ids_1 = require("../../system-ids");
6
6
  const field_type_1 = require("../../types/field-type");
@@ -17,8 +17,20 @@ exports.LOCAL_FOLDERS_CHUNK_AVG = 1024 * 1024;
17
17
  exports.LOCAL_FOLDERS_CHUNK_MAX = 4 * 1024 * 1024;
18
18
  /** Directory name reserved inside every grant for trash and in-flight files. */
19
19
  exports.LOCAL_FOLDERS_RESERVED_DIR = ".peers";
20
- /** How long finished index jobs stay readable via `getJob`. */
20
+ /** How long finished index and assemble jobs stay readable via `getJob`. */
21
21
  exports.LOCAL_FOLDERS_JOB_TTL_MS = 60 * 60 * 1000;
22
+ /**
23
+ * How long an assemble job waits without receiving a block before it fails
24
+ * with `stalled`. A later assemble of the same block list resumes from the
25
+ * partial file.
26
+ */
27
+ exports.LOCAL_FOLDERS_ASSEMBLE_STALL_MS = 10 * 60 * 1000;
28
+ /** Maximum hashes in one `haveBlocks` request on the block-exchange path. */
29
+ exports.LOCAL_FOLDERS_HAVE_BATCH = 2000;
30
+ /** Maximum blocks in one `getBlocks` request. */
31
+ exports.LOCAL_FOLDERS_GETBLOCKS_MAX_BLOCKS = 32;
32
+ /** Maximum total bytes in one `getBlocks` request. */
33
+ exports.LOCAL_FOLDERS_GETBLOCKS_MAX_BYTES = 64 * 1024 * 1024;
22
34
  /** Package ID used by live System Local Folders tool instances. */
23
35
  exports.localFoldersProviderPackageId = system_ids_1.peersSystemPackageId;
24
36
  const field = (name, type, description, opts = {}) => ({ name, type, description, ...opts });
@@ -36,7 +48,7 @@ function getLocalFoldersContractDefinition() {
36
48
  version: exports.LOCAL_FOLDERS_CONTRACT_VERSION,
37
49
  devTag: "dev",
38
50
  name: "System Local Folders",
39
- description: "Scoped access to operator-granted folders on this device: listing, bounded reads, indexing, move, and trash.",
51
+ description: "Scoped access to operator-granted folders on this device: listing, bounded reads, indexing, assemble, move, and trash.",
40
52
  tables: [],
41
53
  tools: [
42
54
  {
@@ -144,26 +156,44 @@ function getLocalFoldersContractDefinition() {
144
156
  }),
145
157
  ],
146
158
  },
159
+ {
160
+ name: "assemble",
161
+ usageDescription: "Start a host job that writes a file from an ordered block list. Missing blocks are pulled from opted-in devices. Requires a readwrite grant.",
162
+ inputFields: [
163
+ grantIdField,
164
+ field("relPath", field_type_1.FieldType.string, "Relative file path to create or replace."),
165
+ field("blocks", field_type_1.FieldType.object, "Ordered blocks with hash and size.", {
166
+ isArray: true,
167
+ }),
168
+ field("mtimeMs", field_type_1.FieldType.number, "mtime applied to the finished file.", {
169
+ optional: true,
170
+ }),
171
+ field("mode", field_type_1.FieldType.number, "Unix permission bits (low 9 bits).", { optional: true }),
172
+ field("sources", field_type_1.FieldType.id, "Device ids allowed to serve blocks.", {
173
+ optional: true,
174
+ isArray: true,
175
+ }),
176
+ ],
177
+ outputFields: [field("jobId", field_type_1.FieldType.id, "Assemble job id.")],
178
+ },
147
179
  {
148
180
  name: "getJob",
149
- usageDescription: "Return the status of an index job, including after it finishes.",
150
- inputFields: [field("jobId", field_type_1.FieldType.id, "Job id returned by index.")],
181
+ usageDescription: "Return the status of an index or assemble job, including after it finishes.",
182
+ inputFields: [field("jobId", field_type_1.FieldType.id, "Job id returned by index or assemble.")],
151
183
  outputFields: [
152
184
  field("jobId", field_type_1.FieldType.id, "Job id."),
153
- field("kind", field_type_1.FieldType.string, "Job kind. v1 is index."),
185
+ field("kind", field_type_1.FieldType.string, 'Job kind: "index" or "assemble".'),
154
186
  field("status", field_type_1.FieldType.string, "running, done, error, or cancelled."),
155
187
  field("done", field_type_1.FieldType.number, "Units completed."),
156
188
  field("total", field_type_1.FieldType.number, "Units discovered."),
157
189
  field("error", field_type_1.FieldType.string, "Failure message.", { optional: true }),
158
- field("result", field_type_1.FieldType.object, "Index counts when status is done.", {
159
- optional: true,
160
- }),
190
+ field("result", field_type_1.FieldType.object, "Index counts ({ files, rehashed, skipped, removed }) or assemble counts ({ bytesPulled, bytesReused, sources }) when status is done.", { optional: true }),
161
191
  ],
162
192
  },
163
193
  {
164
194
  name: "cancelJob",
165
- usageDescription: "Cancel a running index job.",
166
- inputFields: [field("jobId", field_type_1.FieldType.id, "Job id returned by index.")],
195
+ usageDescription: "Cancel a running index or assemble job. An assemble keeps its partial file.",
196
+ inputFields: [field("jobId", field_type_1.FieldType.id, "Job id returned by index or assemble.")],
167
197
  outputFields: [field("cancelled", field_type_1.FieldType.boolean, "Whether the job was cancelled.")],
168
198
  },
169
199
  {
@@ -207,7 +237,7 @@ function getLocalFoldersContractDefinition() {
207
237
  },
208
238
  {
209
239
  name: "jobProgress",
210
- description: "Progress for a host index job.",
240
+ description: "Progress for a host index or assemble job.",
211
241
  payloadFields: [
212
242
  field("jobId", field_type_1.FieldType.id, "Job id."),
213
243
  field("done", field_type_1.FieldType.number, "Units completed."),
@@ -216,7 +246,7 @@ function getLocalFoldersContractDefinition() {
216
246
  },
217
247
  {
218
248
  name: "jobFinished",
219
- description: "An index job finished, failed, or was cancelled.",
249
+ description: "An index or assemble job finished, failed, or was cancelled.",
220
250
  payloadFields: [
221
251
  field("jobId", field_type_1.FieldType.id, "Job id."),
222
252
  field("status", field_type_1.FieldType.string, "done, error, or cancelled."),
@@ -10,4 +10,5 @@ export * from "./package-remote-checker";
10
10
  export * from "./package-seed-installer";
11
11
  export * from "./package-tarball";
12
12
  export * from "./peers-core-installer";
13
+ export * from "./peers-core-trust-anchor";
13
14
  export * from "./types";
@@ -26,4 +26,5 @@ __exportStar(require("./package-remote-checker"), exports);
26
26
  __exportStar(require("./package-seed-installer"), exports);
27
27
  __exportStar(require("./package-tarball"), exports);
28
28
  __exportStar(require("./peers-core-installer"), exports);
29
+ __exportStar(require("./peers-core-trust-anchor"), exports);
29
30
  __exportStar(require("./types"), exports);
@@ -2,6 +2,16 @@ import type { IPackageVersion } from "../data/package-versions";
2
2
  import type { IAppNav } from "../types/app-nav";
3
3
  /** Prefix for the persistent variable name that stores a package's signing secret key. */
4
4
  export declare const PACKAGE_SIGNING_KEY_PREFIX = "packageSigningKey_";
5
+ /**
6
+ * Whether `publicKey` is a publisher key that must never be trusted again.
7
+ *
8
+ * The built-in list is {@link peersCoreRevokedPublishPublicKeys}; callers (and tests) may pass
9
+ * their own. An empty or missing key is not "revoked", it is simply not a key.
10
+ *
11
+ * @param publicKey - base64url Ed25519 public key to check
12
+ * @param revoked - list to check against; defaults to the peers-core revocations
13
+ */
14
+ export declare function isRevokedPublisherKey(publicKey: string | undefined, revoked?: readonly string[]): boolean;
5
15
  /**
6
16
  * The subset of PackageVersion fields that the publisher signs.
7
17
  * The `publicKey` field is included so verifiers know which key to check against.
@@ -47,8 +57,13 @@ export interface IVerifyAuthorSignatureResult {
47
57
  * is NOT in the signature itself — it must be provided via `expectedPublicKey` (TOFU check)
48
58
  * to reconstruct the payload and verify.
49
59
  *
60
+ * A key in {@link peersCoreRevokedPublishPublicKeys} never verifies, even with a
61
+ * mathematically valid signature: a revoked key may be in an attacker's hands, so anything it
62
+ * signed after revocation is untrustworthy and anything before is already installed.
63
+ *
50
64
  * @param pv - The PackageVersion record with `packageAuthorSignature` set
51
65
  * @param expectedPublicKey - The public key to verify against (from Package.publishPublicKey)
52
- * @returns `{ valid, publicKey }` — valid is true if signature verifies and key matches
66
+ * @returns `{ valid, publicKey }` — valid is true if signature verifies, key matches, and the
67
+ * key is not revoked
53
68
  */
54
69
  export declare function verifyPackageAuthorSignature(pv: AuthorSignableFields & Pick<IPackageVersion, "packageAuthorSignature">, expectedPublicKey: string): IVerifyAuthorSignatureResult;
@@ -1,14 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PACKAGE_SIGNING_KEY_PREFIX = void 0;
4
+ exports.isRevokedPublisherKey = isRevokedPublisherKey;
4
5
  exports.buildAuthorSignedPayload = buildAuthorSignedPayload;
5
6
  exports.signPackageAuthor = signPackageAuthor;
6
7
  exports.verifyPackageAuthorSignature = verifyPackageAuthorSignature;
7
8
  const nacl = require("tweetnacl");
8
9
  const tweetnacl_util_1 = require("tweetnacl-util");
9
10
  const keys_1 = require("../keys");
11
+ const system_ids_1 = require("../system-ids");
10
12
  /** Prefix for the persistent variable name that stores a package's signing secret key. */
11
13
  exports.PACKAGE_SIGNING_KEY_PREFIX = "packageSigningKey_";
14
+ /**
15
+ * Whether `publicKey` is a publisher key that must never be trusted again.
16
+ *
17
+ * The built-in list is {@link peersCoreRevokedPublishPublicKeys}; callers (and tests) may pass
18
+ * their own. An empty or missing key is not "revoked", it is simply not a key.
19
+ *
20
+ * @param publicKey - base64url Ed25519 public key to check
21
+ * @param revoked - list to check against; defaults to the peers-core revocations
22
+ */
23
+ function isRevokedPublisherKey(publicKey, revoked = system_ids_1.peersCoreRevokedPublishPublicKeys) {
24
+ return !!publicKey && revoked.includes(publicKey);
25
+ }
12
26
  /**
13
27
  * Extracts the signable payload from a PackageVersion record.
14
28
  * Only includes optional fields when they have a value (undefined fields are omitted
@@ -59,13 +73,18 @@ function signPackageAuthor(pv, secretKey) {
59
73
  * is NOT in the signature itself — it must be provided via `expectedPublicKey` (TOFU check)
60
74
  * to reconstruct the payload and verify.
61
75
  *
76
+ * A key in {@link peersCoreRevokedPublishPublicKeys} never verifies, even with a
77
+ * mathematically valid signature: a revoked key may be in an attacker's hands, so anything it
78
+ * signed after revocation is untrustworthy and anything before is already installed.
79
+ *
62
80
  * @param pv - The PackageVersion record with `packageAuthorSignature` set
63
81
  * @param expectedPublicKey - The public key to verify against (from Package.publishPublicKey)
64
- * @returns `{ valid, publicKey }` — valid is true if signature verifies and key matches
82
+ * @returns `{ valid, publicKey }` — valid is true if signature verifies, key matches, and the
83
+ * key is not revoked
65
84
  */
66
85
  function verifyPackageAuthorSignature(pv, expectedPublicKey) {
67
86
  const result = { valid: false, publicKey: expectedPublicKey };
68
- if (!pv.packageAuthorSignature) {
87
+ if (!pv.packageAuthorSignature || isRevokedPublisherKey(expectedPublicKey)) {
69
88
  return result;
70
89
  }
71
90
  try {
@@ -11,6 +11,7 @@ const package_versions_1 = require("../data/package-versions");
11
11
  const packages_1 = require("../data/packages");
12
12
  const utils_1 = require("../utils");
13
13
  const package_author_signing_1 = require("./package-author-signing");
14
+ const peers_core_trust_anchor_1 = require("./peers-core-trust-anchor");
14
15
  // --- Module-level state ---
15
16
  const RETRY_DELAYS_MS = [2_000, 10_000, 30_000, 60_000, 120_000];
16
17
  const pvPropagateMap = {};
@@ -190,6 +191,12 @@ async function retryOrFail(source, dest, pv, retryCnt, reason) {
190
191
  * transient failures (missing source key).
191
192
  */
192
193
  async function verifyOrEstablishTofu(sourceContext, targetContext, targetPkg, pv) {
194
+ // peers-core pinned to a revoked key: move the pin to the host's current key first so a
195
+ // version signed by the replacement key can be accepted.
196
+ const repinned = await (0, peers_core_trust_anchor_1.repinPeersCoreIfRevoked)(targetContext, targetPkg);
197
+ if (repinned !== targetPkg) {
198
+ targetPkg.publishPublicKey = repinned.publishPublicKey;
199
+ }
193
200
  if (targetPkg.publishPublicKey) {
194
201
  if (!(0, package_author_signing_1.verifyPackageAuthorSignature)(pv, targetPkg.publishPublicKey).valid) {
195
202
  console.error(`[propagation] Invalid signature for ${pv.packageVersionId}`);
@@ -201,6 +208,11 @@ async function verifyOrEstablishTofu(sourceContext, targetContext, targetPkg, pv
201
208
  const sourcePkg = await (0, packages_1.Packages)(sourceContext).get(targetPkg.packageId);
202
209
  if (!sourcePkg?.publishPublicKey)
203
210
  return false;
211
+ // Never adopt a revoked key as a trust anchor, however it reached the source group.
212
+ if ((0, package_author_signing_1.isRevokedPublisherKey)(sourcePkg.publishPublicKey)) {
213
+ console.error(`[propagation] Refusing revoked publisher key for ${targetPkg.packageId} from ${sourceContext.dataContextId}`);
214
+ return false;
215
+ }
204
216
  if (!(0, package_author_signing_1.verifyPackageAuthorSignature)(pv, sourcePkg.publishPublicKey).valid) {
205
217
  console.error(`[propagation] Invalid signature for ${pv.packageVersionId} (TOFU)`);
206
218
  return false;
@@ -2,10 +2,26 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const package_versions_1 = require("../data/package-versions");
4
4
  const keys_1 = require("../keys");
5
+ const systemIds = require("../system-ids");
5
6
  const utils_1 = require("../utils");
6
7
  const package_author_signing_1 = require("./package-author-signing");
7
8
  const package_propagation_1 = require("./package-propagation");
8
9
  // --- Mocks ---
10
+ // Pin the host to a fresh "current" peers-core key and revoke a fresh "old" one so the
11
+ // revocation tests do not depend on the real constants.
12
+ jest.mock("../system-ids", () => {
13
+ const actual = jest.requireActual("../system-ids");
14
+ const { newKeys: makeKeys } = jest.requireActual("../keys");
15
+ const revoked = makeKeys();
16
+ const current = makeKeys();
17
+ return {
18
+ ...actual,
19
+ peersCorePublishPublicKey: current.publicKey,
20
+ peersCoreRevokedPublishPublicKeys: [revoked.publicKey],
21
+ __testKeys: { revoked, current },
22
+ };
23
+ });
24
+ const { revoked: revokedCoreKeys, current: currentCoreKeys } = systemIds.__testKeys;
9
25
  const mockGetUserRole = jest.fn();
10
26
  jest.mock("../data/group-permissions", () => ({
11
27
  getUserRole: (...args) => mockGetUserRole(...args),
@@ -589,6 +605,114 @@ describe("discoverAndPropagateVersions", () => {
589
605
  expect(results).toHaveLength(0);
590
606
  consoleError.mockRestore();
591
607
  });
608
+ describe("revoked publisher keys", () => {
609
+ function twoGroupSetup() {
610
+ const groupA = "group-a";
611
+ const groupB = "group-b";
612
+ const groups = new Map([
613
+ [groupA, makeDataContext(groupA)],
614
+ [groupB, makeDataContext(groupB)],
615
+ ]);
616
+ const roles = new Map([
617
+ [groupA, 60],
618
+ [groupB, 60],
619
+ ]);
620
+ return { groupA, groupB, userContext: makeUserContext({ userId, groups }), roles };
621
+ }
622
+ let consoleError;
623
+ let consoleWarn;
624
+ beforeEach(() => {
625
+ consoleError = jest.spyOn(console, "error").mockImplementation(() => { });
626
+ consoleWarn = jest.spyOn(console, "warn").mockImplementation(() => { });
627
+ });
628
+ afterEach(() => {
629
+ consoleError.mockRestore();
630
+ consoleWarn.mockRestore();
631
+ });
632
+ it("refuses to establish TOFU from a source pinned to a revoked key", async () => {
633
+ const { groupA, groupB, userContext, roles } = twoGroupSetup();
634
+ const packageId = (0, utils_1.newid)();
635
+ const sourcePkg = makePkg({ packageId, publishPublicKey: revokedCoreKeys.publicKey });
636
+ const targetPkg = makePkg({ packageId, publishPublicKey: "" });
637
+ const pv = makeSignedPVWithSecretKey(packageId, revokedCoreKeys.secretKey, {
638
+ version: "2.0.0",
639
+ });
640
+ setupMocksForScenario({
641
+ packages: new Map([[groupB, [targetPkg]]]),
642
+ pvs: new Map([
643
+ [groupA, [pv]],
644
+ [groupB, []],
645
+ ]),
646
+ roles,
647
+ sourcePackages: new Map([[groupA, new Map([[packageId, sourcePkg]])]]),
648
+ });
649
+ const results = await (0, package_propagation_1.discoverAndPropagateVersions)(userContext);
650
+ expect(results).toHaveLength(0);
651
+ expect(mockPackagesSignAndSave).not.toHaveBeenCalled();
652
+ expect(mockPvSignAndSave).not.toHaveBeenCalled();
653
+ expect(consoleError).toHaveBeenCalledWith(expect.stringContaining("Refusing revoked publisher key"));
654
+ });
655
+ it("re-pins peers-core from a revoked key and accepts a version signed by the current key", async () => {
656
+ const { groupA, groupB, userContext, roles } = twoGroupSetup();
657
+ const packageId = systemIds.peersCorePackageId;
658
+ const sourcePkg = makePkg({ packageId, publishPublicKey: currentCoreKeys.publicKey });
659
+ const targetPkg = makePkg({ packageId, publishPublicKey: revokedCoreKeys.publicKey });
660
+ const pv = makeSignedPVWithSecretKey(packageId, currentCoreKeys.secretKey, {
661
+ version: "2.0.0",
662
+ });
663
+ setupMocksForScenario({
664
+ packages: new Map([[groupB, [targetPkg]]]),
665
+ pvs: new Map([
666
+ [groupA, [pv]],
667
+ [groupB, []],
668
+ ]),
669
+ roles,
670
+ sourcePackages: new Map([[groupA, new Map([[packageId, sourcePkg]])]]),
671
+ });
672
+ const results = await (0, package_propagation_1.discoverAndPropagateVersions)(userContext);
673
+ expect(results).toHaveLength(1);
674
+ expect(results[0]).toMatchObject({ groupId: groupB, packageId, version: "2.0.0" });
675
+ expect(mockPackagesSignAndSave).toHaveBeenCalledWith(expect.objectContaining({ packageId, publishPublicKey: currentCoreKeys.publicKey }));
676
+ });
677
+ it("rejects a peers-core version signed by the revoked key even where that key is pinned", async () => {
678
+ const { groupA, groupB, userContext, roles } = twoGroupSetup();
679
+ const packageId = systemIds.peersCorePackageId;
680
+ const targetPkg = makePkg({ packageId, publishPublicKey: revokedCoreKeys.publicKey });
681
+ const pv = makeSignedPVWithSecretKey(packageId, revokedCoreKeys.secretKey, {
682
+ version: "2.0.0",
683
+ });
684
+ setupMocksForScenario({
685
+ packages: new Map([[groupB, [targetPkg]]]),
686
+ pvs: new Map([
687
+ [groupA, [pv]],
688
+ [groupB, []],
689
+ ]),
690
+ roles,
691
+ });
692
+ const results = await (0, package_propagation_1.discoverAndPropagateVersions)(userContext);
693
+ expect(results).toHaveLength(0);
694
+ expect(mockPvSignAndSave).not.toHaveBeenCalled();
695
+ });
696
+ it("does not re-pin other packages pinned to a revoked key; their versions are rejected", async () => {
697
+ const { groupA, groupB, userContext, roles } = twoGroupSetup();
698
+ const packageId = (0, utils_1.newid)();
699
+ const targetPkg = makePkg({ packageId, publishPublicKey: revokedCoreKeys.publicKey });
700
+ const pv = makeSignedPVWithSecretKey(packageId, revokedCoreKeys.secretKey, {
701
+ version: "2.0.0",
702
+ });
703
+ setupMocksForScenario({
704
+ packages: new Map([[groupB, [targetPkg]]]),
705
+ pvs: new Map([
706
+ [groupA, [pv]],
707
+ [groupB, []],
708
+ ]),
709
+ roles,
710
+ });
711
+ const results = await (0, package_propagation_1.discoverAndPropagateVersions)(userContext);
712
+ expect(results).toHaveLength(0);
713
+ expect(mockPackagesSignAndSave).not.toHaveBeenCalled();
714
+ });
715
+ });
592
716
  it("skips PV when packageVersionId already exists in target (dedup)", async () => {
593
717
  const groupA = "group-a";
594
718
  const groupB = "group-b";
@@ -19,6 +19,7 @@ const package_author_signing_1 = require("./package-author-signing");
19
19
  const package_installer_1 = require("./package-installer");
20
20
  const package_propagation_1 = require("./package-propagation");
21
21
  const package_tarball_1 = require("./package-tarball");
22
+ const peers_core_trust_anchor_1 = require("./peers-core-trust-anchor");
22
23
  // Module-level memoization: unique by `updateUrl + ":" + tag`
23
24
  const remoteCheckCache = new Map();
24
25
  /**
@@ -44,9 +45,13 @@ function checkPackageRemoteForNewVersion(packageId, updateUrl, tag, http) {
44
45
  * group does not already have the remote PackageVersion.
45
46
  */
46
47
  async function checkAndInstallPackageRemoteVersion(dataContext, pkg, tag, http) {
47
- if (!pkg.updateUrl || pkg.disabled)
48
+ const updateUrl = pkg.updateUrl;
49
+ if (!updateUrl || pkg.disabled)
48
50
  return null;
49
- const result = await checkPackageRemoteForNewVersion(pkg.packageId, pkg.updateUrl, tag, http);
51
+ // A peers-core row pinned to a revoked publisher key would reject every new release; move
52
+ // it to the host's current anchor before looking at the remote.
53
+ await (0, peers_core_trust_anchor_1.repinPeersCoreIfRevoked)(dataContext, pkg);
54
+ const result = await checkPackageRemoteForNewVersion(pkg.packageId, updateUrl, tag, http);
50
55
  if (!result)
51
56
  return null;
52
57
  const existing = await (0, package_versions_1.PackageVersions)(dataContext).get(result.pointer.packageVersionId);
@@ -32,13 +32,30 @@ const mockResolveDevicePackageVersion = jest.fn();
32
32
  jest.mock("../data/package-version-resolver", () => ({
33
33
  resolveDevicePackageVersion: (...args) => mockResolveDevicePackageVersion(...args),
34
34
  }));
35
+ // Pin the host to a fresh "current" peers-core key and revoke a fresh "old" one so the
36
+ // revocation tests do not depend on the real constants.
37
+ jest.mock("../system-ids", () => {
38
+ const actual = jest.requireActual("../system-ids");
39
+ const { newKeys: makeKeys } = jest.requireActual("../keys");
40
+ const revoked = makeKeys();
41
+ const current = makeKeys();
42
+ return {
43
+ ...actual,
44
+ peersCorePublishPublicKey: current.publicKey,
45
+ peersCoreRevokedPublishPublicKeys: [revoked.publicKey],
46
+ __testKeys: { revoked, current },
47
+ };
48
+ });
35
49
  const file_types_1 = require("../data/files/file.types");
36
50
  const keys_1 = require("../keys");
51
+ const systemIds = require("../system-ids");
37
52
  const package_author_signing_1 = require("./package-author-signing");
38
53
  const package_remote_checker_1 = require("./package-remote-checker");
39
54
  const package_tarball_1 = require("./package-tarball");
55
+ // PKG_ID is peers-core's id so the trust-anchor re-pin path is exercised.
40
56
  const PKG_ID = "00mh0wlipjixk2gqmurbwee0o";
41
57
  const PV_ID = "00mpdg7xstwt3oe51x9brk15i";
58
+ const { revoked: revokedCoreKeys, current: currentCoreKeys } = systemIds.__testKeys;
42
59
  function makeContext(dataContextId) {
43
60
  return { dataContextId };
44
61
  }
@@ -234,6 +251,54 @@ describe("package-remote-checker", () => {
234
251
  expect(http.fetchJson).toHaveBeenCalledTimes(1);
235
252
  expect(mockSaveBundleFile).toHaveBeenCalled();
236
253
  });
254
+ it("re-pins peers-core from a revoked key and installs a release signed by the current key", async () => {
255
+ expect(PKG_ID).toBe(systemIds.peersCorePackageId);
256
+ const ctx = makeContext("user-ctx");
257
+ const { pointer, tarball } = await makeSignedRemoteResult(currentCoreKeys.secretKey, currentCoreKeys.publicKey);
258
+ let pkg = {
259
+ packageId: PKG_ID,
260
+ name: "peers-core",
261
+ publishPublicKey: revokedCoreKeys.publicKey,
262
+ updateUrl: "https://example.com/peers-core",
263
+ };
264
+ const http = {
265
+ fetchJson: jest.fn().mockResolvedValue(pointer),
266
+ fetchBinary: jest.fn().mockResolvedValue(tarball),
267
+ };
268
+ mockPackageVersionsGet.mockResolvedValue(undefined);
269
+ mockPackagesGet.mockImplementation(async () => pkg);
270
+ mockPackagesSignAndSave.mockImplementation(async (_ctx, saved) => {
271
+ pkg = saved;
272
+ });
273
+ const warn = jest.spyOn(console, "warn").mockImplementation(() => { });
274
+ const installed = await (0, package_remote_checker_1.checkAndInstallPackageRemoteVersion)(ctx, pkg, "stable", http);
275
+ expect(installed?.packageVersion.packageVersionId).toBe(PV_ID);
276
+ expect(mockPackagesSignAndSave).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ packageId: PKG_ID, publishPublicKey: currentCoreKeys.publicKey }));
277
+ expect(pkg.publishPublicKey).toBe(currentCoreKeys.publicKey);
278
+ warn.mockRestore();
279
+ });
280
+ it("rejects a remote release signed by a revoked key", async () => {
281
+ const ctx = makeContext("user-ctx");
282
+ const { pointer, tarball } = await makeSignedRemoteResult(revokedCoreKeys.secretKey, revokedCoreKeys.publicKey);
283
+ const pkg = {
284
+ packageId: PKG_ID,
285
+ name: "peers-core",
286
+ publishPublicKey: currentCoreKeys.publicKey,
287
+ updateUrl: "https://example.com/peers-core-old",
288
+ };
289
+ const http = {
290
+ fetchJson: jest.fn().mockResolvedValue(pointer),
291
+ fetchBinary: jest.fn().mockResolvedValue(tarball),
292
+ };
293
+ mockPackageVersionsGet.mockResolvedValue(undefined);
294
+ mockPackagesGet.mockResolvedValue(pkg);
295
+ const error = jest.spyOn(console, "error").mockImplementation(() => { });
296
+ const installed = await (0, package_remote_checker_1.checkAndInstallPackageRemoteVersion)(ctx, pkg, "stable", http);
297
+ expect(installed).toBeNull();
298
+ expect(mockSaveBundleFile).not.toHaveBeenCalled();
299
+ expect(mockPackageVersionsSignAndSave).not.toHaveBeenCalled();
300
+ error.mockRestore();
301
+ });
237
302
  });
238
303
  describe("installRemotePackageVersion", () => {
239
304
  it("rejects publisher key mismatch before saving files", async () => {
@@ -43,6 +43,10 @@ export interface IEnsurePeersCoreOpts {
43
43
  * Ensure peers-core is installed (with a usable, loaded version) in
44
44
  * `dataContext`, applying the layered strategy described in the module docs.
45
45
  *
46
+ * Before anything else, an already-installed peers-core whose pinned publisher key has been
47
+ * revoked is re-pinned to the host's current key (see `peers-core-trust-anchor.ts`), so the
48
+ * later remote check can accept releases signed by the replacement key.
49
+ *
46
50
  * @returns true if an install occurred, false if it was already present or no
47
51
  * strategy succeeded.
48
52
  */
@@ -23,16 +23,22 @@ const packages_utils_1 = require("../data/packages.utils");
23
23
  const system_ids_1 = require("../system-ids");
24
24
  const package_remote_checker_1 = require("./package-remote-checker");
25
25
  const package_seed_installer_1 = require("./package-seed-installer");
26
+ const peers_core_trust_anchor_1 = require("./peers-core-trust-anchor");
26
27
  const PEERS_CORE_NAME = "peers-core";
27
28
  const PEERS_CORE_DESCRIPTION = "Core system package for Peers platform";
28
29
  /**
29
30
  * Ensure peers-core is installed (with a usable, loaded version) in
30
31
  * `dataContext`, applying the layered strategy described in the module docs.
31
32
  *
33
+ * Before anything else, an already-installed peers-core whose pinned publisher key has been
34
+ * revoked is re-pinned to the host's current key (see `peers-core-trust-anchor.ts`), so the
35
+ * later remote check can accept releases signed by the replacement key.
36
+ *
32
37
  * @returns true if an install occurred, false if it was already present or no
33
38
  * strategy succeeded.
34
39
  */
35
40
  async function ensurePeersCore(userContext, dataContext, opts) {
41
+ await (0, peers_core_trust_anchor_1.repinInstalledPeersCoreIfRevoked)(dataContext);
36
42
  const existingPvs = await (0, package_versions_1.PackageVersions)(dataContext).list({ packageId: system_ids_1.peersCorePackageId });
37
43
  if (existingPvs.length > 0)
38
44
  return false;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const mockPvList = jest.fn();
4
+ jest.mock("../data/package-versions", () => ({
5
+ PackageVersions: (ctx) => ({
6
+ list: (...args) => mockPvList(ctx, ...args),
7
+ }),
8
+ }));
9
+ const mockRepinInstalled = jest.fn();
10
+ jest.mock("./peers-core-trust-anchor", () => ({
11
+ repinInstalledPeersCoreIfRevoked: (...args) => mockRepinInstalled(...args),
12
+ }));
13
+ jest.mock("../data/packages", () => ({ Packages: () => ({}) }));
14
+ jest.mock("../data/packages.utils", () => ({ copyPackageToAnotherDataContext: jest.fn() }));
15
+ jest.mock("../data/package-version-resolver", () => ({
16
+ resolveDevicePackageVersion: jest.fn(),
17
+ }));
18
+ jest.mock("./package-remote-checker", () => ({
19
+ checkAndInstallPackageRemoteVersion: jest.fn(),
20
+ }));
21
+ jest.mock("./package-seed-installer", () => ({
22
+ ensurePackageRecord: jest.fn(),
23
+ seedPackageInContext: jest.fn(),
24
+ }));
25
+ const system_ids_1 = require("../system-ids");
26
+ const peers_core_installer_1 = require("./peers-core-installer");
27
+ describe("ensurePeersCore", () => {
28
+ beforeEach(() => {
29
+ jest.clearAllMocks();
30
+ mockRepinInstalled.mockResolvedValue(null);
31
+ });
32
+ it("re-pins a revoked peers-core key before checking for existing versions", async () => {
33
+ const dataContext = { dataContextId: "user-ctx" };
34
+ const order = [];
35
+ mockRepinInstalled.mockImplementation(async () => {
36
+ order.push("repin");
37
+ return null;
38
+ });
39
+ mockPvList.mockImplementation(async () => {
40
+ order.push("list");
41
+ return [{ packageVersionId: "existing" }];
42
+ });
43
+ const installed = await (0, peers_core_installer_1.ensurePeersCore)({}, dataContext, {});
44
+ expect(installed).toBe(false);
45
+ expect(mockRepinInstalled).toHaveBeenCalledWith(dataContext);
46
+ expect(mockPvList).toHaveBeenCalledWith(dataContext, { packageId: system_ids_1.peersCorePackageId });
47
+ expect(order).toEqual(["repin", "list"]);
48
+ });
49
+ });
@@ -0,0 +1,47 @@
1
+ import type { DataContext } from "../context/data-context";
2
+ import { type IPackage } from "../data/packages";
3
+ /**
4
+ * Migration of the peers-core trust anchor after a publisher-key rotation.
5
+ *
6
+ * `Packages.publishPublicKey` is pinned on first install (TOFU) and otherwise never changes, so
7
+ * a client that installed peers-core under a key that was later revoked would keep that key
8
+ * forever and reject every version signed by its replacement. This module replaces a pinned
9
+ * key that appears in {@link peersCoreRevokedPublishPublicKeys} with the host's current
10
+ * {@link peersCorePublishPublicKey}. Only peers-core is handled here: it is the one package
11
+ * whose trust anchor ships inside the host.
12
+ */
13
+ /** Options for {@link repinPeersCoreIfRevoked}, mainly for tests. */
14
+ export interface IRepinOptions {
15
+ /** Current trusted key. Defaults to {@link peersCorePublishPublicKey}. */
16
+ currentPublicKey?: string;
17
+ /** Keys that must be replaced. Defaults to {@link peersCoreRevokedPublishPublicKeys}. */
18
+ revokedPublicKeys?: readonly string[];
19
+ }
20
+ /**
21
+ * Whether `pkg` is the peers-core package pinned to a revoked publisher key.
22
+ *
23
+ * @param pkg - package row to inspect
24
+ * @param opts - alternative key lists (tests)
25
+ */
26
+ export declare function needsPeersCoreRepin(pkg: Pick<IPackage, "packageId" | "publishPublicKey">, opts?: IRepinOptions): boolean;
27
+ /**
28
+ * If `pkg` is peers-core pinned to a revoked key, re-pin it to the current key in
29
+ * `dataContext` and return the updated row; otherwise return `pkg` unchanged.
30
+ *
31
+ * Safe to call on every remote check and propagation: it is a no-op unless the pin is revoked.
32
+ * In a group where the caller may not write package rows, `signAndSave` throws; the error is
33
+ * logged and the original row returned, so verification keeps failing closed there until an
34
+ * admin's device performs the re-pin and the row syncs.
35
+ *
36
+ * @param dataContext - context holding the package row
37
+ * @param pkg - the package row as currently loaded
38
+ * @param opts - alternative key lists (tests)
39
+ * @returns the (possibly re-pinned) package row
40
+ */
41
+ export declare function repinPeersCoreIfRevoked(dataContext: DataContext, pkg: IPackage, opts?: IRepinOptions): Promise<IPackage>;
42
+ /**
43
+ * Look up peers-core in `dataContext` and re-pin it if its key is revoked.
44
+ *
45
+ * @returns the package row when peers-core is installed in the context, else null
46
+ */
47
+ export declare function repinInstalledPeersCoreIfRevoked(dataContext: DataContext, opts?: IRepinOptions): Promise<IPackage | null>;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.needsPeersCoreRepin = needsPeersCoreRepin;
4
+ exports.repinPeersCoreIfRevoked = repinPeersCoreIfRevoked;
5
+ exports.repinInstalledPeersCoreIfRevoked = repinInstalledPeersCoreIfRevoked;
6
+ const packages_1 = require("../data/packages");
7
+ const system_ids_1 = require("../system-ids");
8
+ const package_author_signing_1 = require("./package-author-signing");
9
+ /**
10
+ * Whether `pkg` is the peers-core package pinned to a revoked publisher key.
11
+ *
12
+ * @param pkg - package row to inspect
13
+ * @param opts - alternative key lists (tests)
14
+ */
15
+ function needsPeersCoreRepin(pkg, opts = {}) {
16
+ const current = opts.currentPublicKey ?? system_ids_1.peersCorePublishPublicKey;
17
+ const revoked = opts.revokedPublicKeys ?? system_ids_1.peersCoreRevokedPublishPublicKeys;
18
+ return (pkg.packageId === system_ids_1.peersCorePackageId &&
19
+ !!current &&
20
+ pkg.publishPublicKey !== current &&
21
+ (0, package_author_signing_1.isRevokedPublisherKey)(pkg.publishPublicKey, revoked));
22
+ }
23
+ /**
24
+ * If `pkg` is peers-core pinned to a revoked key, re-pin it to the current key in
25
+ * `dataContext` and return the updated row; otherwise return `pkg` unchanged.
26
+ *
27
+ * Safe to call on every remote check and propagation: it is a no-op unless the pin is revoked.
28
+ * In a group where the caller may not write package rows, `signAndSave` throws; the error is
29
+ * logged and the original row returned, so verification keeps failing closed there until an
30
+ * admin's device performs the re-pin and the row syncs.
31
+ *
32
+ * @param dataContext - context holding the package row
33
+ * @param pkg - the package row as currently loaded
34
+ * @param opts - alternative key lists (tests)
35
+ * @returns the (possibly re-pinned) package row
36
+ */
37
+ async function repinPeersCoreIfRevoked(dataContext, pkg, opts = {}) {
38
+ if (!needsPeersCoreRepin(pkg, opts))
39
+ return pkg;
40
+ const current = opts.currentPublicKey ?? system_ids_1.peersCorePublishPublicKey;
41
+ try {
42
+ const updated = { ...pkg, publishPublicKey: current };
43
+ await (0, packages_1.Packages)(dataContext).signAndSave(updated);
44
+ console.warn(`[peers-core-trust-anchor] Replaced revoked peers-core publisher key in ${dataContext.dataContextId}`);
45
+ return updated;
46
+ }
47
+ catch (err) {
48
+ console.error(`[peers-core-trust-anchor] Could not re-pin peers-core in ${dataContext.dataContextId}:`, err);
49
+ return pkg;
50
+ }
51
+ }
52
+ /**
53
+ * Look up peers-core in `dataContext` and re-pin it if its key is revoked.
54
+ *
55
+ * @returns the package row when peers-core is installed in the context, else null
56
+ */
57
+ async function repinInstalledPeersCoreIfRevoked(dataContext, opts = {}) {
58
+ const pkg = await (0, packages_1.Packages)(dataContext).get(system_ids_1.peersCorePackageId);
59
+ if (!pkg)
60
+ return null;
61
+ return repinPeersCoreIfRevoked(dataContext, pkg, opts);
62
+ }
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const keys_1 = require("../keys");
4
+ const systemIds = require("../system-ids");
5
+ const utils_1 = require("../utils");
6
+ const package_author_signing_1 = require("./package-author-signing");
7
+ const peers_core_trust_anchor_1 = require("./peers-core-trust-anchor");
8
+ // Pin the host to a fresh "current" key and revoke a fresh "old" key so the tests do not
9
+ // depend on the real constants. Keys are created inside the factory because jest hoists
10
+ // `jest.mock` above module-level declarations.
11
+ jest.mock("../system-ids", () => {
12
+ const actual = jest.requireActual("../system-ids");
13
+ const { newKeys: makeKeys } = jest.requireActual("../keys");
14
+ const revoked = makeKeys();
15
+ const current = makeKeys();
16
+ return {
17
+ ...actual,
18
+ peersCorePublishPublicKey: current.publicKey,
19
+ peersCoreRevokedPublishPublicKeys: [revoked.publicKey],
20
+ __testKeys: { revoked, current },
21
+ };
22
+ });
23
+ const { revoked: revokedKeys, current: currentKeys } = systemIds.__testKeys;
24
+ const mockPackagesGet = jest.fn();
25
+ const mockPackagesSignAndSave = jest.fn();
26
+ jest.mock("../data/packages", () => ({
27
+ Packages: () => ({
28
+ get: (...args) => mockPackagesGet(...args),
29
+ signAndSave: (...args) => mockPackagesSignAndSave(...args),
30
+ }),
31
+ }));
32
+ function makeCtx(id = "ctx") {
33
+ return { dataContextId: id };
34
+ }
35
+ function makePkg(overrides) {
36
+ return {
37
+ packageId: systemIds.peersCorePackageId,
38
+ name: "peers-core",
39
+ publishPublicKey: revokedKeys.publicKey,
40
+ ...overrides,
41
+ };
42
+ }
43
+ function makePV() {
44
+ return {
45
+ packageId: systemIds.peersCorePackageId,
46
+ packageVersionId: (0, utils_1.newid)(),
47
+ version: "1.0.0",
48
+ versionTag: "stable",
49
+ packageBundleFileHash: "hash",
50
+ };
51
+ }
52
+ beforeEach(() => {
53
+ jest.clearAllMocks();
54
+ jest.spyOn(console, "warn").mockImplementation(() => { });
55
+ jest.spyOn(console, "error").mockImplementation(() => { });
56
+ });
57
+ afterEach(() => {
58
+ jest.restoreAllMocks();
59
+ });
60
+ describe("isRevokedPublisherKey", () => {
61
+ it("uses the host revocation list by default", () => {
62
+ expect((0, package_author_signing_1.isRevokedPublisherKey)(revokedKeys.publicKey)).toBe(true);
63
+ expect((0, package_author_signing_1.isRevokedPublisherKey)(currentKeys.publicKey)).toBe(false);
64
+ });
65
+ it("accepts an explicit list", () => {
66
+ expect((0, package_author_signing_1.isRevokedPublisherKey)("abc", ["abc"])).toBe(true);
67
+ expect((0, package_author_signing_1.isRevokedPublisherKey)(revokedKeys.publicKey, [])).toBe(false);
68
+ });
69
+ it("treats empty or missing keys as not revoked", () => {
70
+ expect((0, package_author_signing_1.isRevokedPublisherKey)("", ["", "x"])).toBe(false);
71
+ expect((0, package_author_signing_1.isRevokedPublisherKey)(undefined)).toBe(false);
72
+ });
73
+ });
74
+ describe("verifyPackageAuthorSignature with a revoked key", () => {
75
+ it("rejects a mathematically valid signature from a revoked key", () => {
76
+ const pv = { ...makePV(), packageAuthorSignature: "" };
77
+ pv.packageAuthorSignature = (0, package_author_signing_1.signPackageAuthor)(pv, revokedKeys.secretKey);
78
+ const result = (0, package_author_signing_1.verifyPackageAuthorSignature)(pv, revokedKeys.publicKey);
79
+ expect(result).toEqual({ valid: false, publicKey: revokedKeys.publicKey });
80
+ });
81
+ it("still accepts the current key", () => {
82
+ const pv = { ...makePV(), packageAuthorSignature: "" };
83
+ pv.packageAuthorSignature = (0, package_author_signing_1.signPackageAuthor)(pv, currentKeys.secretKey);
84
+ expect((0, package_author_signing_1.verifyPackageAuthorSignature)(pv, currentKeys.publicKey).valid).toBe(true);
85
+ });
86
+ });
87
+ describe("needsPeersCoreRepin", () => {
88
+ it("is true only for peers-core pinned to a revoked key", () => {
89
+ expect((0, peers_core_trust_anchor_1.needsPeersCoreRepin)(makePkg())).toBe(true);
90
+ expect((0, peers_core_trust_anchor_1.needsPeersCoreRepin)(makePkg({ publishPublicKey: currentKeys.publicKey }))).toBe(false);
91
+ expect((0, peers_core_trust_anchor_1.needsPeersCoreRepin)(makePkg({ publishPublicKey: "" }))).toBe(false);
92
+ expect((0, peers_core_trust_anchor_1.needsPeersCoreRepin)(makePkg({ packageId: (0, utils_1.newid)() }))).toBe(false);
93
+ });
94
+ it("honours explicit key lists", () => {
95
+ const other = (0, keys_1.newKeys)();
96
+ const pkg = makePkg({ publishPublicKey: other.publicKey });
97
+ expect((0, peers_core_trust_anchor_1.needsPeersCoreRepin)(pkg)).toBe(false);
98
+ expect((0, peers_core_trust_anchor_1.needsPeersCoreRepin)(pkg, {
99
+ currentPublicKey: currentKeys.publicKey,
100
+ revokedPublicKeys: [other.publicKey],
101
+ })).toBe(true);
102
+ });
103
+ it("is false when the host has no current key to pin", () => {
104
+ expect((0, peers_core_trust_anchor_1.needsPeersCoreRepin)(makePkg(), { currentPublicKey: "" })).toBe(false);
105
+ });
106
+ });
107
+ describe("repinPeersCoreIfRevoked", () => {
108
+ it("re-pins and saves when the key is revoked", async () => {
109
+ mockPackagesSignAndSave.mockResolvedValue(undefined);
110
+ const pkg = makePkg();
111
+ const result = await (0, peers_core_trust_anchor_1.repinPeersCoreIfRevoked)(makeCtx(), pkg);
112
+ expect(result).not.toBe(pkg);
113
+ expect(result.publishPublicKey).toBe(currentKeys.publicKey);
114
+ expect(mockPackagesSignAndSave).toHaveBeenCalledWith(expect.objectContaining({
115
+ packageId: systemIds.peersCorePackageId,
116
+ publishPublicKey: currentKeys.publicKey,
117
+ }));
118
+ expect(console.warn).toHaveBeenCalled();
119
+ });
120
+ it("returns the same row and does not save when nothing is revoked", async () => {
121
+ const pkg = makePkg({ publishPublicKey: currentKeys.publicKey });
122
+ const result = await (0, peers_core_trust_anchor_1.repinPeersCoreIfRevoked)(makeCtx(), pkg);
123
+ expect(result).toBe(pkg);
124
+ expect(mockPackagesSignAndSave).not.toHaveBeenCalled();
125
+ });
126
+ it("does not touch other packages even when their key is revoked", async () => {
127
+ const pkg = makePkg({ packageId: (0, utils_1.newid)() });
128
+ const result = await (0, peers_core_trust_anchor_1.repinPeersCoreIfRevoked)(makeCtx(), pkg);
129
+ expect(result).toBe(pkg);
130
+ expect(mockPackagesSignAndSave).not.toHaveBeenCalled();
131
+ });
132
+ it("returns the original row when the save is not permitted", async () => {
133
+ mockPackagesSignAndSave.mockRejectedValue(new Error("not a writer"));
134
+ const pkg = makePkg();
135
+ const result = await (0, peers_core_trust_anchor_1.repinPeersCoreIfRevoked)(makeCtx("group-ro"), pkg);
136
+ expect(result).toBe(pkg);
137
+ expect(result.publishPublicKey).toBe(revokedKeys.publicKey);
138
+ expect(console.error).toHaveBeenCalled();
139
+ });
140
+ });
141
+ describe("repinInstalledPeersCoreIfRevoked", () => {
142
+ it("returns null when peers-core is not installed", async () => {
143
+ mockPackagesGet.mockResolvedValue(undefined);
144
+ expect(await (0, peers_core_trust_anchor_1.repinInstalledPeersCoreIfRevoked)(makeCtx())).toBeNull();
145
+ expect(mockPackagesSignAndSave).not.toHaveBeenCalled();
146
+ });
147
+ it("loads peers-core and re-pins it", async () => {
148
+ mockPackagesGet.mockResolvedValue(makePkg());
149
+ mockPackagesSignAndSave.mockResolvedValue(undefined);
150
+ const result = await (0, peers_core_trust_anchor_1.repinInstalledPeersCoreIfRevoked)(makeCtx());
151
+ expect(mockPackagesGet).toHaveBeenCalledWith(systemIds.peersCorePackageId);
152
+ expect(result?.publishPublicKey).toBe(currentKeys.publicKey);
153
+ expect(mockPackagesSignAndSave).toHaveBeenCalledTimes(1);
154
+ });
155
+ });
@@ -23,8 +23,28 @@ export declare const createGroupToolId = "000peers0tool0creategroup";
23
23
  export declare const peersCorePackageId = "00mh0wlipjixk2gqmurbwee0o";
24
24
  /** Canonical source repository for peers-core. */
25
25
  export declare const peersCorePackageRepoUrl = "https://github.com/peers-app/peers-core";
26
- /** Trusted publishing key for official peers-core releases. */
27
- export declare const peersCorePublishPublicKey = "S7cQqnvVz-FVDO6Lk5zC7epSvcP_-NyBVZz9gdcj73c";
26
+ /**
27
+ * Trusted publishing key for official peers-core releases (Ed25519, base64url).
28
+ *
29
+ * This is the bootstrap trust anchor shipped with every host. It seeds `Packages.publishPublicKey`
30
+ * for peers-core and is what `verifyPackageAuthorSignature` checks remote and propagated
31
+ * peers-core versions against. The same key is registered as the active key of subject
32
+ * {@link peersCorePackageId} in the peers-services key registry (`GET /api/v1/keys/:subjectId`).
33
+ *
34
+ * Rotation: generate a new pair with `scripts/generate-publisher-key.mjs`, move the previous
35
+ * value into {@link peersCoreRevokedPublishPublicKeys}, seed the registry, and ship a host release.
36
+ */
37
+ export declare const peersCorePublishPublicKey = "8S1pAWvka0RZauJnwM13n7qO01NtdGV7FSU_aSJw0dc";
38
+ /**
39
+ * peers-core publishing keys that must never be trusted again, whatever a local `Packages` row
40
+ * or a propagating peer says. A version signed by one of these fails author verification, a
41
+ * peer offering one as a TOFU anchor is refused, and `ensurePeersCore` replaces a pinned
42
+ * revoked key with {@link peersCorePublishPublicKey}.
43
+ *
44
+ * Append-only. Entries are retained forever so a client coming back after a long time still
45
+ * rejects them.
46
+ */
47
+ export declare const peersCoreRevokedPublishPublicKeys: readonly string[];
28
48
  /**
29
49
  * Stable id of the peers **system** itself, used as the `providerPackageId` for
30
50
  * built-in system contracts (contracts the platform provides rather than a package).
@@ -109,6 +129,7 @@ export declare const localFoldersToolIds: {
109
129
  readonly readRange: "00mukk9dwdih6mh8rrofph80o";
110
130
  readonly index: "00mukk9dwdm01bxg3hrby6pli";
111
131
  readonly getFileBlocks: "00mumw1pmmf3y2ujfdc99h4ib";
132
+ readonly assemble: "00mungara9ixeolgxukzj2aku";
112
133
  readonly getJob: "00mukk9dwd1c1f51xjysssgtv";
113
134
  readonly cancelJob: "00mukk9dweizvif4ghp6kfa92";
114
135
  readonly move: "00mukk9dwer8wsw5eiwis8tzm";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.localFoldersToolIds = exports.localFoldersContractId = exports.identityToolIds = exports.identityContractId = exports.invitesToolIds = exports.invitesContractId = exports.toolAccessControlToolIds = exports.toolAccessControlContractId = exports.networkControlContractId = exports.networkDiagnosticsContractId = exports.deviceOperationsContractId = exports.logsContractId = exports.peersSystemPackageId = exports.peersCorePublishPublicKey = exports.peersCorePackageRepoUrl = exports.peersCorePackageId = exports.createGroupToolId = exports.queryDatabaseToolId = exports.runWorkflowToolId = exports.defaultSendMessageToolId = exports.defaultAssistantRunnerToolId = exports.adminAssistantId = exports.openCodeAssistantId = exports.defaultAssistantId = exports.peersRootUserId = void 0;
3
+ exports.localFoldersToolIds = exports.localFoldersContractId = exports.identityToolIds = exports.identityContractId = exports.invitesToolIds = exports.invitesContractId = exports.toolAccessControlToolIds = exports.toolAccessControlContractId = exports.networkControlContractId = exports.networkDiagnosticsContractId = exports.deviceOperationsContractId = exports.logsContractId = exports.peersSystemPackageId = exports.peersCoreRevokedPublishPublicKeys = exports.peersCorePublishPublicKey = exports.peersCorePackageRepoUrl = exports.peersCorePackageId = exports.createGroupToolId = exports.queryDatabaseToolId = exports.runWorkflowToolId = exports.defaultSendMessageToolId = exports.defaultAssistantRunnerToolId = exports.adminAssistantId = exports.openCodeAssistantId = exports.defaultAssistantId = exports.peersRootUserId = void 0;
4
4
  /** Stable user ID that owns built-in Peers system records. */
5
5
  exports.peersRootUserId = "000peers0user000000000001";
6
6
  /** Default assistant used when no user-selected assistant is available. */
@@ -27,8 +27,31 @@ exports.createGroupToolId = "000peers0tool0creategroup";
27
27
  exports.peersCorePackageId = "00mh0wlipjixk2gqmurbwee0o";
28
28
  /** Canonical source repository for peers-core. */
29
29
  exports.peersCorePackageRepoUrl = "https://github.com/peers-app/peers-core";
30
- /** Trusted publishing key for official peers-core releases. */
31
- exports.peersCorePublishPublicKey = "S7cQqnvVz-FVDO6Lk5zC7epSvcP_-NyBVZz9gdcj73c";
30
+ /**
31
+ * Trusted publishing key for official peers-core releases (Ed25519, base64url).
32
+ *
33
+ * This is the bootstrap trust anchor shipped with every host. It seeds `Packages.publishPublicKey`
34
+ * for peers-core and is what `verifyPackageAuthorSignature` checks remote and propagated
35
+ * peers-core versions against. The same key is registered as the active key of subject
36
+ * {@link peersCorePackageId} in the peers-services key registry (`GET /api/v1/keys/:subjectId`).
37
+ *
38
+ * Rotation: generate a new pair with `scripts/generate-publisher-key.mjs`, move the previous
39
+ * value into {@link peersCoreRevokedPublishPublicKeys}, seed the registry, and ship a host release.
40
+ */
41
+ exports.peersCorePublishPublicKey = "8S1pAWvka0RZauJnwM13n7qO01NtdGV7FSU_aSJw0dc";
42
+ /**
43
+ * peers-core publishing keys that must never be trusted again, whatever a local `Packages` row
44
+ * or a propagating peer says. A version signed by one of these fails author verification, a
45
+ * peer offering one as a TOFU anchor is refused, and `ensurePeersCore` replaces a pinned
46
+ * revoked key with {@link peersCorePublishPublicKey}.
47
+ *
48
+ * Append-only. Entries are retained forever so a client coming back after a long time still
49
+ * rejects them.
50
+ */
51
+ exports.peersCoreRevokedPublishPublicKeys = [
52
+ // Exposed in packaged macOS builds v0.19.9 through v0.25.11 (2026-10-02 incident).
53
+ "S7cQqnvVz-FVDO6Lk5zC7epSvcP_-NyBVZz9gdcj73c",
54
+ ];
32
55
  /**
33
56
  * Stable id of the peers **system** itself, used as the `providerPackageId` for
34
57
  * built-in system contracts (contracts the platform provides rather than a package).
@@ -113,6 +136,7 @@ exports.localFoldersToolIds = {
113
136
  readRange: "00mukk9dwdih6mh8rrofph80o",
114
137
  index: "00mukk9dwdm01bxg3hrby6pli",
115
138
  getFileBlocks: "00mumw1pmmf3y2ujfdc99h4ib",
139
+ assemble: "00mungara9ixeolgxukzj2aku",
116
140
  getJob: "00mukk9dwd1c1f51xjysssgtv",
117
141
  cancelJob: "00mukk9dweizvif4ghp6kfa92",
118
142
  move: "00mukk9dwer8wsw5eiwis8tzm",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.25.10",
3
+ "version": "0.25.12",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"
@@ -28,9 +28,11 @@
28
28
  "build": "npm run clean && tsc",
29
29
  "dev": "tsc -w",
30
30
  "test": "jest",
31
- "release:patch": "npm run build && npm version patch && git push && git push --tags",
32
- "release:minor": "npm run build && npm version minor && git push && git push --tags",
33
- "release:major": "npm run build && npm version major && git push && git push --tags",
31
+ "release:guard": "node release-guard.js",
32
+ "prepublishOnly": "npm run release:guard",
33
+ "release:patch": "npm run release:guard && npm run build && npm version patch && git push && git push --tags",
34
+ "release:minor": "npm run release:guard && npm run build && npm version minor && git push && git push --tags",
35
+ "release:major": "npm run release:guard && npm run build && npm version major && git push && git push --tags",
34
36
  "release": "npm run release:patch"
35
37
  },
36
38
  "dependencies": {