cultnet-ts: a blank text in a raw frame is Unicode White_Space, not trim()
validateBinaryMessageRoot, requireNonEmptyString and requireOptionalStringArray used String.prototype.trim(), which takes U+FEFF and misses U+0085, so the TS decoder and the servers disagreed on what names nothing (write-receipt rule 5). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3b1b769762
commit
b082101c78
2 changed files with 27 additions and 3 deletions
|
|
@ -1422,7 +1422,7 @@ function validateBinaryMessageRoot(
|
|||
if (candidate.schemaVersion !== schemaVersion) {
|
||||
throw new Error(`Validation failed for ${schemaVersion}: /schemaVersion: must equal ${schemaVersion}`);
|
||||
}
|
||||
if (typeof candidate.messageId !== "string" || candidate.messageId.trim().length === 0) {
|
||||
if (typeof candidate.messageId !== "string" || isBlank(candidate.messageId)) {
|
||||
throw new Error(`Validation failed for ${schemaVersion}: /messageId: must be a non-empty string`);
|
||||
}
|
||||
|
||||
|
|
@ -1460,8 +1460,13 @@ function validateRawDocumentRecord(
|
|||
requireOptionalStringArray(candidate.tags, `${instancePath}/tags`);
|
||||
}
|
||||
|
||||
/** Blank is empty or only Unicode White_Space (write-receipt.md, rule 5), not `trim()`: JS trims U+FEFF and keeps U+0085. */
|
||||
function isBlank(value: string): boolean {
|
||||
return /^\p{White_Space}*$/u.test(value);
|
||||
}
|
||||
|
||||
function requireNonEmptyString(input: unknown, instancePath: string): void {
|
||||
if (typeof input !== "string" || input.trim().length === 0) {
|
||||
if (typeof input !== "string" || isBlank(input)) {
|
||||
throw new Error(`Validation failed for raw CultNet document record: ${instancePath}: must be a non-empty string`);
|
||||
}
|
||||
}
|
||||
|
|
@ -1477,7 +1482,7 @@ function requireOptionalStringArray(input: unknown, instancePath: string): void
|
|||
if (typeof input === "undefined" || input === null) {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(input) || input.some((item) => typeof item !== "string" || item.trim().length === 0)) {
|
||||
if (!Array.isArray(input) || input.some((item) => typeof item !== "string" || isBlank(item))) {
|
||||
throw new Error(`Validation failed for raw CultNet document record: ${instancePath}: must be an array of non-empty strings`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -227,3 +227,22 @@ test("a write whose own send failed settles local_send_failed even when its wind
|
|||
tracker.sendFailed("w1", 1000);
|
||||
assert.deepEqual(tracker.state("w1"), { outcome: "unconfirmed", reason: "local_send_failed" });
|
||||
});
|
||||
|
||||
// Blank is Unicode White_Space (write-receipt.md, rule 5), not what String.prototype.trim() trims: trim() takes U+FEFF and
|
||||
// misses U+0085. Every frame decoder that rejects a blank text must agree with the servers that do.
|
||||
test("a blank messageId, record key or tag is exactly Unicode White_Space, not trim()", () => {
|
||||
const put = (document: Record<string, unknown>, messageId = "p1") => ({
|
||||
schemaVersion: "cultnet.document_put_raw.v0", messageId,
|
||||
document: { schemaId: "note.v0", recordKey: "note:1", storedAt: "2026-11-08T00:00:00Z", payloadEncoding: "messagepack", payload: Uint8Array.of(0x81, 0xa1, 0x61, 0x01), ...document },
|
||||
});
|
||||
for (const blank of ["\u0085", " ", " "]) {
|
||||
assert.throws(() => parseCultNetMessage(put({}, blank)), `messageId ${JSON.stringify(blank)}`);
|
||||
assert.throws(() => parseCultNetMessage(put({ recordKey: blank })), `recordKey ${JSON.stringify(blank)}`);
|
||||
assert.throws(() => parseCultNetMessage(put({ tags: [blank] })), `tag ${JSON.stringify(blank)}`);
|
||||
}
|
||||
for (const named of ["", "", "\u001c", ""]) {
|
||||
parseCultNetMessage(put({}, named));
|
||||
parseCultNetMessage(put({ recordKey: named }));
|
||||
parseCultNetMessage(put({ tags: [named] }));
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue