{"record":{"id":"7cbf0d8200fd6027","repo":"ruvnet/ruflo","slug":"fractional-number-at-path-must-be-a-scale-12-decimal-string","errorCode":null,"errorMessage":"fractional number at ${path} must be a scale-12 decimal string (ADR-322C rule 2)","messagePattern":"fractional number at (.+?) must be a scale-12 decimal string \\(ADR-322C rule 2\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/flywheel-receipt.ts","lineNumber":284,"sourceCode":" * Enforce ADR-322C rule 2 / conformance-checklist A3 over a receipt payload:\n * \"Every fractional value is a canonical decimal string, not a binary float.\"\n *\n * This lives at the RECEIPT boundary rather than inside `canonicalizeJcs`,\n * which is shared with the proposer envelope (`flywheel-proposer.ts`) and the\n * promotion ledger (`flywheel-transaction.ts`) — structures the contract does\n * not govern. A first attempt put the check in the canonicalizer and broke\n * those callers, which is the reason the scope is spelled out here.\n *\n * Integers and scaled integers stay JSON numbers (currency micros, durations,\n * iteration counts). A fractional JSON number anywhere in the payload is a\n * contract violation, and the error names the path — the original bug\n * (ruvnet/ruflo#3229) needed a live fixture to find precisely because neither\n * verifier said which field was wrong.\n */\nexport function assertReceiptNumberDomain(value: unknown, path = '$'): void {\n  if (typeof value === 'number') {\n    if (!Number.isInteger(value)) {\n      throw new Error(\n        `fractional number at ${path} must be a scale-12 decimal string (ADR-322C rule 2)`,\n      );\n    }\n    return;\n  }\n  if (Array.isArray(value)) {\n    value.forEach((v, i) => assertReceiptNumberDomain(v, `${path}[${i}]`));\n    return;\n  }\n  if (value && typeof value === 'object') {\n    for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n      assertReceiptNumberDomain(v, `${path}.${k}`);\n    }\n  }\n}\n\nexport function policyCandidateId(policy: Record<string, unknown>): string {\n  // Encode at the hashing boundary so the content ID is always over the","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/ruvnet/ruflo/blob/2602b642d92234c710ffbe96bfb33007d481ceab/v3/@claude-flow/cli/src/services/flywheel-receipt.ts#L266-L302","documentation":"assertReceiptNumberDomain validates that receipt numbers conform to ADR-322C rule 2: every fractional value must be represented as a scale-12 decimal string, and plain JSON numbers must be integers. It throws when it encounters a fractional (non-integer) number because numbers like 0.1 are not exact in binary floating point and would make signed receipts non-deterministic.","triggerScenarios":"Calling assertReceiptNumberDomain (directly or via signedBytes/verifyFlywheelReceipt) on a receipt object that contains a non-integer JS number at the given path, e.g. { amount: 0.65 } instead of { amount: \"0.650000000000\" }.","commonSituations":"Deserializing a receipt that was produced by a client encoding decimals as JSON numbers; hand-writing a fixture with fractional literals; bypassing encodePolicyFractions and feeding raw policy objects to the verifier.","solutions":["Convert the fractional value at the reported path to a scale-12 decimal string","Run the receipt through encodePolicyFractions before signing or verifying","Fix the producer so fractional values are emitted as strings (see the ruvnet/ruflo#3229 fixture lesson)"],"exampleFix":"// before\n{ \"hybridWeight\": 0.65 }\n// after\n{ \"hybridWeight\": \"0.650000000000\" }","handlingStrategy":"validation","validationCode":"function assertScale12Strings(obj, path='$') { for (const [k,v] of Object.entries(obj)) { if (typeof v === 'number' && !Number.isInteger(v)) throw new Error(`fractional at ${path}.${k}: use scale-12 string`); if (v && typeof v === 'object') assertScale12Strings(v, `${path}.${k}`); } }","typeGuard":"const isReceiptNumberOk = (v: unknown): boolean => typeof v !== 'number' || Number.isInteger(v);","tryCatchPattern":"try { verifyFlywheelReceipt(receipt); } catch (e) { if (String(e.message).match(/fractional number at (.+) must be a scale-12/)) { console.error(`Fix field ${e.message.match(/at (\\S+) /)?.[1]} to a decimal string`); } throw e; }","preventionTips":["Always run encodePolicyFractions before signing or verifying receipts","Never hand-write fractional JSON numbers in receipt fixtures","Persist decimals as strings end-to-end across producers and consumers"],"tags":["validation","decimal","receipt","determinism"],"backgroundTag":"invalid-argument-value","analyzedSha":"2602b642d92234c710ffbe96bfb33007d481ceab","analyzedAt":"2026-09-15T22:58:14.805Z","contentChangedAt":"2026-09-15T22:58:14.805Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}