Bug/year end numbers (#744)
* fix(bookkeeping): allow creating a fiscal year that fills an interior gap Fiscal-period creation only allowed chaining a new räkenskapsår before the earliest or after the latest existing period, so a company with a gap between years (e.g. 2024 + 2026 from an SIE import, missing 2025) could not create the missing year — it failed with "New period must chain before the earliest or after the latest existing period". Generalise forward chaining onto the new period's immediate predecessor, which covers both appending a new latest year and filling an interior gap. The "prior year must be locked" guard now applies only to true appends, not gap fills (a backfill, like backward chaining). previous_period_id is set to the predecessor and the successor is relinked so the BFNAR 2013:2 continuity chain stays intact. The create dialog suggests the missing year (capped so it never overlaps the next period), the settings page seeds the dialog at the earliest gap, and the default suggested name is now "Räkenskapsår <year>". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(bookkeeping): omföra föregående års resultat (2099 → 2098) at year-end Year-end closing posts the result to 2099 "Årets resultat" and the opening balance carried it forward on 2099 every year, so 2099 accumulated across years and the prior result never moved off "Årets resultat". executeYearEndClosing now posts a separate "Omföring av föregående års resultat" verifikat (Dr 2099 / Cr 2098 for a profit, reversed for a loss) into the new period after the continuity check passes, so 2099 starts each year at zero. Kept as a standalone entry rather than folded into the opening balance so the IB stays a faithful mirror of the prior UB and IB/UB continuity still holds. Aktiebolag only; idempotent; no-op when 2099 is flat. The 2098 → 2091/2898 disposition (bolagsstämma decision) is intentionally left to a separate step. - new source_type 'result_appropriation' (migration + type + Zod enum) - generateResultAppropriation helper (planner + poster) wired as step 11 - ResultStep surfaces the omföring voucher - unit tests + pg-real invariant - scripts/repair-result-appropriation.ts: retroactive catch-up (dry-run default) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(transactions): shadow-detect date-drift duplicate bank transactions The content-dedup bridge buckets on exact (date, ore), so the same transaction re-imported with a booking date that drifted a day lands in a different bucket and slips past every dedup layer. Add a measure-only ("shadow") detector that flags would-be +/-1-day duplicates and counts them, without changing what is inserted - so the gap can be validated on real data before any enforcement, mirroring the scope-drift shadow. - shiftIsoDate(): pure, deterministic adjacent-date helper - ingest: DEDUP_DATE_DRIFT_MODE flag (default on), pre-loop bucket snapshot, per-row gate with desc-bridge + cross-channel-symmetry signals; logs shadow_date_drift_candidates, never alters inserts - fail-safe date guard so the measurement can never abort an import - regression tests for both signals, account/window/distinct guards, no-double-count, and the malformed-date fail-safe Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(bookkeeping): anonymize a customer reference in fiscal-period tests Remove a real customer name ("AXMD AB") from regression-test comments; no logic change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(workflows): enhance Docker image scanning and caching mechanisms * fix(bookkeeping): enhance year-end result appropriation handling and error reporting --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
name: Scheduled Image Vulnerability Scan
|
||||
|
||||
# The build pipeline (docker-publish.yml) builds and publishes the image but
|
||||
# does NOT fail on CVEs — it stays green so deploys are deterministic. This
|
||||
# workflow is the actual vulnerability gate: it re-scans the published `latest`
|
||||
# image and fails (notifying repo admins) plus raises a Security-tab alert on a
|
||||
# fixable CRITICAL/HIGH CVE, prompting a dependency or base-image bump.
|
||||
#
|
||||
# It fires on three triggers:
|
||||
# 1. workflow_run — the moment "Build and Push Docker Image" completes, so a
|
||||
# freshly published image is gated within the scan's own duration (minutes)
|
||||
# rather than waiting up to 24h for the cron. This is what shrinks the
|
||||
# vulnerable-image exposure window after every publish.
|
||||
# 2. schedule (daily) — catches CVEs newly disclosed against an already-
|
||||
# published image even when nothing was republished.
|
||||
# 3. workflow_dispatch — run on demand from the Actions tab after a patch to
|
||||
# confirm clean.
|
||||
#
|
||||
# NOTE: GitHub only runs `schedule` and `workflow_run` triggers from the default
|
||||
# branch, so both start firing once this is merged to main.
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['Build and Push Docker Image']
|
||||
types: [completed]
|
||||
schedule:
|
||||
# 06:17 UTC daily — off the hour to dodge cron congestion on GitHub.
|
||||
- cron: '17 6 * * *'
|
||||
workflow_dispatch: {}
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: erp-mafia/gnubok
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
runs-on: ubuntu-latest
|
||||
# workflow_run fires even when the publish FAILED — there's no new image to
|
||||
# gate in that case, so skip. schedule/workflow_dispatch carry no
|
||||
# workflow_run payload, so the `!= 'workflow_run'` arm lets them through.
|
||||
if: >-
|
||||
github.event_name != 'workflow_run' ||
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
permissions:
|
||||
contents: read
|
||||
# Pull the published image from GHCR.
|
||||
packages: read
|
||||
# SARIF upload to the repo's "Security" tab.
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Scan published image with Trivy
|
||||
uses: aquasecurity/trivy-action@v0.36.0
|
||||
with:
|
||||
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
# Block on fixable CRITICAL/HIGH — the same policy the build pipeline
|
||||
# used to enforce inline. It is safe to block here: a red scheduled
|
||||
# run is a "go patch" notification, not a blocked deploy. ignore-unfixed
|
||||
# keeps it actionable (only CVEs we can resolve by rebuilding fail).
|
||||
severity: CRITICAL,HIGH
|
||||
exit-code: '1'
|
||||
ignore-unfixed: true
|
||||
format: sarif
|
||||
output: trivy-results.sarif
|
||||
|
||||
- name: Upload Trivy results to GitHub Security tab
|
||||
# if: always() so findings still reach the Security tab even though the
|
||||
# scan step above failed the run. Same category as docker-publish.yml so
|
||||
# the two analyses share one alert set instead of duplicating.
|
||||
if: always()
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: trivy-results.sarif
|
||||
category: trivy
|
||||
@@ -81,26 +81,39 @@ jobs:
|
||||
run: |
|
||||
cosign sign --yes "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${DIGEST}"
|
||||
|
||||
- name: Scan image with Trivy
|
||||
- name: Scan image with Trivy (report-only)
|
||||
id: trivy
|
||||
# Decoupled from the publish gate on purpose: this pipeline must stay
|
||||
# green so builds are deterministic. The image is already pushed and
|
||||
# signed above, so failing here would only redden the run — it would not
|
||||
# unship a vulnerable image. exit-code:0 + continue-on-error keep CVEs
|
||||
# (and even a Trivy/DB outage) from failing the build; findings still
|
||||
# flow to the Security tab below. The real blocking gate is
|
||||
# docker-image-scan.yml, which re-scans the published image and fails
|
||||
# (notifying admins) on a fixable CRITICAL/HIGH CVE. It runs on a
|
||||
# workflow_run trigger the moment THIS workflow completes — so the gap
|
||||
# between publish and the blocking scan is the scan's own duration
|
||||
# (minutes), not a 24h cron window — plus a daily cron as a safety net.
|
||||
# Accepted residual risk: an image is live for that short scan window
|
||||
# before the gate fires; see SELF-HOSTING.md / the risk register.
|
||||
continue-on-error: true
|
||||
uses: aquasecurity/trivy-action@v0.36.0
|
||||
with:
|
||||
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
|
||||
# HIGH is blocking for an accounting application — Trivy's CRITICAL
|
||||
# bucket is narrow (mostly RCE-class), and HIGH covers everything
|
||||
# from auth bypass to crypto downgrades. ignore-unfixed keeps the
|
||||
# gate actionable: only CVEs we can patch by rebuilding fail the
|
||||
# pipeline, not upstream-pending issues we have no remediation for.
|
||||
severity: CRITICAL,HIGH
|
||||
exit-code: '1'
|
||||
exit-code: '0'
|
||||
ignore-unfixed: true
|
||||
format: sarif
|
||||
output: trivy-results.sarif
|
||||
|
||||
- name: Upload Trivy results to GitHub Security tab
|
||||
# if:always() so a CRITICAL finding still ends up in the Security tab
|
||||
# even though the Trivy step above failed the workflow.
|
||||
# if: always() — evidence must reach the Security tab regardless of the
|
||||
# scan step's exit status. With the previous `outcome == 'success'` guard,
|
||||
# a Trivy/DB outage that errored the scan would silently drop findings.
|
||||
# Kept non-fatal (continue-on-error) so a missing SARIF or a Security-tab
|
||||
# hiccup can't redden an otherwise-good publish.
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: trivy-results.sarif
|
||||
|
||||
Reference in New Issue
Block a user