Hosted uploads larger than the 4 MB multipart ceiling (Vercel's 4.5 MB request-body cap) now go POST /upload/create (signed PUT URL, rate-limited) -> PUT to the raw Storage URL -> POST /upload/complete (server-side magic-byte and size validation, sha256, WORM move, idempotent), reusing the #1378 pending-upload primitives. uploadAndExtract is split into uploadDocument + processArchivedDocument so both paths share the inbox pipeline. Dokumentinkorgen and the supplier-invoice form use the new path only above the threshold; files that fit keep the multipart route. Cap stays at 10 MB (the issue asks for 20 MB: founder call). Refs #1551
This commit is contained in:
@@ -1340,6 +1340,7 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
|
||||
[2026-08-28] /migrate SIE guard skips company-info-only runs (all entity flags false) and the wizard derives "SIE already imported" from the preview OR this session's successful /import-sie results: company info writes no accounts, balances or subledger rows, so the BFL rationale does not apply; and the one-shot preview went stale after phase 1 succeeded and phase 2 failed, falsely blocking an entities-only retry (#2000 review).
|
||||
[2026-08-28] get_vat_ruta_source_lines (the VAT ruta drill-down) now applies the same four exclusions as get_vat_declaration_totals (the filed figure): posted closing entries, source_type 'vat_settlement', the two kontantmetod year-end reversals, and settlement-SHAPED entries (a line on a ruta account plus a line on 2650/1650). It previously filtered on company, status and date only, so expanding a ruta listed verifikat that are not in the number it claims to explain, with no total on the panel to reveal the mismatch. Measured on prod 2026-08-28: 322 posted/reversed entries carrying 26xx lines across 214 companies sit in those excluded classes. A momsdeklaration is räkenskapsinformation (BFL 5 kap.) and this drill-down is what substantiates a filed figure, so the two must agree exactly. The exclusion CTEs are lifted VERBATIM from the figure rather than re-derived: any divergence reintroduces exactly this bug, and an identical copy is easy to diff when the figure changes. Settlement-shape is detected against journal_entry_lines directly instead of through the figure's vat_lines CTE, which is EQUIVALENT not a shortcut (p_ruta_accounts = VAT_ACCOUNTS and p_net_accounts = ['2650','1650'] are both strict subsets of the figure's p_accounts, so restricting to vat_lines first cannot change which entries match); that keeps p_accounts meaning "the accounts of the ruta being expanded" without a fourth account parameter. opening_balance entries are deliberately NOT excluded: the figure exempts them from `shaped`, which keeps their lines IN the totals, so dropping them here would break the equality in the other direction (pinned by its own test). VAT_ACCOUNTS is now exported from lib/reports/vat-declaration.ts so the route detects shape from the same list the figure uses; a second copy is what let the two disagree. DROP + CREATE OR REPLACE, not CREATE OR REPLACE alone: the signature gains p_ruta_accounts/p_net_accounts and adding parameters registers a second overload PostgREST cannot choose between (trap documented in 20260421140000); OR REPLACE on the new arity keeps the file re-runnable. Verified the new pg test actually catches the bug by reinstalling the old body and watching 3 of 4 tests fail with the real misreporting (2611: drill-down 250/240 vs figure 0/200), then restoring.
|
||||
[2026-08-28] Bankavstamning NULL-link fix scoped to transfer legs with contradicting sign (20260828220000): the naive rule (NULL counts only for the primary account) and the formula-only variant (drop far-leg-settled vouchers from unexplained) were both simulated against prod and rejected; the naive rule worsened 4 of 11 affected cards (worst -37 000 kr false alarm on single-leg vouchers with no user action available), the formula variant blew up healthy cards by up to 474 550 kr. The shipped three-condition rule changes 24 vouchers on 7 cards in 6 companies, all verified per-card.
|
||||
[2026-08-29] Document inbox upload over the hosted body limit goes direct-to-storage through a signed URL, threshold-only (#1551): files that fit a 4.5 MB function body keep the multipart /upload route, files between HOSTED_MAX_UPLOAD_BYTES and the inbox's 10 MB cap take /upload/create -> browser PUT -> /upload/complete. Two paths to keep correct, but the multipart path carries every other channel's semantics (rate limit, dedupe, staged extraction) and the split is at uploadDocument(): everything after it is one shared processArchivedDocument(), so the two paths cannot drift in what they file. The browser PUTs to the RAW Supabase Storage URL, never the /api/storage same-origin proxy the MCP tools get: that proxy buffers the body inside a function and sits under the exact ceiling this exists to get around. The cap stays 10 MB (MAX_FILE_SIZE, MAX_DOCUMENT_SIZE) not the issue's 20 MB: raising it is a founder call that touches the archive contract and the MCP tools. Content dedupe on completePendingDocumentUpload is opt-in (the inbox passes it; the MCP tools do not) because the MCP tools key idempotency on document id === upload id and a dedupe hit returns a different id. No cron sweeps abandoned pending objects: cleanupExpiredPendingDocumentUploads runs lazily per company+user prefix on the next create/complete, same as the MCP path, and an abandoned reservation never has a document row.
|
||||
[2026-08-29] SMTP From builder (extensions/general/email/lib/smtp-service.ts) follows the 2026-08-05 no-"via <platform>" rule and honors fromAddress exactly like resend-service.ts (name alone as display name; a verified brand address rides fromAddress; the retry-as-platform-sender path drops both the company sender and the brand address so it always differs): the PR predated #1956, and a self-host must show a customer the same sender shape whichever provider the operator picked. nodemailer transport gets requireTLS by default (SMTP_SECURE=false path) with an explicit SMTP_REQUIRE_TLS=false opt-out: nodemailer's own STARTTLS is opportunistic, so a STARTTLS-stripping on-path attacker would otherwise receive AUTH credentials and every invoice PDF in cleartext; the opt-out exists only for the documented plaintext relay on a trusted Docker/LAN network.
|
||||
[2026-08-28] Company invite accept link returned in-band to the inviter (always, not only under NODE_ENV=development) rather than printed to the server log as #1710 suggested: tokens are SHA-256 hashed at rest (lib/auth/invite-tokens.ts), so the raw link exists exactly once, in the create response, and the inviter is the person who needs it; a log line would put a bearer credential in log storage and still require shell access to the container. Same contract POST /api/team/invite already ships (email_sent + inviteUrl, TeamPanel shareInvite). Acceptance stays email-bound (app/api/team/accept/route.ts requires the signed-in user's email to match the invitation, per the 2026-07-24 line above: invite recovery stays token/cookie based with mailbox possession), so sharing the link over another channel does not widen who can claim the membership. No re-send endpoint added for company invites (revoke + re-invite gives a fresh link; a re-send would be a separate surface). SMTP as a delivery path is deferred to #1746.
|
||||
[2026-08-30] Company invite toast title branches on whether mail actually went out ("Inbjudan skapad" when no provider is configured or the send failed, "Inbjudan skickad" otherwise) instead of the inherited TeamPanel wording that says sent next to a not-sent description; the share-link live region in CompanyMembersSection is always mounted (empty until a link exists) so screen readers announce the line when it appears, same reason as the roster block. TeamPanel keeps its copied wording and conditionally mounted region on purpose: the diff stays scoped to the company path (#1710), parity is a small follow-up.
|
||||
|
||||
Reference in New Issue
Block a user