diff --git a/packages/core/src/v3/isomorphic/friendlyId.test.ts b/packages/core/src/v3/isomorphic/friendlyId.test.ts index a0be5a69f..b5ea7a519 100644 --- a/packages/core/src/v3/isomorphic/friendlyId.test.ts +++ b/packages/core/src/v3/isomorphic/friendlyId.test.ts @@ -446,7 +446,10 @@ describe("waitpoint ids: run-ops format with version char w", () => { it("classifies both the prefixed and the bare form identically", () => { const body = generateWaitpointId("MANUAL"); - expect(parseWaitpointId(body)).toEqual(parseWaitpointId(`waitpoint_${body}`)); + const bare = parseWaitpointId(body); + const prefixed = parseWaitpointId(`waitpoint_${body}`); + expect(bare).toEqual(prefixed); + expect(bare).toEqual({ format: "b32hexW", type: "MANUAL", timestamp: expect.any(Date) }); }); it("recovers the mint timestamp from the core", () => { @@ -494,6 +497,26 @@ describe("waitpoint ids: run-ops format with version char w", () => { expect(parseWaitpointId(generateRunOpsIdV2("7")).format).toBe("legacy"); expect(parseRunId(`run_${generateWaitpointId("RUN")}`).format).toBe("legacy"); }); + + it("rejects a well-formed waitpoint body wearing a foreign prefix", () => { + const body = `${"0".repeat(24)}rw`; // valid core + RUN type char + version w + expect(parseWaitpointId(`run_${body}`).format).toBe("legacy"); + expect(parseWaitpointId(`batch_${body}`).format).toBe("legacy"); + expect(parseWaitpointId(`waitpoint_${body}`)).toEqual({ + format: "b32hexW", + type: "RUN", + timestamp: expect.any(Date), + }); + expect(parseWaitpointId(body).format).toBe("b32hexW"); + }); + + it("handles a bare body that happens to contain an underscore sanely (never throws, never misclassifies)", () => { + const body = generateWaitpointId("BATCH"); + const withUnderscore = `_${body.slice(1)}`; + expect(() => parseWaitpointId(withUnderscore)).not.toThrow(); + // "_" is outside the base32hex alphabet, so this can never be a real waitpoint id. + expect(parseWaitpointId(withUnderscore).format).toBe("legacy"); + }); }); describe("deriveWaitpointIdFromAnchor", () => { diff --git a/packages/core/src/v3/isomorphic/friendlyId.ts b/packages/core/src/v3/isomorphic/friendlyId.ts index 443ca9fc4..2f436b93a 100644 --- a/packages/core/src/v3/isomorphic/friendlyId.ts +++ b/packages/core/src/v3/isomorphic/friendlyId.ts @@ -291,7 +291,7 @@ export function deriveWaitpointIdFromAnchor( anchorId: string, type: WaitpointIdType ): string | undefined { - const body = stripIdPrefix(anchorId); + const body = stripAnchorPrefix(anchorId); if (!parseRunOpsIdBody(body) && !parseRunOpsIdV2Body(body)) { return undefined; } @@ -300,12 +300,14 @@ export function deriveWaitpointIdFromAnchor( } /** - * Classify a waitpoint id. Accepts the prefixed form (`waitpoint_
`) and the bare - * internal form, because both circulate: IdUtil.generate() returns each, and the store's - * own call sites carry the internal one. Total: never throws. + * Classify a waitpoint id. Accepts the prefixed (`waitpoint_`) and bare forms, but + * NOT another entity's prefix (`run_`, `batch_`, ...) — this is the discriminator a + * later ticket uses to route a possibly customer-supplied id, so a foreign prefix must + * classify legacy rather than have its body reinterpreted as a waitpoint id. Total: + * never throws. */ export function parseWaitpointId(id: string): ParsedWaitpointId { - const body = stripIdPrefix(id); + const body = stripWaitpointIdPrefix(id); if (body.length !== RUN_OPS_ID_LENGTH) return LEGACY_WAITPOINT_ID; if (body[RUN_OPS_ID_VERSION_INDEX] !== WAITPOINT_ID_VERSION) return LEGACY_WAITPOINT_ID; @@ -318,14 +320,23 @@ export function parseWaitpointId(id: string): ParsedWaitpointId { return { format: "b32hexW", type, timestamp }; } -// Strip a single leading `