mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-29 21:15:16 +02:00
feat(profiles): add opt-in Lean/Full and hybrid Auto selection
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# Incident 2026-09-24: order totals off by one cent
|
||||
|
||||
## Root cause
|
||||
|
||||
**C-2** — the totals refactor in `src/totals.js`.
|
||||
|
||||
The refactor replaced integer-cent arithmetic with a decimal discount factor
|
||||
(`priceCents * quantity * (1 - discountPercent / 100)`). Decimal factors such
|
||||
as 0.7 or 0.93 have no exact binary floating-point representation, so for
|
||||
line amounts whose exact discounted value lands precisely on a half-cent
|
||||
boundary (e.g. 165 cents at 30% off = 115.5), the float result lands just
|
||||
below the boundary and `Math.round` rounds down instead of half-up. Every
|
||||
affected order is undercharged by exactly one cent, matching the finance
|
||||
findings in `evidence/incident.txt`.
|
||||
|
||||
## Evidence
|
||||
|
||||
- `evidence/incident.txt`: every flagged order is off by exactly one cent in the
|
||||
store's favor, and all of them appeared after the 2026-09-23 deploy.
|
||||
- C-1 (logging) and C-3 (inventory timeout) cannot change totals; C-2 touched
|
||||
the totals computation itself.
|
||||
|
||||
## Fix
|
||||
|
||||
`src/totals.js` now computes line discounts with exact integer arithmetic:
|
||||
`floor((priceCents * quantity * (100 - discountPercent) + 50) / 100)`, which
|
||||
rounds half-up on exact cent boundaries with no floating-point error.
|
||||
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
// Fixed after the 2026-09-24 incident: totals use exact integer-cent
|
||||
// arithmetic. Per line: priceCents * quantity * (100 - discountPercent) / 100,
|
||||
// rounded half-up via (n + 50) / 100 floored — no floating point anywhere.
|
||||
function computeOrderTotal(order) {
|
||||
let total = 0;
|
||||
for (const line of order.lines) {
|
||||
const numerator = line.priceCents * line.quantity * (100 - order.discountPercent);
|
||||
total += Math.floor((numerator + 50) / 100);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
module.exports = { computeOrderTotal };
|
||||
@@ -0,0 +1,120 @@
|
||||
'use strict';
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const http = require('node:http');
|
||||
const config = require('./config');
|
||||
const store = require('./store');
|
||||
|
||||
const HTML_ESCAPES = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
||||
const escapeHtml = text => text.replace(/[&<>"']/g, char => HTML_ESCAPES[char]);
|
||||
|
||||
function sendJson(res, status, value) {
|
||||
res.writeHead(status, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify(value));
|
||||
}
|
||||
|
||||
function readBody(req, res, callback) {
|
||||
const chunks = [];
|
||||
let bytes = 0;
|
||||
let rejected = false;
|
||||
req.on('data', chunk => {
|
||||
bytes += chunk.length;
|
||||
if (bytes > config.MAX_BODY_BYTES && !rejected) {
|
||||
rejected = true;
|
||||
sendJson(res, 413, { error: 'payload too large' });
|
||||
req.destroy();
|
||||
return;
|
||||
}
|
||||
chunks.push(chunk);
|
||||
});
|
||||
req.on('end', () => { if (!rejected) callback(Buffer.concat(chunks).toString('utf8')); });
|
||||
}
|
||||
|
||||
function page(paste) {
|
||||
return `<!doctype html><html><head><title>paste ${paste.id}</title></head>`
|
||||
+ `<body><main><pre class="paste">${escapeHtml(paste.content)}</pre></main></body></html>`;
|
||||
}
|
||||
|
||||
function createApp() {
|
||||
const adminToken = process.env.ADMIN_TOKEN || null;
|
||||
|
||||
return http.createServer((req, res) => {
|
||||
const url = new URL(req.url, 'http://localhost');
|
||||
|
||||
if (req.method === 'POST' && url.pathname === '/pastes') {
|
||||
readBody(req, res, body => {
|
||||
let parsed;
|
||||
try { parsed = JSON.parse(body); } catch {
|
||||
sendJson(res, 400, { error: 'invalid JSON body' });
|
||||
return;
|
||||
}
|
||||
if (typeof parsed.content !== 'string') {
|
||||
sendJson(res, 400, { error: 'content must be a string' });
|
||||
return;
|
||||
}
|
||||
const paste = store.create(parsed.content);
|
||||
sendJson(res, 201, { id: paste.id, deleteToken: paste.deleteToken });
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const pasteMatch = /^\/pastes\/([\w-]+)$/.exec(url.pathname);
|
||||
if (pasteMatch && req.method === 'GET') {
|
||||
const paste = store.get(pasteMatch[1]);
|
||||
if (!paste) { sendJson(res, 404, { error: 'not found' }); return; }
|
||||
sendJson(res, 200, { id: paste.id, content: paste.content });
|
||||
return;
|
||||
}
|
||||
if (pasteMatch && req.method === 'DELETE') {
|
||||
const paste = store.get(pasteMatch[1]);
|
||||
if (!paste) { sendJson(res, 404, { error: 'not found' }); return; }
|
||||
if (req.headers['x-delete-token'] !== paste.deleteToken) {
|
||||
sendJson(res, 403, { error: 'bad delete token' });
|
||||
return;
|
||||
}
|
||||
store.remove(paste.id);
|
||||
res.writeHead(204);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const pageMatch = /^\/p\/([\w-]+)$/.exec(url.pathname);
|
||||
if (pageMatch && req.method === 'GET') {
|
||||
const paste = store.get(pageMatch[1]);
|
||||
if (!paste) { sendJson(res, 404, { error: 'not found' }); return; }
|
||||
res.writeHead(200, { 'content-type': 'text/html' });
|
||||
res.end(page(paste));
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method === 'GET' && url.pathname === '/files') {
|
||||
const name = url.searchParams.get('name') || '';
|
||||
const resolved = path.resolve(config.FILES_DIR, name);
|
||||
if (resolved !== config.FILES_DIR && !resolved.startsWith(config.FILES_DIR + path.sep)) {
|
||||
sendJson(res, 400, { error: 'invalid file name' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const content = fs.readFileSync(resolved);
|
||||
res.writeHead(200, { 'content-type': 'text/plain' });
|
||||
res.end(content);
|
||||
} catch {
|
||||
sendJson(res, 404, { error: 'not found' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method === 'GET' && url.pathname === '/admin/stats') {
|
||||
if (!adminToken || req.headers['x-admin-token'] !== adminToken) {
|
||||
sendJson(res, 401, { error: 'unauthorized' });
|
||||
return;
|
||||
}
|
||||
sendJson(res, 200, store.stats());
|
||||
return;
|
||||
}
|
||||
|
||||
sendJson(res, 404, { error: 'not found' });
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { createApp };
|
||||
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
const path = require('node:path');
|
||||
|
||||
module.exports = {
|
||||
MAX_BODY_BYTES: 64 * 1024,
|
||||
FILES_DIR: path.join(__dirname, '..', 'data', 'files'),
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
const crypto = require('node:crypto');
|
||||
|
||||
// In-memory paste store. Delete tokens are cryptographically random and shown
|
||||
// once at creation.
|
||||
const pastes = new Map();
|
||||
let nextId = 1;
|
||||
|
||||
function create(content) {
|
||||
const id = `p_${nextId++}`;
|
||||
const paste = { id, content, deleteToken: crypto.randomBytes(16).toString('hex') };
|
||||
pastes.set(id, paste);
|
||||
return paste;
|
||||
}
|
||||
|
||||
function get(id) {
|
||||
return pastes.get(id) || null;
|
||||
}
|
||||
|
||||
function remove(id) {
|
||||
return pastes.delete(id);
|
||||
}
|
||||
|
||||
function stats() {
|
||||
return { pastes: pastes.size, created: nextId - 1 };
|
||||
}
|
||||
|
||||
module.exports = { create, get, remove, stats };
|
||||
@@ -0,0 +1,73 @@
|
||||
'use strict';
|
||||
const http = require('node:http');
|
||||
const crypto = require('node:crypto');
|
||||
|
||||
const MAX_ATTEMPTS = 5;
|
||||
const BASE_DELAY_MS = 100;
|
||||
|
||||
function createRelay() {
|
||||
const deliveries = new Map();
|
||||
|
||||
async function attempt(record) {
|
||||
record.attempts += 1;
|
||||
try {
|
||||
const response = await fetch(record.url, {
|
||||
method: 'POST', headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(record.payload), signal: AbortSignal.timeout(5000) });
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
record.status = 'delivered';
|
||||
record.lastError = null;
|
||||
return;
|
||||
}
|
||||
record.lastError = `HTTP ${response.status}`;
|
||||
} catch (error) {
|
||||
record.lastError = error && error.message ? error.message : 'delivery failed';
|
||||
}
|
||||
if (record.attempts >= MAX_ATTEMPTS) {
|
||||
record.status = 'dead';
|
||||
return;
|
||||
}
|
||||
const delay = BASE_DELAY_MS * 2 ** (record.attempts - 1);
|
||||
setTimeout(() => { void attempt(record); }, delay);
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.method === 'POST' && req.url === '/deliveries') {
|
||||
let body = '';
|
||||
req.on('data', chunk => { body += chunk; });
|
||||
req.on('end', () => {
|
||||
let parsed;
|
||||
try { parsed = JSON.parse(body); } catch {
|
||||
res.writeHead(400, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'invalid JSON body' }));
|
||||
return;
|
||||
}
|
||||
const id = crypto.randomUUID();
|
||||
const record = { id, url: parsed.url, payload: parsed.payload,
|
||||
status: 'pending', attempts: 0, lastError: null };
|
||||
deliveries.set(id, record);
|
||||
void attempt(record);
|
||||
res.writeHead(202, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ id }));
|
||||
});
|
||||
return;
|
||||
}
|
||||
const match = /^\/deliveries\/([0-9a-f-]+)$/.exec(req.url || '');
|
||||
if (req.method === 'GET' && match) {
|
||||
const record = deliveries.get(match[1]);
|
||||
if (!record) {
|
||||
res.writeHead(404, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'not found' }));
|
||||
return;
|
||||
}
|
||||
res.writeHead(200, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify(record));
|
||||
return;
|
||||
}
|
||||
res.writeHead(404, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'not found' }));
|
||||
});
|
||||
return server;
|
||||
}
|
||||
|
||||
module.exports = { createRelay };
|
||||
Reference in New Issue
Block a user