Compare commits

...
15 changed files with 65 additions and 37 deletions
+4 -3
View File
@@ -54,9 +54,10 @@ jobs:
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: Check Java formatting (Spotless)
# Runs once per matrix combination - pick the cheapest leg
# (core - no proprietary, no saas) so we don't wait for the
# heavier flavors just to fail formatting.
# Runs on the cheapest leg so we don't wait for the heavier flavors just
# to fail formatting. Coverage does not depend on the leg: the task pins
# STIRLING_FLAVOR itself so spotless always sees every subproject,
# :saas included.
if: matrix.jdk-version == 25 && matrix.flavor == 'core'
id: spotless-check
run: task backend:format:check
+5
View File
@@ -35,6 +35,11 @@ jobs:
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
# The .ts/.tsx half of comment-lint runs as an oxlint plugin. The task no
# longer installs it for us (that stalled the local git hook), so do it here.
- name: Install frontend dependencies
run: task frontend:install
- name: Run pre-commit checks
run: task pre-commit
+13
View File
@@ -15,6 +15,15 @@ version: '3'
# stripping; cmd.exe also requires `.\` (not bare `gradlew.bat`)
# because modern Windows excludes cwd from cmd's search path.
vars:
# settings.gradle only puts :saas in the project graph when the saas flavor is
# selected, so a default `spotlessApply` silently leaves app/saas unformatted
# and nothing ever notices. Pin the flavor for the format tasks so they cover
# every subproject the checkout actually has. Empty on clones without
# app/saas (settings.gradle treats an empty value as "unset").
SPOTLESS_FLAVOR:
sh: test -d app/saas && echo saas || true
tasks:
dev:
desc: "Start backend dev server"
@@ -227,6 +236,8 @@ tasks:
format:
desc: "Auto-fix code formatting"
env:
STIRLING_FLAVOR: '{{.SPOTLESS_FLAVOR}}'
cmds:
- cmd: cmd /c ".\gradlew.bat spotlessApply"
platforms: [windows]
@@ -235,6 +246,8 @@ tasks:
format:check:
desc: "Check code formatting"
env:
STIRLING_FLAVOR: '{{.SPOTLESS_FLAVOR}}'
cmds:
- cmd: cmd /c ".\gradlew.bat spotlessCheck"
platforms: [windows]
+4 -4
View File
@@ -152,10 +152,10 @@ tasks:
Full tree (report only): task pre-commit:comment-lint:all
Fixture corpus: task pre-commit:comment-lint:selftest
# Depends on the frontend install because the .ts/.tsx half of the rule set
# runs as an oxlint plugin. Without it the TS engine warns and skips, which
# would leave the frontend silently unchecked on CI.
deps: [":frontend:install"]
# No :frontend:install dep: this runs from the git hook, and a from-scratch
# npm install there stalls every commit. Missing oxlint warns and skips the
# TS half; the callers that must not skip (:ci, :branch, :all) keep the dep,
# and pre_commit.yml installs the frontend before it runs this.
cmds:
- node scripts/lint/comment-lint.mjs {{.CLI_ARGS}}
@@ -26,9 +26,6 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -53,6 +50,9 @@ import stirling.software.saas.payg.model.JobSource;
import stirling.software.saas.payg.model.ProcessType;
import stirling.software.saas.util.AuthenticationUtils;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectMapper;
@RestController
@Profile("saas")
@RequestMapping("/api/v1/ai/create")
@@ -14,9 +14,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -29,6 +26,9 @@ import stirling.software.saas.ai.service.AiCreateSessionService;
import stirling.software.saas.payg.cap.RequiresFeature;
import stirling.software.saas.payg.model.FeatureGate;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectMapper;
@RestController
@Profile("saas")
@RequestMapping("/api/v1/ai/create/internal")
@@ -12,12 +12,12 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import tools.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import stirling.software.saas.config.SupabaseConfigurationProperties;
import tools.jackson.databind.ObjectMapper;
/**
* Reports per-tenant overage to Stripe Billing Meters via the Supabase {@code meter-usage} Edge
* Function. Only credits consumed above the free tier flow through {@link #reportUsageToStripe}.
@@ -13,13 +13,13 @@ import java.util.regex.Pattern;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
/**
* Loads the versioned legal-document registry from {@code legal/manifest.json} on startup and
* serves document metadata + rendered markdown from the classpath.
@@ -63,8 +63,7 @@ public class LegalDocumentRegistry {
d.path("parts"),
objectMapper
.getTypeFactory()
.constructCollectionType(
List.class, String.class));
.constructCollectionType(List.class, String.class));
documents.put(
id,
new LegalDocumentMeta(
@@ -20,8 +20,6 @@ import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import tools.jackson.databind.ObjectMapper;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
@@ -40,6 +38,8 @@ import stirling.software.saas.payg.cap.RequiresFeature;
import stirling.software.saas.payg.model.FeatureGate;
import stirling.software.saas.util.AuthenticationUtils;
import tools.jackson.databind.ObjectMapper;
/**
* Hot-path entitlement check. Runs after {@code PaygChargeInterceptor} in the MVC chain and short-
* circuits the request before any handler work happens when the team's snapshot is missing one of
@@ -18,8 +18,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import tools.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.servlet.http.HttpServletRequest;
@@ -43,6 +41,8 @@ import stirling.software.saas.procurement.pricing.QuoteLineItem;
import stirling.software.saas.procurement.service.ProcurementService;
import stirling.software.saas.util.AuthenticationUtils;
import tools.jackson.databind.ObjectMapper;
/**
* The enterprise procurement journey for a linked team: read the deal snapshot, start/extend a
* (mock-licensed) trial, build a server-priced quote, and accept it. Stripe checkout itself is a
@@ -10,8 +10,6 @@ import java.util.Map;
import org.springframework.stereotype.Service;
import tools.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -22,6 +20,8 @@ import stirling.software.saas.procurement.pricing.ProcurementPricingService;
import stirling.software.saas.procurement.pricing.QuoteConfig;
import stirling.software.saas.procurement.pricing.QuoteLineItem;
import tools.jackson.databind.ObjectMapper;
/**
* Builds the full Stirling Enterprise Agreement for a specific quote: the static MSA (Part A) and
* DPA (Part C) from the {@link LegalDocumentRegistry}, with the dynamic Order Form (Part B)
@@ -16,13 +16,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import stirling.software.saas.procurement.config.KeygenConfigurationProperties;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
/**
* Real {@link EnterpriseLicenseService}: manages the team's enterprise licence directly against the
* Keygen API (the "call Keygen from Java" direction), rather than via the Supabase edge functions
@@ -10,9 +10,6 @@ import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.enumeration.TeamRole;
@@ -38,6 +35,9 @@ import stirling.software.saas.procurement.repository.ProcurementQuoteRepository;
import stirling.software.saas.service.SaasTeamService;
import stirling.software.saas.util.LogRedactionUtils;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectMapper;
/**
* Orchestrates a linked team's procurement journey: start a (mock-licensed) trial, build a
* server-priced quote, and accept it. Stripe checkout itself lives in a Supabase edge function the
@@ -28,9 +28,6 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.method.HandlerMethod;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
@@ -45,6 +42,9 @@ import stirling.software.saas.payg.model.FeatureGate;
import stirling.software.saas.payg.model.FeatureSet;
import stirling.software.saas.security.EnhancedJwtAuthenticationToken;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
/**
* Pure-Mockito tests for {@link EntitlementGuard}. Covers the four decision-matrix cells: anonymous
* billable → 401, anonymous manual → pass, authenticated FULL → pass, authenticated DEGRADED for a
+12 -2
View File
@@ -64,9 +64,17 @@ if (result.status !== FOUND) {
process.exit(1);
}
// A report with nothing in it is the launcher failing, not the linter: a missing
// `task` also exits 1, and blocking on that reads as "your comments are bad".
const report = result.output.trim();
if (!report) {
process.stderr.write("comment-lint exited 1 with no report, so comments in this turn were not checked.\n");
process.exit(1);
}
// The linter's own report already names the file, line, rule and the standard, so
// it is passed through rather than rewritten.
process.stderr.write(`${result.output.trim()}\n\nFix these before finishing.\n`);
process.stderr.write(`${report}\n\nFix these before finishing.\n`);
process.exit(2);
function readStdin() {
@@ -92,7 +100,9 @@ function taskCommand() {
for (const candidate of candidates) {
if (existsSync(candidate)) return { command: candidate, shell: false };
}
return { command: process.platform === "win32" ? "task.cmd" : "task", shell: process.platform === "win32" };
// Bare `task` on win32, not `task.cmd`: cmd.exe resolves it through PATHEXT, so
// it finds scoop's task.exe as well as the npm package's task.cmd.
return { command: "task", shell: process.platform === "win32" };
}
function run() {