Skip to content

Releases

Porto v1.0.0

Choose a tag to compare

@github-actions github-actions released this 11 Nov 00:40
7206291

Added

Public release preparation (ADR 0018, docs/release.md):

  • Licence decision recorded in ADR 0018 (Business Source License 1.1, Additional Use Grant for Marina Facilities,
    AGPL-3.0 or later three years after each release, brand excluded); CONTRIBUTING.md rewritten for outside
    contributions (no CLA or DCO required for now, open decision D15); SECURITY.md (security@coste360.it,
    supported versions).
  • Release workflow .github/workflows/release.yml: on a vX.Y.Z tag it checks the tag and the changelog section,
    publishes the multi-arch image ghcr.io/coste360/porto (SBOM, provenance, BUSL-1.1 label) and the GitHub
    Release with the notes and api/openapi.yaml; only GITHUB_TOKEN. Procedure in docs/release.md.
  • Deployment templates: deploy/coolify (SQLite and PostgreSQL compose), deploy/caprover (captain-definition,
    one-click app) and deploy/demo (public demo instance with a nightly reset script).
  • Desktop: the version check reads the shared Coste360 updates service
    (https://updates.coste360.it/porto/latest.json, its manifest format with the extra top-level url; unknown
    fields ignored, release_notes as fallback link) with the User-Agent porto/<version> (desktop; <goos>/<goarch>)
    (m6a-plan D16 closed).
  • Packaging: macOS notarisation waits at most MACOS_NOTARY_TIMEOUT (default 45m) and fails with the submission id
    when still pending, or with Apple's log when rejected; the Windows uninstaller and installer are signed by Inno
    Setup with the same WINDOWS_SIGN_CMD hook (scripts/desktop/inno-sign.sh); the Linux AppImage falls back to the
    browser build whenever the window build cannot start (missing WebKitGTK 4.1, older glibc), checked in CI.
  • Demo: porto seed demo --reset (only with PORTO_DEMO_INSTANCE=true, refused unless the database holds only the
    demo organization, recognised by the demo-organization marker in the data directory so that visitors renaming
    it or adding marinas do not block the reset) wipes the database and the stored files and seeds again, used by
    deploy/demo/reset.sh for the nightly reset of a public demo (docs/demo.md). The seed now prints the counts of yard, lifting, rental,
    online booking and sensor data and the seed time, and adds online bookings in every state, the seasonal renewal
    history (dashboard "Tasso di rinnovo"), a fuller yard (quotes in every state, work orders across the board with
    hours, materials and external costs, planner assignments, lifting schedule), rental days with handovers, and
    one open sensor alert of every kind.
  • UI polish before the first release: every navigation item has an icon (collapsed sidebar shows icons and the
    avatar only); the rental planner day header shows month labels only where they fit, hour bookings are drawn
    above the margins with their code fitted to the bar, half-day columns show the band label; the berth calendar
    stretches the 14- and 31-night views to the width; the shed planner accessibility lens colours every boat and
    shows a key; the operational map and the phone map fill the window height.

M12 "Sensori e scheda pubblica" (spec 24, 25, 9.2, 10.6, 12.5, 23, 27, 30, m12-plan, ADR 0017):

  • Sensor devices (berth, segment [from_cm, to_cm) of a linear area, or pedestal socket) with model, firmware, map
    position, battery, signal and last contact; gateways and polled sources (ext.SensorSource, fake source in dev,
    demo and tests only); sensor settings per marina (harbor.configure, If-Match).
  • Device and gateway keys (po_sns_…, po_gw_…) from crypto/rand, shown once, stored as a SHA-256
    hash plus an encrypted HMAC verifier, rotation with a grace period, revoke; a restore under another
    PORTO_SECRET_KEY needs re-keying (INV-105).
  • Ingestion POST /api/ingest/v1/readings: batches up to 500 readings / 256 KiB signed with Porto-Key and
    Porto-Signature (HMAC-SHA256, ±300 s) or a gateway bearer key over HTTPS; the marina comes from the key, never
    the payload (INV-102); idempotent on (device_id, observed_at) (INV-101); per-item results; rate limits per
    device key, gateway key and IP prefix. Protocol for vendors in docs/sensor-protocol.md.
  • Pure hysteresis (occupied/free/unknown after N agreeing readings or T minutes, default 2 or 5 min), silence to
    unknown, reset on move; physical state changes in sensor_state_changes with sensor.state_changed in the
    same transaction. Raw readings kept PORTO_SENSOR_RAW_RETENTION (default 30 days) and excluded from backup.
  • Reconciliation (pure rules R1–R8, job sensors.reconcile woken by ingestion): unexpected occupied, transit
    arrival proposal, annual vessel away (automatic presence out only when the marina enables it), release proposal
    where release_policy = with_consent, return detected, early return over a resold berth, silent or low-battery
    sensor proposing a maintenance task. Alerts are proposals: contracts and occupancy change only on an operator
    confirmation through the existing services (INV-100, INV-104).
  • Sensori map context (lens, legend, markers, quick card) updated live by SSE sensor.changed without personal
    data; alerts inbox and the dock "Avvisi" tab on the phone; per-berth and per-device physical history (physical
    night = majority of 22:00–06:00); report sensors (discrepancies) with CSV/Excel export; day-view anomaly
    sensor_discrepancy.
  • Marina profile edited in the panel (position WGS84, contacts, VHF, hours, features, depths, services, photos
    re-encoded without metadata, it/en) and published on /p/{slug} next to the booking page: simplified map without
    codes or occupancy, transit price matrix of the list flagged publish_matrix, aggregate free berths per category
    when enabled (never which berths or vessels), booking button, JSON-LD Marina/Place, canonical and hreflang;
    public APIs cacheable, rate limited and cookie-free (INV-103). Nothing is sent to external directories.
  • API: tags sensors, sensors-ingest, profile, profile-public; events sensor.state_changed,
    sensor.anomaly, sensor.changed, profile.changed; jobs sensors.poll, sensors.silence,
    sensors.retention, sensors.reconcile. Migrations 00026_sensors, 00027_marina_profile (SQLite and
    PostgreSQL).
  • Demo seed: a gateway, the fake source, 40 berth devices on the largest pier, 2 segment devices and 1 pedestal
    device with a back-dated history and 6 h of readings, an alert of every kind, one automatic presence, one silent
    device and one at 12 % battery; a published demo profile with photos, POIs, aggregate occupancy and the transit
    price matrix (the demo price list now sets publish_matrix).
  • Load acceptance: 1,000 simulated sensors with one reading per minute each on SQLite stay well within 2 s from
    accepted reading to SSE and to a fresh operational view (TestAcceptance_M12_SensorLoad).
  • Not in M12: a reference hardware partner (open decision), MQTT/LoRaWAN code in the core, automatic release
    without confirmation.

M11 "Noleggio" (spec 19, 20, 21, 29.2, m11-plan, ADR 0016):

  • Rental fleet: units linked to registry vessels (category, seats, licence requirement never/always/threshold on
    engine kW, fuel, deposit, status, home berth, own or third-party owner), bookable skippers with weekly hours,
    days off and licence, formulas (hourly, half day on configurable bands, day, multi-day, week; with or without
    skipper; fuel included or metered), extras and marina rental settings (open window, bands, buffer, hold TTL,
    checklist, grace and late-fee step).
  • Pricing through the pure engine (pricing.RentalQuote, EngineVersion unchanged): rental items of the price
    lists with time units hour, half_day, day, week, scope time_bands, rate periods per priced date,
    customer category and channel; skipper line and extras per rental or per day; stateless quote listing every
    valid formula; reproducible snapshot on the booking (INV-20, RentalEngineVersion).
  • Bookings (NL<year>-NNNNNN) on 30-minute slots: no double booking of a unit or a skipper enforced by the primary
    keys of rental_slots and rental_skipper_slots, with a configurable buffer row after each rental (INV-18,
    INV-19, ADR 0003); holds ("Opzione") with lazy reclaim of expired holds at conflict time and the
    rental.holds_expire job (INV-05); blocks; states held → confirmed → checked_out → returned → closed, plus
    cancelled and no_show, only through rental/reservation with audit and outbox (INV-97).
  • Planner unit × time (hours, half days, days) on a shared SVG grid: drag to create and to move, colours by state,
    409 highlight on overlap; bookings list and booking page with the handover panel.
  • Check-out and check-in from the phone (Today, New walk-in booking from now, Scan, Till): renter identity and licence typed by the operator and
    encrypted, document photos, rental contract signed on screen (new screen signature driver; OTP and paper
    unchanged), deposit in cash, card POS, pre-authorisation through the payment driver or waived, checklist, fuel,
    engine hours and photos (offline kind rental.checklist); on return the actual time with the late fee of the
    formula, metered fuel, extras, damages with photos and estimate, deposit released or retained fully or partially.
    A deposit is never revenue until retained (INV-98); one main and one return charge per booking (INV-99).
  • Quick cash in one step (cash, card POS, payment link) with the commercial document through FiscalDriver;
    invoice-requested charges go to the export. The payment link and the deposit pre-authorisation page are shown as
    a QR for the renter's own phone; the payment link needs a registered customer (disabled for walk-ins).
  • An unexported, unpaid main charge is cancelled and re-created (link replaced) on a reprice, a change of
    customer or of invoice_requested, and cancelled with the booking; exported or paid charges get linked
    adjustments, and ...
Read more