feat(server): take nest record counts, material and thickness from the archive

The server stored whatever plate count, part count, material and
thickness the client claimed, so a record could disagree with its own
archive, and a bulk importer had to read each written .nest back in full
just to describe it (about a quarter of the PEP archive import's time).

Uploads (POST and PUT /file) now read only the archive's nest.json -
never programs or geometry, and without referencing OpenNest.IO - and
overwrite those fields, following NestReader's fallbacks for thickness,
material, parts with a missing drawing and the trailing new-plate slot.
A file without a readable nest.json is 400 and stores nothing, so the
server can guarantee the record describes the bytes; nest.json
decompression stops at 64 MB. A metadata-only PUT keeps the stored
counts because the archive is unchanged; material and thickness stay
editable there, since the desktop applies the record's values on open.

Server tests that uploaded placeholder bytes now upload small
deterministic archives, and the container smoke's near-limit probe is a
padded real archive. New tests pin the server's values to a full
NestReader read of the same archive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-21 07:14:06 -04:00
1 parent 0eb6d5886a
commit 64e24fc6c8
12 files changed
+446 -73

No files matched your search

+22 -5
View File
@@ -191,14 +191,14 @@ internal static class Program
var before = await Snapshot(repository, ct);
Check(before.Count > 0, "Limit checks require seeded records on an owned server.");
// Opaque synthetic bytes (the server does not parse archives) above the 64 KiB form
// memory threshold, so the non-root runtime must buffer the part to its temp directory.
var accepted = new byte[29_000_000];
RandomNumberGenerator.Fill(accepted);
// A readable nest archive (the server parses its nest.json) padded with incompressible
// bytes above the 64 KiB form memory threshold, so the non-root runtime must buffer the
// part to its temp directory.
var accepted = PaddedArchive(29_000_000);
var created = await repository.UploadAsync(accepted, new NestRecord
{
Name = "Synthetic upload-limit probe",
Comments = "Random bytes; deleted by the same smoke stage",
Comments = "Random padding; deleted by the same smoke stage",
}, ct);
Check(created.Id != Guid.Empty && created.FileSize == accepted.LongLength,
"A near-limit upload must be stored with its exact size.");
@@ -363,6 +363,23 @@ internal static class Program
return stream.ToArray();
}
/// <summary>A minimal nest archive with a stored random entry, close to <paramref name="size"/> bytes.</summary>
private static byte[] PaddedArchive(int size)
{
using var stream = new MemoryStream();
using (var zip = new System.IO.Compression.ZipArchive(stream, System.IO.Compression.ZipArchiveMode.Create, leaveOpen: true))
{
using (var writer = new StreamWriter(zip.CreateEntry("nest.json").Open()))
writer.Write("""{"version":2,"name":"Synthetic upload-limit probe","drawings":[],"plates":[]}""");
var padding = new byte[size - 1024];
RandomNumberGenerator.Fill(padding);
using var entry = zip.CreateEntry("padding.bin", System.IO.Compression.CompressionLevel.NoCompression).Open();
entry.Write(padding);
}
return stream.ToArray();
}
private static GeometryState Geometry(Nest nest) => new()
{
PlateWidth = nest.Plates[0].Size.Width,