hoi4-agent-tools 4.0.5 → 4.2.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.
- package/CHANGELOG.md +32 -0
- package/README.md +2 -0
- package/dist/hoi4_agent_tools/core/bounded-source.js +210 -0
- package/dist/hoi4_agent_tools/core/canonical.js +42 -0
- package/dist/hoi4_agent_tools/core/condition-evaluator.js +12 -8
- package/dist/hoi4_agent_tools/core/dependency-walk.js +21 -2
- package/dist/hoi4_agent_tools/core/diagnostic-rollup.js +91 -0
- package/dist/hoi4_agent_tools/core/domain-validation.js +12 -0
- package/dist/hoi4_agent_tools/core/engine.js +2 -1
- package/dist/hoi4_agent_tools/core/impact-graph.js +6 -2
- package/dist/hoi4_agent_tools/core/impact-semantic.js +12 -2
- package/dist/hoi4_agent_tools/core/index.js +1 -0
- package/dist/hoi4_agent_tools/core/job-executor.js +9 -0
- package/dist/hoi4_agent_tools/core/job-worker-host.js +12 -2
- package/dist/hoi4_agent_tools/core/managed-outputs.js +72 -0
- package/dist/hoi4_agent_tools/core/operation-result.js +28 -2
- package/dist/hoi4_agent_tools/core/scanner.js +17 -0
- package/dist/hoi4_agent_tools/core/server-state.js +9 -1
- package/dist/hoi4_agent_tools/core/temporary-directories.js +5 -1
- package/dist/hoi4_agent_tools/core/transaction-canonical.js +43 -0
- package/dist/hoi4_agent_tools/core/transaction-limits.js +7 -2
- package/dist/hoi4_agent_tools/core/transactions.js +355 -111
- package/dist/hoi4_agent_tools/decision/service.js +22 -1
- package/dist/hoi4_agent_tools/event/algorithms.js +3 -2
- package/dist/hoi4_agent_tools/event/graph.js +52 -35
- package/dist/hoi4_agent_tools/event/limits.js +4 -0
- package/dist/hoi4_agent_tools/event/operations.js +11 -0
- package/dist/hoi4_agent_tools/event/queries.js +13 -34
- package/dist/hoi4_agent_tools/event/render.js +4 -2
- package/dist/hoi4_agent_tools/event/service.js +93 -33
- package/dist/hoi4_agent_tools/event/source-analysis.js +94 -4
- package/dist/hoi4_agent_tools/event/state-keys.js +63 -0
- package/dist/hoi4_agent_tools/event/summary.js +130 -0
- package/dist/hoi4_agent_tools/impact/service.js +2 -1
- package/dist/hoi4_agent_tools/map/derived.js +21 -6
- package/dist/hoi4_agent_tools/map/job-operations.js +9 -3
- package/dist/hoi4_agent_tools/map/operations.js +72 -21
- package/dist/hoi4_agent_tools/map/reference-labels.js +238 -0
- package/dist/hoi4_agent_tools/map/reference-preparation.js +247 -0
- package/dist/hoi4_agent_tools/map/reference-world.js +1097 -0
- package/dist/hoi4_agent_tools/map/render.js +9 -1
- package/dist/hoi4_agent_tools/map/service.js +222 -18
- package/dist/hoi4_agent_tools/map/tool-operations.js +71 -2
- package/dist/hoi4_agent_tools/map/world-context.js +148 -0
- package/dist/hoi4_agent_tools/map/world-drainage.js +273 -0
- package/dist/hoi4_agent_tools/map/world-outputs.js +55 -0
- package/dist/hoi4_agent_tools/map/world-preview-style.js +157 -0
- package/dist/hoi4_agent_tools/map/world-preview.js +230 -0
- package/dist/hoi4_agent_tools/map/world-provinces.js +524 -0
- package/dist/hoi4_agent_tools/map/world.js +374 -253
- package/dist/hoi4_agent_tools/mcp/tools/analysis.js +1 -0
- package/dist/hoi4_agent_tools/mcp/tools/event.js +66 -0
- package/dist/hoi4_agent_tools/mcp/tools/map.js +15 -2
- package/dist/hoi4_agent_tools/reference/source-lookup.js +4 -2
- package/dist/hoi4_agent_tools/schemas/map-requests.js +4 -0
- package/dist/hoi4_agent_tools/schemas/map.js +103 -10
- package/dist/hoi4_agent_tools/schemas/transaction.js +20 -3
- package/dist/hoi4_agent_tools/version.js +2 -2
- package/docs/analysis.md +4 -0
- package/docs/events.md +12 -0
- package/docs/images/reference-maps/political.png +0 -0
- package/docs/images/reference-maps/sketch.png +0 -0
- package/docs/map.md +45 -0
- package/docs/third-party-notices.md +6 -0
- package/npm-shrinkwrap.json +124 -2
- package/package.json +4 -1
- package/schemas/configuration.schema.json +1 -1
- package/schemas/continuous-focus-palette.schema.json +1 -1
- package/schemas/custom-weighted-pool.schema.json +1 -1
- package/schemas/decision-inspect-input.schema.json +1 -1
- package/schemas/error-log-input.schema.json +1 -1
- package/schemas/event-feature-manifest.schema.json +1 -1
- package/schemas/focus-plan.schema.json +1 -1
- package/schemas/focus-planning-sidecar.schema.json +1 -1
- package/schemas/gui-animation-source.schema.json +1 -1
- package/schemas/gui-helper.schema.json +1 -1
- package/schemas/gui-scenario.schema.json +1 -1
- package/schemas/helper-expansion-request.schema.json +1 -1
- package/schemas/helper-expansion-summary.schema.json +1 -1
- package/schemas/impact-inspect-input.schema.json +1 -1
- package/schemas/map-operation.schema.json +373 -70
- package/schemas/mechanic-test-input.schema.json +1 -1
- package/schemas/mod-index-input.schema.json +1 -1
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/package-check-input.schema.json +1 -1
- package/schemas/package-manifest.schema.json +1 -1
- package/schemas/probability-analysis-result.schema.json +1 -1
- package/schemas/probability-compare-input.schema.json +1 -1
- package/schemas/probability-evaluate-input.schema.json +1 -1
- package/schemas/probability-inspect-input.schema.json +1 -1
- package/schemas/probability-render-input.schema.json +1 -1
- package/schemas/probability-scenario-set.schema.json +1 -1
- package/schemas/probability-sequence-input.schema.json +1 -1
- package/schemas/probability-simulate-input.schema.json +1 -1
- package/schemas/probability-sweep-input.schema.json +1 -1
- package/schemas/reference-context-input.schema.json +1 -1
- package/schemas/reference-read-input.schema.json +1 -1
- package/schemas/reference-search-input.schema.json +1 -1
- package/schemas/scenario-suite.schema.json +1 -1
- package/schemas/scenario-test-input.schema.json +1 -1
- package/schemas/script-validate-input.schema.json +1 -1
- package/schemas/shared-scenario.schema.json +1 -1
- package/schemas/source-lookup-input.schema.json +1 -1
- package/server.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { closeSync, fstatSync, openSync, readSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
3
|
-
import { mkdir } from 'node:fs/promises';
|
|
3
|
+
import { mkdir, stat } from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
@@ -134,6 +134,16 @@ function retainOrRemoveLog(file, evidence, logRoot) {
|
|
|
134
134
|
* Bounded, fixed-entry child execution. Client disconnection never kills a worker; an
|
|
135
135
|
* overdue or unresponsive cancelled read-only worker is stopped by its supervising host.
|
|
136
136
|
*/
|
|
137
|
+
/**
|
|
138
|
+
* Select the workspaces a job worker resolves again. A mod folder removed after the host
|
|
139
|
+
* discovered it must not stop jobs for every other workspace, so absent roots are left out.
|
|
140
|
+
* The job's own workspace is always kept, and a missing root still fails that job explicitly.
|
|
141
|
+
*/
|
|
142
|
+
export async function workerWorkspaces(workspaces, workspaceId) {
|
|
143
|
+
const retained = await Promise.all(workspaces.map(async (workspace) => workspace.id === workspaceId ||
|
|
144
|
+
(await stat(workspace.modRoot).then((metadata) => metadata.isDirectory(), () => false))));
|
|
145
|
+
return workspaces.filter((_workspace, index) => retained[index]);
|
|
146
|
+
}
|
|
137
147
|
export class JobWorkerHost {
|
|
138
148
|
engine;
|
|
139
149
|
jobs;
|
|
@@ -234,7 +244,7 @@ export class JobWorkerHost {
|
|
|
234
244
|
return;
|
|
235
245
|
const configuration = this.engine.resolver.config();
|
|
236
246
|
const registeredWorkspaceIds = new Set(configuration.workspaces.map((workspace) => workspace.id));
|
|
237
|
-
const resolvedWorkspaces = this.engine.resolver.list();
|
|
247
|
+
const resolvedWorkspaces = await workerWorkspaces(this.engine.resolver.list(), workspaceId);
|
|
238
248
|
const discoveredWorkspaceIds = resolvedWorkspaces
|
|
239
249
|
.map((workspace) => workspace.id)
|
|
240
250
|
.filter((workspaceId) => !registeredWorkspaceIds.has(workspaceId));
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { canonicalJson, compareCodeUnits, sha256Bytes } from './canonical.js';
|
|
3
|
+
import { ServiceError } from './result.js';
|
|
4
|
+
const manifestSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
version: z.literal(1),
|
|
7
|
+
files: z
|
|
8
|
+
.array(z
|
|
9
|
+
.object({
|
|
10
|
+
relativePath: z.string().min(1).max(512),
|
|
11
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
12
|
+
})
|
|
13
|
+
.strict())
|
|
14
|
+
.max(5000),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
17
|
+
/** Pure planning only: the shared transaction engine performs every replacement and deletion. */
|
|
18
|
+
export function reconcileManagedOutputs(input) {
|
|
19
|
+
const sources = new Map(input.sources
|
|
20
|
+
.filter((f) => f.rootKind === 'mod')
|
|
21
|
+
.map((f) => [f.relativePath.replaceAll('\\', '/').toLowerCase(), f]));
|
|
22
|
+
const manifest = sources.get(input.manifestPath.toLowerCase());
|
|
23
|
+
const previous = manifest === undefined
|
|
24
|
+
? []
|
|
25
|
+
: (() => {
|
|
26
|
+
if (manifest.bytes.length > 2_000_000)
|
|
27
|
+
throw new ServiceError('MANAGED_OUTPUT_MANIFEST_INVALID', 'Generated-output inventory exceeds its byte limit');
|
|
28
|
+
try {
|
|
29
|
+
return manifestSchema.parse(JSON.parse(manifest.bytes.toString('utf8'))).files;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new ServiceError('MANAGED_OUTPUT_MANIFEST_INVALID', 'Generated-output inventory is invalid');
|
|
33
|
+
}
|
|
34
|
+
})();
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
for (const file of previous) {
|
|
37
|
+
const key = file.relativePath.toLowerCase();
|
|
38
|
+
if (file.relativePath.includes('\\') ||
|
|
39
|
+
file.relativePath.split('/').some((s) => !s || s === '.' || s === '..') ||
|
|
40
|
+
/^[a-z]:/iu.test(file.relativePath) ||
|
|
41
|
+
!input.permitsPath(file.relativePath) ||
|
|
42
|
+
seen.has(key))
|
|
43
|
+
throw new ServiceError('MANAGED_OUTPUT_MANIFEST_INVALID', 'Generated-output inventory contains an unsupported or duplicate path');
|
|
44
|
+
seen.add(key);
|
|
45
|
+
const current = sources.get(key);
|
|
46
|
+
if (current !== undefined && sha256Bytes(current.bytes) !== file.sha256)
|
|
47
|
+
throw new ServiceError('MANAGED_OUTPUT_EDITED', 'A generated file has been edited; regeneration would discard those changes', { relativePath: file.relativePath });
|
|
48
|
+
}
|
|
49
|
+
const outputs = input.proposed.filter((f) => f.content !== null && input.permitsPath(f.relativePath));
|
|
50
|
+
const next = new Set(outputs.map((f) => f.relativePath.toLowerCase()));
|
|
51
|
+
const deletions = previous
|
|
52
|
+
.filter((f) => !next.has(f.relativePath.toLowerCase()) && sources.has(f.relativePath.toLowerCase()))
|
|
53
|
+
.map((f) => ({
|
|
54
|
+
relativePath: f.relativePath,
|
|
55
|
+
content: null,
|
|
56
|
+
operationIds: [input.operationId],
|
|
57
|
+
}));
|
|
58
|
+
return [
|
|
59
|
+
...deletions,
|
|
60
|
+
{
|
|
61
|
+
relativePath: input.manifestPath,
|
|
62
|
+
content: Buffer.from(canonicalJson({
|
|
63
|
+
version: 1,
|
|
64
|
+
files: outputs
|
|
65
|
+
.map((f) => ({ relativePath: f.relativePath, sha256: sha256Bytes(f.content) }))
|
|
66
|
+
.sort((a, b) => compareCodeUnits(a.relativePath, b.relativePath)),
|
|
67
|
+
}) + '\n'),
|
|
68
|
+
operationIds: [input.operationId],
|
|
69
|
+
mediaType: 'application/json',
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
}
|
|
@@ -500,10 +500,36 @@ export function errorResult(error, workspaceId = '', context = {}) {
|
|
|
500
500
|
? `REWRITE_${serviceError.code.slice('TRANSACTION_'.length)}`
|
|
501
501
|
: serviceError.code;
|
|
502
502
|
const publicMessage = internalWriteError
|
|
503
|
-
?
|
|
503
|
+
? serviceError.code === 'TRANSACTION_STRUCTURE_LIMIT'
|
|
504
|
+
? 'Rewrite exceeds the supported file, operation, validation, or evidence limits'
|
|
505
|
+
: serviceError.code === 'TRANSACTION_JOURNAL_LIMIT'
|
|
506
|
+
? 'The requested rewrite exceeds the bounded rollback storage allowance'
|
|
507
|
+
: 'Rewrite could not be completed'
|
|
504
508
|
: serviceError.message;
|
|
505
509
|
const publicDetails = internalWriteError
|
|
506
|
-
?
|
|
510
|
+
? ['TRANSACTION_STRUCTURE_LIMIT', 'TRANSACTION_JOURNAL_LIMIT'].includes(serviceError.code)
|
|
511
|
+
? Object.fromEntries(Object.entries(serviceError.details).filter(([key, value]) => [
|
|
512
|
+
'files',
|
|
513
|
+
'filesLimit',
|
|
514
|
+
'operations',
|
|
515
|
+
'operationsLimit',
|
|
516
|
+
'readDependencies',
|
|
517
|
+
'readDependenciesLimit',
|
|
518
|
+
'artifacts',
|
|
519
|
+
'artifactsLimit',
|
|
520
|
+
'diagnostics',
|
|
521
|
+
'diagnosticsLimit',
|
|
522
|
+
'checks',
|
|
523
|
+
'checksLimit',
|
|
524
|
+
'requestedWorkBytes',
|
|
525
|
+
'plannedJournalBytes',
|
|
526
|
+
'retainedBytes',
|
|
527
|
+
'maximumBytes',
|
|
528
|
+
].includes(key) &&
|
|
529
|
+
typeof value === 'number' &&
|
|
530
|
+
Number.isSafeInteger(value) &&
|
|
531
|
+
value >= 0))
|
|
532
|
+
: {}
|
|
507
533
|
: Object.fromEntries(Object.entries(serviceError.details).filter(([key]) => ![
|
|
508
534
|
'cause',
|
|
509
535
|
'stack',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { open, realpath, stat } from 'node:fs/promises';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { iterateFiles } from './file-glob.js';
|
|
4
5
|
import { compareCodeUnits, sha256Bytes } from './canonical.js';
|
|
@@ -7,6 +8,22 @@ import { ServiceError } from './result.js';
|
|
|
7
8
|
import { isWithin } from './workspace.js';
|
|
8
9
|
const SCAN_IO_CONCURRENCY = 8;
|
|
9
10
|
const SCAN_READ_BATCH_BYTES = 16_777_216;
|
|
11
|
+
/** Hash an open source using fixed memory; callers verify its canonical identity afterwards. */
|
|
12
|
+
export async function hashSourceBytes(source, signal) {
|
|
13
|
+
const hash = createHash('sha256');
|
|
14
|
+
const chunk = Buffer.alloc(Math.max(1, Math.min(1_048_576, source.metadata.size)));
|
|
15
|
+
let offset = 0;
|
|
16
|
+
while (offset < source.metadata.size) {
|
|
17
|
+
signal?.throwIfAborted();
|
|
18
|
+
const read = await source.handle.read(chunk, 0, Math.min(chunk.length, source.metadata.size - offset), offset);
|
|
19
|
+
if (!read.bytesRead)
|
|
20
|
+
throw new ServiceError('SCAN_SOURCE_CHANGED', 'Source changed while being hashed');
|
|
21
|
+
hash.update(chunk.subarray(0, read.bytesRead));
|
|
22
|
+
offset += read.bytesRead;
|
|
23
|
+
}
|
|
24
|
+
signal?.throwIfAborted();
|
|
25
|
+
return hash.digest('hex');
|
|
26
|
+
}
|
|
10
27
|
export async function verifySource(root, source) {
|
|
11
28
|
const resolved = await realpath(source.absolutePath);
|
|
12
29
|
if (!isWithin(root.path, resolved))
|
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { z } from 'zod/v4';
|
|
5
5
|
import { compareCodeUnits, canonicalJson, secureId } from './canonical.js';
|
|
6
6
|
import { ServiceError } from './result.js';
|
|
7
|
+
import { TRANSACTION_MAX_MANIFEST_BYTES } from './transaction-limits.js';
|
|
7
8
|
const journalKeyFile = 'journal-hmac.key';
|
|
8
9
|
const journalKeyBytes = 32;
|
|
9
10
|
const sha256Schema = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
@@ -157,7 +158,14 @@ export class ServerState {
|
|
|
157
158
|
return new ServerState(canonicalRoot, Buffer.from(key));
|
|
158
159
|
}
|
|
159
160
|
authenticateJournal(payload) {
|
|
160
|
-
return
|
|
161
|
+
return this.authenticateCanonicalJournal(canonicalJson(payload));
|
|
162
|
+
}
|
|
163
|
+
/** Internal canonical-byte path; the signature is identical to authenticateJournal. */
|
|
164
|
+
authenticateCanonicalJournal(payload) {
|
|
165
|
+
if (Buffer.byteLength(payload, 'utf8') > TRANSACTION_MAX_MANIFEST_BYTES) {
|
|
166
|
+
throw stateError('TRANSACTION_MANIFEST_LIMIT', 'Transaction manifest exceeds its fixed byte limit');
|
|
167
|
+
}
|
|
168
|
+
return createHmac('sha256', this.journalKey).update(payload).digest('hex');
|
|
161
169
|
}
|
|
162
170
|
verifyJournal(payload, authenticationTag) {
|
|
163
171
|
if (!/^[a-f0-9]{64}$/u.test(authenticationTag))
|
|
@@ -10,6 +10,7 @@ export const TEMPORARY_MAX_BYTES = 256 * 1024 * 1024;
|
|
|
10
10
|
export const TEMPORARY_STALE_MS = 3 * 60 * 60 * 1000;
|
|
11
11
|
// Dated event evaluation directories contain retained benchmark evidence, not scratch files.
|
|
12
12
|
const scratchName = /^hoi4-(?:reference-source|event-eval)-[A-Za-z0-9]{6}$/u;
|
|
13
|
+
const referenceSnapshotName = /^\.reference-snapshot-[A-Za-z0-9]{6}$/u;
|
|
13
14
|
const ownerSchema = z
|
|
14
15
|
.object({
|
|
15
16
|
version: z.literal(1),
|
|
@@ -100,7 +101,8 @@ export async function sweepTemporaryDirectories(parent, options = {}) {
|
|
|
100
101
|
}
|
|
101
102
|
}))).flat();
|
|
102
103
|
const candidates = [];
|
|
103
|
-
|
|
104
|
+
const names = options.referenceSnapshotsOnly ? referenceSnapshotName : scratchName;
|
|
105
|
+
for (const name of (await readdir(root)).filter((name) => names.test(name)).sort()) {
|
|
104
106
|
const directory = path.join(root, name);
|
|
105
107
|
if (protectedRoots.some((protectedRoot) => overlaps(directory, protectedRoot)))
|
|
106
108
|
continue;
|
|
@@ -112,6 +114,8 @@ export async function sweepTemporaryDirectories(parent, options = {}) {
|
|
|
112
114
|
(await realpath(directory)) !== directory)
|
|
113
115
|
continue;
|
|
114
116
|
const record = await owner(directory);
|
|
117
|
+
if (options.referenceSnapshotsOnly && record == null)
|
|
118
|
+
continue;
|
|
115
119
|
// An interrupted sweep must not leave a permanent lock. Malformed or live locks
|
|
116
120
|
// stay protected; only a confirmed dead local owner can relinquish its lock.
|
|
117
121
|
const previousSweep = await owner(directory, SWEEP_LOCK);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { canonicalJson, compareCodeUnits } from './canonical.js';
|
|
2
|
+
import { ServiceError } from './result.js';
|
|
3
|
+
import { TRANSACTION_MAX_MANIFEST_BYTES } from './transaction-limits.js';
|
|
4
|
+
/** A request-local snapshot of canonical immutable fields, without shared writable references. */
|
|
5
|
+
export class TransactionCanonicalSnapshot {
|
|
6
|
+
immutable = new Map();
|
|
7
|
+
mutable;
|
|
8
|
+
constructor(source, mutableKeys) {
|
|
9
|
+
this.mutable = new Set(mutableKeys);
|
|
10
|
+
let bytes = 2;
|
|
11
|
+
for (const [key, value] of Object.entries(source)) {
|
|
12
|
+
if (this.mutable.has(key) || value === undefined)
|
|
13
|
+
continue;
|
|
14
|
+
const encoded = canonicalJson(value);
|
|
15
|
+
bytes += Buffer.byteLength(JSON.stringify(key)) + Buffer.byteLength(encoded) + 2;
|
|
16
|
+
this.assertBound(bytes);
|
|
17
|
+
this.immutable.set(key, encoded);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
serialize(source, excludedKeys = []) {
|
|
21
|
+
const mutable = source;
|
|
22
|
+
const excluded = new Set(excludedKeys);
|
|
23
|
+
const keys = [...new Set([...this.immutable.keys(), ...this.mutable])]
|
|
24
|
+
.filter((key) => !excluded.has(key) && (this.immutable.has(key) || mutable[key] !== undefined))
|
|
25
|
+
.sort(compareCodeUnits);
|
|
26
|
+
// JSON objects emit integer keys before other keys, including after canonical insertion.
|
|
27
|
+
const orderedKeys = Object.keys(Object.fromEntries(keys.map((key) => [key, null])));
|
|
28
|
+
let bytes = 2;
|
|
29
|
+
const entries = orderedKeys.map((key) => {
|
|
30
|
+
const value = this.immutable.get(key) ?? canonicalJson(mutable[key]);
|
|
31
|
+
const entry = `${JSON.stringify(key)}:${value}`;
|
|
32
|
+
bytes += Buffer.byteLength(entry) + 1;
|
|
33
|
+
this.assertBound(bytes);
|
|
34
|
+
return entry;
|
|
35
|
+
});
|
|
36
|
+
return `{${entries.join(',')}}`;
|
|
37
|
+
}
|
|
38
|
+
assertBound(bytes) {
|
|
39
|
+
if (bytes > TRANSACTION_MAX_MANIFEST_BYTES) {
|
|
40
|
+
throw new ServiceError('TRANSACTION_MANIFEST_LIMIT', 'Transaction manifest exceeds its fixed byte limit');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
// A generated world can contain thousands of state/region files. Review collections
|
|
2
|
+
// remain bounded independently of the unchanged 16 MiB authenticated manifest cap.
|
|
3
|
+
export const TRANSACTION_MAX_FILES = 5_000;
|
|
2
4
|
export const TRANSACTION_MAX_OPERATIONS = 1_000;
|
|
3
5
|
export const TRANSACTION_MAX_VALIDATION_CHECKS = 100;
|
|
4
6
|
export const TRANSACTION_MAX_DIAGNOSTICS = 100;
|
|
5
|
-
export const TRANSACTION_MAX_ARTIFACTS =
|
|
7
|
+
export const TRANSACTION_MAX_ARTIFACTS = 6_000;
|
|
6
8
|
export const TRANSACTION_MAX_APPLIED_FILES = TRANSACTION_MAX_FILES;
|
|
7
9
|
export const TRANSACTION_MAX_READ_DEPENDENCIES = 10_000;
|
|
8
10
|
export const TRANSACTION_MAX_MANIFEST_BYTES = 16_777_216;
|
|
11
|
+
export const TRANSACTION_MAX_PENDING_FILES = 16;
|
|
12
|
+
// A maximum-size world needs before/after blobs plus staging and rollback copies.
|
|
13
|
+
export const TRANSACTION_MAX_JOURNAL_BYTES = 1_073_741_824;
|