Compare commits

...

4 Commits

Author SHA1 Message Date
cesnimda 5addcde1e2 fix(companies): reuse exact Norwegian company names 2026-10-20 09:16:11 +02:00
cesnimda f9bf31c9a9 test(e2e): reconcile route census with approved migrations
CI and Deploy / test (push) Failing after 2h12m32s
CI and Deploy / deploy (push) Has been skipped
2026-10-20 09:02:20 +02:00
cesnimda 32bc65c60b fix(ci): expose browser progress and retain failure diagnostics
CI and Deploy / test (push) Failing after 1h12m6s
CI and Deploy / deploy (push) Has been skipped
2026-10-20 08:32:41 +02:00
cesnimda 2278473763 fix(deploy): patch audited frontend dependencies
CI and Deploy / test (push) Failing after 25m4s
CI and Deploy / deploy (push) Has been skipped
2026-10-17 21:02:45 +02:00
16 changed files with 716 additions and 190 deletions
+15 -3
View File
@@ -191,11 +191,13 @@ jobs:
&& npm_cache="$(mktemp -d)" \
&& npm ci --no-audit --no-fund --cache "$npm_cache" )
# Moderate React Router advisories remain documented and mitigated; high/critical findings
# in either production or test/browser tooling block the build.
# High/critical findings remain blocking except the owner's expiring dev-only
# GHSA-vfj7-8cjw-p6xm exception; raw audit results are retained and the policy is tested.
- name: Audit frontend dependencies
working-directory: job-tracker-ui
run: npm audit --audit-level=high
run: |
node --test scripts/audit-dependencies.test.mjs
node scripts/audit-dependencies.mjs
- name: Lint frontend
working-directory: job-tracker-ui
@@ -327,6 +329,16 @@ jobs:
echo "Browser smoke process crashed with SIGSEGV — retrying the unchanged suite once..."
npm run test:e2e
- name: Preserve browser diagnostics
if: always()
uses: actions/upload-artifact@v3
with:
name: browser-diagnostics
path: |
job-tracker-ui/.artifacts/playwright-results.json
job-tracker-ui/test-results
retention-days: 7
deploy:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
@@ -0,0 +1,112 @@
using System.Security.Claims;
using JobTrackerApi.Controllers;
using JobTrackerApi.Data;
using JobTrackerApi.Models;
using JobTrackerApi.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Moq;
using Xunit;
namespace JobTrackerApi.Tests;
public sealed class CompanyCreateCompatibilityTests
{
[Fact]
public async Task Exact_Norwegian_name_reuses_owned_company_without_overwriting_existing_fields()
{
await using var fixture = await Fixture.CreateAsync();
var existing = new Company { OwnerUserId = "owner-a", Name = "Ærlig Økonomi Ås", Location = "Original location", Source = "Finn", RecruiterEmail = "retained@example.test" };
fixture.Db.Companies.Add(existing);
await fixture.Db.SaveChangesAsync();
var result = await fixture.Controller.Create(new(" Ærlig Økonomi Ås ", "Imported location", "LinkedIn"), default);
var company = Assert.IsType<Company>(Assert.IsType<OkObjectResult>(result.Result).Value);
Assert.Equal(existing.Id, company.Id);
Assert.Equal("owner-a", company.OwnerUserId);
Assert.Equal("Ærlig Økonomi Ås", company.Name);
Assert.Equal("Original location", company.Location);
Assert.Equal("Finn", company.Source);
Assert.Equal("retained@example.test", company.RecruiterEmail);
Assert.Equal(1, await fixture.Db.Companies.IgnoreQueryFilters().CountAsync());
}
[Fact]
public async Task Ascii_case_insensitive_reuse_and_trimmed_input_remain_compatible()
{
await using var fixture = await Fixture.CreateAsync();
var existing = new Company { OwnerUserId = "owner-a", Name = "Northwind AS", Location = "Original location", Source = "Finn" };
fixture.Db.Companies.Add(existing);
await fixture.Db.SaveChangesAsync();
var result = await fixture.Controller.Create(new(" nORTHWIND aS ", "Another location", "LinkedIn"), default);
var company = Assert.IsType<Company>(Assert.IsType<OkObjectResult>(result.Result).Value);
Assert.Equal(existing.Id, company.Id);
Assert.Equal("Northwind AS", company.Name);
Assert.Equal("Original location", company.Location);
Assert.Equal("Finn", company.Source);
Assert.Equal(1, await fixture.Db.Companies.CountAsync());
}
[Fact]
public async Task An_identical_foreign_name_creates_a_separate_owned_company_and_preserves_source_normalization()
{
await using var fixture = await Fixture.CreateAsync();
var foreign = new Company { OwnerUserId = "owner-b", Name = "Ærlig AS", Location = "Private location", Source = "Private source", RecruiterEmail = "private@example.test" };
fixture.Db.Companies.Add(foreign);
await fixture.Db.SaveChangesAsync();
var result = await fixture.Controller.Create(new(" Ærlig AS ", " Bergen ", "https://www.nav.no/jobs"), default);
var company = Assert.IsType<Company>(Assert.IsType<CreatedAtActionResult>(result.Result).Value);
Assert.NotEqual(foreign.Id, company.Id);
Assert.Equal("owner-a", company.OwnerUserId);
Assert.Equal("Ærlig AS", company.Name);
Assert.Equal("Bergen", company.Location);
Assert.Equal("NAV", company.Source);
Assert.Null(company.RecruiterEmail);
Assert.Equal(1, await fixture.Db.Companies.CountAsync());
Assert.Equal(2, await fixture.Db.Companies.IgnoreQueryFilters().CountAsync());
var retained = await fixture.Db.Companies.IgnoreQueryFilters().AsNoTracking().SingleAsync(value => value.Id == foreign.Id);
Assert.Equal("owner-b", retained.OwnerUserId);
Assert.Equal("Private location", retained.Location);
Assert.Equal("Private source", retained.Source);
Assert.Equal("private@example.test", retained.RecruiterEmail);
}
private sealed class Fixture(SqliteConnection connection, JobTrackerContext db) : IAsyncDisposable
{
public JobTrackerContext Db { get; } = db;
public CompaniesController Controller { get; } = new(db)
{
ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext
{
User = new ClaimsPrincipal(new ClaimsIdentity([new Claim(ClaimTypes.NameIdentifier, "owner-a")], "test")),
},
},
};
public static async Task<Fixture> CreateAsync()
{
var connection = new SqliteConnection("Data Source=:memory:");
await connection.OpenAsync();
var currentUser = new Mock<ICurrentUserService>();
currentUser.SetupGet(value => value.UserId).Returns("owner-a");
var db = new JobTrackerContext(new DbContextOptionsBuilder<JobTrackerContext>().UseSqlite(connection).Options, currentUser.Object);
await db.Database.EnsureCreatedAsync();
return new Fixture(connection, db);
}
public async ValueTask DisposeAsync()
{
await Db.DisposeAsync();
await connection.DisposeAsync();
}
}
}
@@ -126,7 +126,7 @@ namespace JobTrackerApi.Controllers
existingQuery = existingQuery.Where(c => c.OwnerUserId == userId);
var existing = await existingQuery
.FirstOrDefaultAsync(c => c.Name.ToLower() == name.ToLower(), cancellationToken);
.FirstOrDefaultAsync(c => c.Name == name || c.Name.ToLower() == name.ToLower(), cancellationToken);
if (existing is not null)
{
+1
View File
@@ -242,6 +242,7 @@ services:
- "11434"
environment:
- OLLAMA_HOST=0.0.0.0:11434
- GOMAXPROCS=1
volumes:
- ollama_data:/root/.ollama
# On ai_internal so the bundled Ollama stays reachable at http://ollama:11434 now that
@@ -0,0 +1,37 @@
# 17 October 2026 release repair
The owner authorized promotion of completed `e7dba9a` to main despite existing production NO-GO qualifications, then requested deployment first, `qwen3.5:9b-q4_K_M` on Ollama second, and continued Phase 7 work afterward. Unfinished Settings/import/company changes remain outside this release repair.
## Deployment findings
Gitea run 820 initially failed during parser dependency installation: one attempt segfaulted and subsequent downloads failed locked hashes. An unchanged-release retry failed during Git checkout with a corrupt pack. A later attempt passed these stages and the backend checks, then failed the frontend audit with 19 moderate, 9 high and 1 critical findings.
The NumPy lock hash was already correct. Independent Windows verification and three fresh downloads on the supplied runner host matched the published SHA-256 and passed ZIP CRC checks. The host had rebooted between the earlier failures and these checks; the underlying integrity incident is not diagnosed or declared repaired. Hash enforcement and all ordinary test gates remain enabled.
Server source edits were preserved privately at `/home/pi/.local/state/jobjakt/pre-release-source-20261017T185504Z`. The only content change was `GOMAXPROCS=1` for bundled Ollama, which this repair retains. The deploy script's executable bit is already restored by the deployment workflow. No database, volume or user data was reset.
## Bounded dependency changes
| Dependency | Locked before | Locked after |
|---|---|---|
| Next.js and matching ESLint/SWC packages | 16.3.4 | 16.3.8 |
| Axios | 1.19.0 | 1.20.0 |
| brace-expansion | 2.1.4 | 2.1.7 |
| sharp and its matching platform payloads | 0.35.4 | 0.35.5 |
| source-map-js | 1.2.1 | 1.2.2 |
React, MUI, routing, application data owners and PDF geometry are unchanged. The production-only audit reports zero vulnerabilities.
## Explicit temporary development exception
The owner separately approved **GHSA-vfj7-8cjw-p6xm** in dev-only `braces` through **31 October 2026 UTC**. [The upstream advisory](https://github.com/advisories/GHSA-vfj7-8cjw-p6xm) lists no patched version. Its five linked audit nodes are `braces`, `micromatch`, `fast-glob`, `@next/eslint-plugin-next` and `eslint-config-next`; this is one advisory, not five independent accepted vulnerabilities.
The audit checker accepts only that high-severity origin, the explicit existing chain, `braces` 3.0.3, and lockfile nodes marked development-only. Production copies, another advisory, critical severity, missing data/nodes, cyclic origins and expiry fail closed. Full audit JSON is saved and printed in CI. All other high/critical findings remain blocking; the existing moderate threshold is retained. The raw full audit still reports the accepted development findings; they are not described as fixed.
Nine ordinary policy tests cover the exception and its rejection boundaries. The previous strict policy was captured failing the approved exception, and a critical-origin edge case was separately reproduced and repaired. The exception must be removed when upstream supplies a fix or explicitly reviewed before expiry; it does not authorize a future blanket bypass.
## Verification and remaining work
Application TypeScript, lint, production build, backend Release compilation, production audit, policy tests and supply-chain/ingress tests pass. Settings persistence, authentication/account isolation and ordinary P2-D01 browser journeys pass (16 cases, zero skipped/flaky).
Fresh full frontend execution passed 824 tests across 95 suites, with zero skipped or pending cases (18:49-18:58 UTC). The nine separate audit-policy cases also pass. Supply-chain and ingress checks each passed three cases; whitespace and the tracked-secret scan passed. No existing test counts were reduced. Evidence is retained in the local release-20261017 artifact directory. CI and deployed runtime identity must be checked after promotion. Existing ingress, complete backup/restore, rollback, human accessibility/device and production-output qualifications remain open; deployment authorization does not convert them to verified GO. P5-13 stays withheld and no model qualification or Phase 8 work is enabled.
@@ -0,0 +1,9 @@
# Browser CI diagnostic repair — 20 October 2026
Run 821, attempt 2, tested main commit `2278473763848254c12d65c1062109eb1b7dc4cf`. Its backend build, 1,054 backend tests, 824 frontend tests, dependency policy, lint, TypeScript and production frontend build completed before the browser step. The browser step began at 20:37:52 UTC on 17 October and ended unsuccessfully at 20:51:55 UTC. The downloadable log ends at “Running 331 tests using 1 worker”, without an assertion, timeout or process-exit diagnostic. Deployment was skipped. Subsequent successful scheduled backup jobs do not constitute a successful application deployment.
The existing GitHub-only Playwright reporter does not emit ordinary per-test progress. The repair adds the built-in list reporter alongside GitHub annotations and a JSON report. An always-run artifact step retains the JSON result and existing failure screenshots/traces for seven days using Gitea-compatible upload-artifact v3. No tests, assertions, timeouts, retries, browser projects, authorization checks or deployment conditions are weakened. This is a diagnostic repair; the underlying termination cause is still unproven and requires the instrumented run.
Verification: all 331 tests in 53 files are still discovered. A separate temporary probe deliberately failed one ordinary assertion: the process returned 1, printed the failing test name, wrote a trace and persisted one unexpected failure and zero skipped cases in JSON. The probe is an isolated reporter self-check, not an application regression result or committed test. Application/test TypeScript passed. Lint and final hygiene are recorded before publication; the next main CI run remains authoritative for application regression and deployment.
Unfinished Phase 7 work in the original checkout is excluded. The owner has explicitly authorized this main deployment despite existing release qualifications. The temporary dev-only braces exception retains its original expiry after 31 October 2026; other dependency findings, outstanding production gates and P5-13 withholding remain unchanged. The requested Qwen model is downloaded but is not activated by this change.
@@ -0,0 +1,11 @@
# Route-census compatibility repair — 20 October 2026
The diagnostic browser run on `32bc65c` reproduced two ordinary failures in the normal-user and administrator route census. Both expected the legacy Gmail-review redirect to discard unrelated query parameters and its fragment. P7-06 deliberately preserves that context: `App.tsx` redirects `/correspondence/review?route=all#review` to `/correspondence?route=all&view=review#review`, and the approved P7-06 native alias case separately verifies preserved query, fragment and review mode. The application is following its accepted contract; the older census expectation was stale.
After correcting the exact destination, the same cases exposed a second stale expectation for the former server-generated English error. P7-06 presents the existing translated `gmailReviewNotConnected` message. The repaired census still requires the real HTTP 409, exact `gmail_not_connected` response code and a visible alert containing that approved translation. No permission, error or redirect assertion is removed or made optional.
The census also includes P7-07's approved `/documents` route, bringing its explicit unique-route assertion from 32 to 33 while retaining every original route. Anonymous entry must remain protected; authenticated entry and refresh must return owned document metadata and the exact Documents heading. This strengthens coverage without adding an application route or feature.
The initial local whole-suite diagnostic was interrupted after 65 completed cases (63 passed, two failed) once the route failures were reproduced; it is not a full-suite acceptance result. Its logs and both failure traces remain retained. The first corrected census recorded one pass and two failures on the obsolete English alert. The final focused run passed all three ordinary identity cases, zero retries/skips, at 06:57:22–06:59:30 UTC: 198 route entry/refresh combinations plus existing history, ownership and navigation checks. Browser TypeScript passed. The overall browser inventory remains 331 tests in 53 files; the three existing identity cases now cover 33 routes each.
The source change is confined to the census fixture. Existing product code, CV pagination, persistence, authorization and P5-13 withholding are unchanged. Lint, whitespace and tracked-secret checks pass. The instrumented Linux run remains diagnostic evidence for the previous candidate until the updated main commit receives its own full CI result. Production/operator and manual accessibility qualifications remain open; this repair does not accept the remaining Phase 7 migration.
@@ -0,0 +1,11 @@
# Company-create compatibility prerequisite
The actual JSON export/import journey exposed a backend prerequisite: importing the exact existing Norwegian company name created a second company. SQLite's default `LOWER()` handles ASCII while the bound .NET lower-case parameter folds Norwegian letters. The previous comparison therefore missed an exact existing name containing `Æ`, `Ø` or `Å`. The original failing import trace and the candidate repair evidence remain under `.artifacts/phase7/p7-09/`; the importer does not work around the server by changing company names.
The bounded repair checks exact equality alongside the existing lower-case comparison inside the same owner-filtered query. Trimming, source normalization, 200-for-reuse/201-for-create, metadata retention and account ownership are preserved. It performs no schema migration, historical deduplication or destructive write. Broader Unicode case-folding equivalence and concurrent creation idempotency remain RD21 debt.
Three real SQLite regression cases prove exact Norwegian-name reuse without overwriting existing fields, retained ASCII case-insensitive reuse, and separate owned creation when an identical foreign company exists. The last case also verifies retained foreign metadata and source normalization. Fresh Release compilation and these three cases pass. A fresh unfiltered backend run passes **1,057/1,057**, zero skipped, at **20 October 2026 07:05:21–07:05:38 UTC**. The increase from 1,054 is exactly these three ordinary cases; existing cases were not removed or weakened.
The fresh native Companies suite passes **6/6**, zero retries/skipped/flaky, at **07:10:12–07:11:05 UTC**. It exercises the existing presentation and persisted owner behavior against the rebuilt API and current production frontend export. The [portable evidence](evidence/company-create-results.json) records commands, results and source/binary identities, explicitly identifying its post-run source observation. Earlier native import/company evidence is historical evidence, not substituted for these fresh results.
Preservation references are **PRES-J08, PRES-J10 and PRES-E09**. The complete matrix stays **56 PARTIAL / C11 DEFERRED**. Actual MariaDB/provider and production qualification, manual reader/device checks, the remaining import/data/privacy/plan/backup migration and RD21 concurrency work are not accepted here. P5-13 stays withheld, followMode stays deferred and Phase 8 is not implemented. This prerequisite is published separately from the active import UI work on the development branch; production readiness remains NO-GO despite the owner's separately authorized release repair.
@@ -0,0 +1,121 @@
{
"schemaVersion": 1,
"checkpoint": "P7-09 company-create prerequisite",
"observedUtc": "2026-10-20T07:15:05.299883+00:00",
"baselineBackend": 1054,
"newBackendCases": 3,
"targeted": {
"path": ".artifacts/phase7/current/backend-career/p709-company-compatibility-fresh-20261020T070423Z/command.json",
"command": [
"rtk",
"proxy",
"C:/Users/Connor/.dotnet/dotnet.exe",
"test",
"JobTrackerApi.Tests/JobTrackerApi.Tests.csproj",
"--configuration",
"Release",
"--no-restore",
"--filter",
"FullyQualifiedName~CompanyCreateCompatibilityTests",
"--logger",
"trx;LogFileName=career.trx",
"--results-directory",
"E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\backend-career\\p709-company-compatibility-fresh-20261020T070423Z"
],
"startedUtc": "2026-10-20T07:04:23.391824+00:00",
"finishedUtc": "2026-10-20T07:04:41.335549+00:00",
"exitCode": 0,
"logSha256": "f9fe87f762d872f11e2d6a7e14448e9f0451c2794b4094f5fe34eed1cd7cd6ad",
"counts": {
"total": 3,
"passed": 3,
"failed": 0,
"notExecuted": 0
},
"resultSha256": "54e0867d3d80c438e9cdd026c1811ea8a52a7d8d9d057c847dac3a1593cf9fa9"
},
"fullBackend": {
"path": ".artifacts/phase7/current/backend-career/p709-company-full-regression-20261020T070521Z/command.json",
"command": [
"rtk",
"proxy",
"C:/Users/Connor/.dotnet/dotnet.exe",
"test",
"JobTrackerApi.Tests/JobTrackerApi.Tests.csproj",
"--configuration",
"Release",
"--no-build",
"--logger",
"trx;LogFileName=career.trx",
"--results-directory",
"E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\backend-career\\p709-company-full-regression-20261020T070521Z"
],
"startedUtc": "2026-10-20T07:05:21.416786+00:00",
"finishedUtc": "2026-10-20T07:05:38.375404+00:00",
"exitCode": 0,
"logSha256": "fa6461b4e59d07bfa2ffe1d0a59daf7ab616a4e87b9dbb80f4548cee703a3537",
"counts": {
"total": 1057,
"passed": 1057,
"failed": 0,
"notExecuted": 0
},
"resultSha256": "0c995ebf824d6a4b446af9878bdcfd53e72ee90759fead491d1bd1108be3b45e"
},
"native": {
"path": ".artifacts/phase7/current/ui/companies.json",
"command": {
"name": "companies",
"command": [
"rtk",
"proxy",
"node",
"node_modules/@playwright/test/cli.js",
"test",
"e2e/phase7-companies.spec.ts",
"--retries=0",
"--reporter=list,json",
"--output=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\ui\\companies-results"
],
"startedUtc": "2026-10-20T07:10:12.619786+00:00",
"finishedUtc": "2026-10-20T07:11:05.493360+00:00",
"exitCode": 0
},
"stats": {
"startTime": "2026-10-20T07:10:13.244Z",
"duration": 52203.75399999999,
"expected": 6,
"skipped": 0,
"unexpected": 0,
"flaky": 0
}
},
"preservation": [
"PRES-J08",
"PRES-J10",
"PRES-E09"
],
"sources": [
{
"path": "JobTrackerApi/Controllers/CompaniesController.cs",
"sha256Raw": "a913bcd1e9993bc4f8fd88b5554b92eade263967c4027b2179244354ed96f7ec",
"sha256Lf": "4db8eb5a0d434a346538003c426f9ace9fad55695b9a57c3a4bf441e5c0d37e3"
},
{
"path": "JobTrackerApi.Tests/CompanyCreateCompatibilityTests.cs",
"sha256Raw": "b15bbe1b256cae2a5712a954d2f84470d5a9acc1b734143d5879690486b5227c",
"sha256Lf": "b15bbe1b256cae2a5712a954d2f84470d5a9acc1b734143d5879690486b5227c"
}
],
"binaries": [
{
"path": "JobTrackerApi/bin/Release/net9.0/JobTrackerApi.dll",
"sha256": "addbeef095ec5a69b9dc4646f790117d18faa24ab5cb7e36663e84f4fa3f9a66"
},
{
"path": "JobTrackerApi.Tests/bin/Release/net9.0/JobTrackerApi.Tests.dll",
"sha256": "12f4003d9e0a4f98598175a72d1bc7291c1f08fc166bd2b4e1b47f844c33b013"
}
],
"qualification": "Source/binary identities observed after the fresh backend and Companies runs; earlier RED/native evidence remains separately retained. Not a pre-run capture or production/MariaDB acceptance."
}
@@ -0,0 +1,104 @@
{
"schemaVersion": 1,
"checkpoint": "p7-09",
"status": "PASS",
"checkedUtc": "2026-10-20T07:15:55.632615+00:00",
"commands": [
{
"name": "secret-scan",
"command": [
"rtk",
"proxy",
"C:\\Users\\Connor\\AppData\\Local\\Programs\\Python\\Python312\\python.exe",
"-X",
"utf8",
"scripts/supply-chain.py",
"scan-secrets"
],
"startedUtc": "2026-10-20T07:15:51.896284+00:00",
"finishedUtc": "2026-10-20T07:15:54.661570+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261020T071551Z/secret-scan.log",
"logSha256Raw": "be5119e6057287d397e87759f855c91e634e3bfac61c8be7584d05592ba2dfcd"
},
{
"name": "working-whitespace",
"command": [
"rtk",
"proxy",
"git",
"diff",
"--check"
],
"startedUtc": "2026-10-20T07:15:54.662586+00:00",
"finishedUtc": "2026-10-20T07:15:54.772491+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261020T071551Z/working-whitespace.log",
"logSha256Raw": "7d4d21b7cfbda7f0adedeec159d5457b079dd3046eab064066ed668c6d1139cd"
},
{
"name": "staged-whitespace",
"command": [
"rtk",
"proxy",
"git",
"diff",
"--cached",
"--check"
],
"startedUtc": "2026-10-20T07:15:54.772959+00:00",
"finishedUtc": "2026-10-20T07:15:54.895580+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261020T071551Z/staged-whitespace.log",
"logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "phase7-whitespace",
"command": [
"rtk",
"proxy",
"git",
"diff",
"--check",
"b5319981ad3d1bd635daff861453d23c0de5320b"
],
"startedUtc": "2026-10-20T07:15:54.896077+00:00",
"finishedUtc": "2026-10-20T07:15:55.135880+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261020T071551Z/phase7-whitespace.log",
"logSha256Raw": "c42dc788a516c9c9baadd44ae7a8c4c6d5b0e4cbff688f5b45647999e953e099"
}
],
"untrackedSecretScan": {
"files": 6,
"findings": 0
},
"documentation": {
"files": 29,
"relativeLinks": 607,
"errors": []
},
"preservation": {
"groups": 57,
"statuses": {
"PARTIAL": 56,
"PASS": 0,
"FAIL": 0,
"DEFERRED": 1
},
"originalRoutesPreserved": true,
"currentConcreteRoutes": 33
},
"production": {
"main": "f9bf31c9a9a9f40edefb855a0911d7f057fa4990",
"originMain": "f9bf31c9a9a9f40edefb855a0911d7f057fa4990",
"status": "NO-GO",
"qualification": "Local fetched refs; direct push observation recorded separately."
},
"hashPolicy": "Portable JSON UTF8 LF; raw log hashes measure exact saved bytes.",
"limits": [
"Pattern scanner skips binary and files over2MiB; not historical Git/production secrets.",
"Local links check target existence, not URL/heading anchors.",
"Static evidence is not runtime/manual/device/provider/production acceptance."
]
}
@@ -43,12 +43,13 @@ function routes(jobId: number, cvId: number): RouteCase[] {
{ pattern: '/companies', path: '/companies', heading: t.companies, endpoint: '/companies' },
{ pattern: '/offers', path: '/offers', heading: t.offerComparisonTitle, endpoint: '/jobapplications' },
{ pattern: '/correspondence', path: '/correspondence', heading: t.correspondenceInboxTitle, endpoint: '/correspondence/page' },
{ pattern: '/correspondence/review', path: '/correspondence/review?route=all#review', final: '/correspondence?view=review', heading: t.correspondenceInboxTitle, endpoint: '/gmail/review-candidates', status: 409 },
{ pattern: '/correspondence/review', path: '/correspondence/review?route=all#review', final: '/correspondence?route=all&view=review#review', heading: t.correspondenceInboxTitle, endpoint: '/gmail/review-candidates', status: 409 },
{ pattern: '/profile', path: '/profile', heading: t.profile, endpoint: '/auth/me' },
{ pattern: '/applications/:id', path: `/applications/${jobId}?section=documents&followMode=waiting-update#workspace`, final: `/jobs/${jobId}?section=documents&followMode=waiting-update`, heading: t.jobDetails, endpoint: `/jobapplications/${jobId}/workspace` },
{ pattern: '/career', path: '/career?section=profile', heading: t.careerWorkspace, endpoint: '/career/profile' },
{ pattern: '/career/builder', path: '/career/builder', heading: t.cvBuilder, endpoint: '/cv/variants' },
{ pattern: '/career/builder/:id', path: `/career/builder/${cvId}`, heading: t.cvBuilder, endpoint: `/cv/variants/${cvId}` },
{ pattern: '/documents', path: '/documents', heading: t.documentsTitle, endpoint: '/documents' },
{ pattern: '/admin/audit', path: '/admin/audit', heading: t.auditLog, endpoint: '/admin/audit' },
{ pattern: '/admin/users', path: '/admin/users', heading: t.users, endpoint: '/users' },
{ pattern: '/admin/system', path: '/admin/system', heading: t.systemStatus, endpoint: '/admin/system' },
@@ -86,7 +87,7 @@ test.beforeEach(async ({ context }) => {
});
for (const identity of ['anonymous', 'normal', 'admin'] as const) {
test(`[P5-15-01 E02 E07 A01] all 32 routes retain native entry, refresh and ${identity} permissions`, async ({ page, playwright }, info) => {
test(`[P5-15-01 E02 E07 A01] all 33 routes retain native entry, refresh and ${identity} permissions`, async ({ page, playwright }, info) => {
test.setTimeout(300_000);
const admin = await playwright.request.newContext();
const evidence: { pattern: string; mode: string; destination: string; status?: number }[] = [];
@@ -105,7 +106,7 @@ for (const identity of ['anonymous', 'normal', 'admin'] as const) {
expect(cv.status()).toBe(200); cvId = (await cv.json()).id;
}
const census = routes(jobId, cvId);
expect(new Set(census.map(row => row.pattern)).size).toBe(32);
expect(new Set(census.map(row => row.pattern)).size).toBe(33);
for (const row of census) {
await test.step(row.pattern, async () => {
for (const mode of ['entry', 'refresh']) {
@@ -123,7 +124,7 @@ for (const identity of ['anonymous', 'normal', 'admin'] as const) {
expect.soft(native.status()).toBe(denied ? 403 : row.status ?? 200);
if (row.pattern === '/correspondence/review') {
expect(await native.json()).toMatchObject({ code: 'gmail_not_connected' });
await expect(page.getByRole('alert').filter({ hasText: 'Connect Gmail before using the Gmail review queue.' })).toBeVisible();
await expect(page.getByRole('alert').filter({ hasText: t.gmailReviewNotConnected })).toBeVisible();
}
if (denied) {
await expect(page.getByRole('alert').first()).toBeVisible();
+176 -176
View File
@@ -24,9 +24,9 @@
"@types/node": "^16.18.126",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"axios": "^1.19.0",
"axios": "^1.20.0",
"date-fns": "^4.1.0",
"next": "^16.3.3",
"next": "^16.3.8",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.18.2",
@@ -42,7 +42,7 @@
"@types/jest": "^30.0.0",
"babel-jest": "^30.4.1",
"eslint": "^9.39.5",
"eslint-config-next": "^16.3.3",
"eslint-config-next": "^16.3.8",
"identity-obj-proxy": "^3.0.0",
"jest": "^30.4.2",
"jest-environment-jsdom": "^30.4.1"
@@ -2610,9 +2610,9 @@
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz",
"integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz",
"integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==",
"cpu": [
"arm64"
],
@@ -2628,13 +2628,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.3.3"
"@img/sharp-libvips-darwin-arm64": "1.3.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz",
"integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz",
"integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==",
"cpu": [
"x64"
],
@@ -2650,20 +2650,20 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.3.3"
"@img/sharp-libvips-darwin-x64": "1.3.4"
}
},
"node_modules/@img/sharp-freebsd-wasm32": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz",
"integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz",
"integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==",
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"dependencies": {
"@img/sharp-wasm32": "0.35.4"
"@img/sharp-wasm32": "0.35.5"
},
"engines": {
"node": ">=20.9.0"
@@ -2673,9 +2673,9 @@
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz",
"integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz",
"integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==",
"cpu": [
"arm64"
],
@@ -2689,9 +2689,9 @@
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz",
"integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz",
"integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==",
"cpu": [
"x64"
],
@@ -2705,9 +2705,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz",
"integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz",
"integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==",
"cpu": [
"arm"
],
@@ -2724,9 +2724,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz",
"integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz",
"integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==",
"cpu": [
"arm64"
],
@@ -2743,9 +2743,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz",
"integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz",
"integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==",
"cpu": [
"ppc64"
],
@@ -2762,9 +2762,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-riscv64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz",
"integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz",
"integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==",
"cpu": [
"riscv64"
],
@@ -2781,9 +2781,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz",
"integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz",
"integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==",
"cpu": [
"s390x"
],
@@ -2800,9 +2800,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz",
"integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz",
"integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==",
"cpu": [
"x64"
],
@@ -2819,9 +2819,9 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz",
"integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz",
"integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==",
"cpu": [
"arm64"
],
@@ -2838,9 +2838,9 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz",
"integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz",
"integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==",
"cpu": [
"x64"
],
@@ -2857,9 +2857,9 @@
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz",
"integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz",
"integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==",
"cpu": [
"arm"
],
@@ -2878,13 +2878,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.3.3"
"@img/sharp-libvips-linux-arm": "1.3.4"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz",
"integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz",
"integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==",
"cpu": [
"arm64"
],
@@ -2903,13 +2903,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.3.3"
"@img/sharp-libvips-linux-arm64": "1.3.4"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz",
"integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz",
"integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==",
"cpu": [
"ppc64"
],
@@ -2928,13 +2928,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.3.3"
"@img/sharp-libvips-linux-ppc64": "1.3.4"
}
},
"node_modules/@img/sharp-linux-riscv64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz",
"integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz",
"integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==",
"cpu": [
"riscv64"
],
@@ -2953,13 +2953,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.3.3"
"@img/sharp-libvips-linux-riscv64": "1.3.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz",
"integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz",
"integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==",
"cpu": [
"s390x"
],
@@ -2978,13 +2978,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.3.3"
"@img/sharp-libvips-linux-s390x": "1.3.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz",
"integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz",
"integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==",
"cpu": [
"x64"
],
@@ -3003,13 +3003,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.3.3"
"@img/sharp-libvips-linux-x64": "1.3.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz",
"integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz",
"integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==",
"cpu": [
"arm64"
],
@@ -3028,13 +3028,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.3.3"
"@img/sharp-libvips-linuxmusl-arm64": "1.3.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz",
"integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz",
"integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==",
"cpu": [
"x64"
],
@@ -3053,13 +3053,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.3.3"
"@img/sharp-libvips-linuxmusl-x64": "1.3.4"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz",
"integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz",
"integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==",
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
@@ -3073,16 +3073,16 @@
}
},
"node_modules/@img/sharp-webcontainers-wasm32": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz",
"integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz",
"integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@img/sharp-wasm32": "0.35.4"
"@img/sharp-wasm32": "0.35.5"
},
"engines": {
"node": ">=20.9.0"
@@ -3092,9 +3092,9 @@
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz",
"integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz",
"integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==",
"cpu": [
"arm64"
],
@@ -3111,9 +3111,9 @@
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz",
"integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz",
"integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==",
"cpu": [
"ia32"
],
@@ -3130,9 +3130,9 @@
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz",
"integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz",
"integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==",
"cpu": [
"x64"
],
@@ -4102,15 +4102,15 @@
}
},
"node_modules/@next/env": {
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.4.tgz",
"integrity": "sha512-cjWZnUUa6jZq2kFaNe/ZyJdZonOZ/QoN0Zka2nz/FLOrfx14pQuM9c5RaSVkWMqgdt4ksgPAMWPyHSs/CyV48Q==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.8.tgz",
"integrity": "sha512-Al9zqHVV7TJv0eFuOU4U7Lvv74PTih4Ch63sk2xCIpSTkE3udFnaOcnzP2lQVymiL7yS9Cj2iClUXlR3EQ5sEw==",
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.3.4.tgz",
"integrity": "sha512-szW9y2Aumu4z88YXfTzcFsgUAg2k64uzbtcO5L9f1AKS4w/GUKJcbFllRflROVyNPgJtGOnvNxiyp3v6b+prIA==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.3.8.tgz",
"integrity": "sha512-eCR9RcLTZrVS+K3+VOhi0xMdUUGNuriCHHhU1ZoABC/QQ/Gp0OWbLKQxV2/9DkmihYdYRcQWFmZXW8M1AMUXYw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4151,9 +4151,9 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.4.tgz",
"integrity": "sha512-iBr3I5LZNk5/bgl5//iTgD2tcym14MX0Xo7fD//u9dYAEgGzza1y9oywluPtf74YnOswVdH1908aK9xVz7zQTw==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.8.tgz",
"integrity": "sha512-2JPRMh2nmQG5CiL7cXGL9AGwnPWJQ//cTtAUCT+w511QHk79SYz3LGv/pc5X643B/WEO0rvu3Yww0hqwt3kgeA==",
"cpu": [
"arm64"
],
@@ -4167,9 +4167,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.4.tgz",
"integrity": "sha512-2dpiSyl2Jw/NrBPaU2MAKGSa+2MR82pJIn4Sm5Rjr+gxAeuh0z158Su3Z2O8zn7UNNq+ej4bToed6RcRN/Lydg==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.8.tgz",
"integrity": "sha512-GZtCCOBKJ4leVIT/Th0llWKhD1ca92lzbQiS5R5ON9QkoiFnilFsebDae1JU2a3HWoKMEmEZWGs1AGLavVM72Q==",
"cpu": [
"x64"
],
@@ -4183,9 +4183,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.4.tgz",
"integrity": "sha512-+t+U8HZT+fApePCS5h89CSH3datz29MkzyfCn+6fpsZBG/oiEOhINcb9rtkv6sdpToLGFn2e6146NzaKCXkqrA==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.8.tgz",
"integrity": "sha512-O659ygeQYqneJ1fBKMpFxIFqYkYswu8IAS1OCKK/4f3ZgJJm1dRz4fVJZRi/kLLWjnBKnebOePA4WNv+sV1pVA==",
"cpu": [
"arm64"
],
@@ -4202,9 +4202,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.4.tgz",
"integrity": "sha512-mx03GNs1ocQA5JQ4FxDMmIsNkdrZh8cuezKCrId28e5/gIPU/l7Kcy2+vmCCzdjnnmXJy+iOAu+7K0QppO6Urg==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.8.tgz",
"integrity": "sha512-dSjKSyWpzxoO1d3DIZZcP4XJcNaKeLmxQMFOiYl5vuBRMmweIqnAhty8tAmRsvTss779cK1FtYnDMj40e4TQlg==",
"cpu": [
"arm64"
],
@@ -4221,9 +4221,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.4.tgz",
"integrity": "sha512-YIhGY6fSMfha52bnVxnzc9zaVBzJg+cqQTOD8tXIBSx4fuv0pVMxQTE0PaS59YhnMOiYiG09IMwxJAf/CFm/Dw==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.8.tgz",
"integrity": "sha512-lbqOuz3RPRcv+o9msNsJw5x4+Y1ZwPTs6vmL6DCf7i0fZfvng/F59wyeDwqHIvV0mK//RBy/jJkZ+nCKsSMXjQ==",
"cpu": [
"x64"
],
@@ -4240,9 +4240,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.4.tgz",
"integrity": "sha512-+eaaX6axpDb0yF1GCpiERe6njplvdC+nks/fKfcHu3XPGRrald8P3/X7yv7QLdjA51knnxwl9pxdIJsg+w1L+Q==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.8.tgz",
"integrity": "sha512-+316WswI8ScVgZeUd+1KGaXkHhaYQzCjvH/05TZSpJ8zBizb1a4G7DtO7F12jcBIqMOtsz9ji1t48fmKtzqsGA==",
"cpu": [
"x64"
],
@@ -4259,9 +4259,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.4.tgz",
"integrity": "sha512-0jcXW7Xs/uzICrmgV3MhDYDeRy++1CqnpDIerlPIqYO4bhzB4WNbX/aRnQclustsAyTkFKB0z6rbcjmNg5tR8A==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.8.tgz",
"integrity": "sha512-ji0gd4kMYUxO+1fJBIbiBVRCjzG/lloiyCccnlebvb1ZJ5qXCPZqYg4Jl1DrrixnWNMKylzgpmMWx0yNDYXlzw==",
"cpu": [
"arm64"
],
@@ -4275,9 +4275,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.4.tgz",
"integrity": "sha512-vvBzwu1pYQCp92maZCFCIw/XgOTMR5tur9GjakwIo2cmwRTMKajRZZDS9+e4KsUZWKu1E007WUeAFXRRjZeuzw==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.8.tgz",
"integrity": "sha512-WcTlaKt/TWkh5kUjdJcUmB1XgZ+1c6fz4Y9fDHL73YNSdGaUWjceeWrrlwF0nv19iABYWC4iAq1oX1w4Bn0vfg==",
"cpu": [
"x64"
],
@@ -5715,9 +5715,9 @@
}
},
"node_modules/axios": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz",
"integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==",
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz",
"integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.16.0",
@@ -5922,9 +5922,9 @@
}
},
"node_modules/brace-expansion": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz",
"integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6949,13 +6949,13 @@
}
},
"node_modules/eslint-config-next": {
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.3.4.tgz",
"integrity": "sha512-35/8RM10huEL9vlr8hUZMERMENHBrnyHN3ZZkF9efSgzGaqK34jIqry44A956//zriUhUAUW0XSkcolhrryqAA==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.3.8.tgz",
"integrity": "sha512-81vovwMe6NGnoFsl0KUJWzlS+y239i3fdsxs49gSEX3pDQW2cuKy+fxUPXNkQqqx6OCPheYPtBlSufc9aZ0k+w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@next/eslint-plugin-next": "16.3.4",
"@next/eslint-plugin-next": "16.3.8",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.32.0",
@@ -10308,12 +10308,12 @@
"license": "MIT"
},
"node_modules/next": {
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/next/-/next-16.3.4.tgz",
"integrity": "sha512-/Ztf6CeRH+ejEXUrYtqI4gkS66eFIHuSwqi60RgcpWKodxFZx2/dqVCMKBwILfAHXQ+F1b1vAudgj3mnxqtoIA==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/next/-/next-16.3.8.tgz",
"integrity": "sha512-U7QEZaTini6wKrb8A8hqLLqYQyCetegKjCpJOyxk642vWoMoU1x5PyZCJFvgYgiptA8xc5j/9xYlZFO7w9Sjmw==",
"license": "MIT",
"dependencies": {
"@next/env": "16.3.4",
"@next/env": "16.3.8",
"@swc/helpers": "0.5.23",
"baseline-browser-mapping": "^2.9.19",
"caniuse-lite": "^1.0.30001579",
@@ -10327,14 +10327,14 @@
"node": ">=20.9.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "16.3.4",
"@next/swc-darwin-x64": "16.3.4",
"@next/swc-linux-arm64-gnu": "16.3.4",
"@next/swc-linux-arm64-musl": "16.3.4",
"@next/swc-linux-x64-gnu": "16.3.4",
"@next/swc-linux-x64-musl": "16.3.4",
"@next/swc-win32-arm64-msvc": "16.3.4",
"@next/swc-win32-x64-msvc": "16.3.4",
"@next/swc-darwin-arm64": "16.3.8",
"@next/swc-darwin-x64": "16.3.8",
"@next/swc-linux-arm64-gnu": "16.3.8",
"@next/swc-linux-arm64-musl": "16.3.8",
"@next/swc-linux-x64-gnu": "16.3.8",
"@next/swc-linux-x64-musl": "16.3.8",
"@next/swc-win32-arm64-msvc": "16.3.8",
"@next/swc-win32-x64-msvc": "16.3.8",
"sharp": "^0.35.4"
},
"peerDependencies": {
@@ -11519,9 +11519,9 @@
}
},
"node_modules/sharp": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz",
"integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==",
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz",
"integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
@@ -11536,31 +11536,31 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.35.4",
"@img/sharp-darwin-x64": "0.35.4",
"@img/sharp-freebsd-wasm32": "0.35.4",
"@img/sharp-libvips-darwin-arm64": "1.3.3",
"@img/sharp-libvips-darwin-x64": "1.3.3",
"@img/sharp-libvips-linux-arm": "1.3.3",
"@img/sharp-libvips-linux-arm64": "1.3.3",
"@img/sharp-libvips-linux-ppc64": "1.3.3",
"@img/sharp-libvips-linux-riscv64": "1.3.3",
"@img/sharp-libvips-linux-s390x": "1.3.3",
"@img/sharp-libvips-linux-x64": "1.3.3",
"@img/sharp-libvips-linuxmusl-arm64": "1.3.3",
"@img/sharp-libvips-linuxmusl-x64": "1.3.3",
"@img/sharp-linux-arm": "0.35.4",
"@img/sharp-linux-arm64": "0.35.4",
"@img/sharp-linux-ppc64": "0.35.4",
"@img/sharp-linux-riscv64": "0.35.4",
"@img/sharp-linux-s390x": "0.35.4",
"@img/sharp-linux-x64": "0.35.4",
"@img/sharp-linuxmusl-arm64": "0.35.4",
"@img/sharp-linuxmusl-x64": "0.35.4",
"@img/sharp-webcontainers-wasm32": "0.35.4",
"@img/sharp-win32-arm64": "0.35.4",
"@img/sharp-win32-ia32": "0.35.4",
"@img/sharp-win32-x64": "0.35.4"
"@img/sharp-darwin-arm64": "0.35.5",
"@img/sharp-darwin-x64": "0.35.5",
"@img/sharp-freebsd-wasm32": "0.35.5",
"@img/sharp-libvips-darwin-arm64": "1.3.4",
"@img/sharp-libvips-darwin-x64": "1.3.4",
"@img/sharp-libvips-linux-arm": "1.3.4",
"@img/sharp-libvips-linux-arm64": "1.3.4",
"@img/sharp-libvips-linux-ppc64": "1.3.4",
"@img/sharp-libvips-linux-riscv64": "1.3.4",
"@img/sharp-libvips-linux-s390x": "1.3.4",
"@img/sharp-libvips-linux-x64": "1.3.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.3.4",
"@img/sharp-libvips-linuxmusl-x64": "1.3.4",
"@img/sharp-linux-arm": "0.35.5",
"@img/sharp-linux-arm64": "0.35.5",
"@img/sharp-linux-ppc64": "0.35.5",
"@img/sharp-linux-riscv64": "0.35.5",
"@img/sharp-linux-s390x": "0.35.5",
"@img/sharp-linux-x64": "0.35.5",
"@img/sharp-linuxmusl-arm64": "0.35.5",
"@img/sharp-linuxmusl-x64": "0.35.5",
"@img/sharp-webcontainers-wasm32": "0.35.5",
"@img/sharp-win32-arm64": "0.35.5",
"@img/sharp-win32-ia32": "0.35.5",
"@img/sharp-win32-x64": "0.35.5"
},
"peerDependenciesMeta": {
"@types/node": {
@@ -11700,9 +11700,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
+5 -5
View File
@@ -19,9 +19,9 @@
"@types/node": "^16.18.126",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"axios": "^1.19.0",
"axios": "^1.20.0",
"date-fns": "^4.1.0",
"next": "^16.3.3",
"next": "^16.3.8",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.18.2",
@@ -62,14 +62,14 @@
"@types/jest": "^30.0.0",
"babel-jest": "^30.4.1",
"eslint": "^9.39.5",
"eslint-config-next": "^16.3.3",
"eslint-config-next": "^16.3.8",
"identity-obj-proxy": "^3.0.0",
"jest": "^30.4.2",
"jest-environment-jsdom": "^30.4.1"
},
"overrides": {
"brace-expansion": "2.1.4",
"brace-expansion": "2.1.7",
"postcss": "8.5.25",
"sharp": "0.35.4"
"sharp": "0.35.5"
}
}
+3 -1
View File
@@ -20,7 +20,9 @@ export default defineConfig({
workers: 1,
retries: process.env.CI ? 1 : 0,
metadata: { isolatedDataRoot: reuseServers ? null : dataRoot },
reporter: process.env.CI ? "github" : "list",
reporter: process.env.CI
? [["list"], ["github"], ["json", { outputFile: ".artifacts/playwright-results.json" }]]
: "list",
use: {
baseURL: appUrl,
trace: "retain-on-failure",
@@ -0,0 +1,50 @@
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import { pathToFileURL } from 'node:url';
export function evaluateAudit(report, lock, now = Date.now()) {
if (report?.error || !report?.metadata?.vulnerabilities || !report?.vulnerabilities || !lock?.packages) {
return { accepted: [], failures: ['Missing or invalid audit/lock data'] };
}
const accepted = [], failures = [];
const allowedChain = new Set(['braces', 'micromatch', 'fast-glob', '@next/eslint-plugin-next', 'eslint-config-next']);
const devOnlyApprovedOrigin = (name, visited = new Set()) => {
const finding = report.vulnerabilities[name];
if (!allowedChain.has(name) || visited.has(name) || finding?.severity !== 'high' ||
!Array.isArray(finding.nodes) || !finding.nodes.length ||
finding.nodes.some(node => lock.packages[node]?.dev !== true || name === 'braces' && lock.packages[node]?.version !== '3.0.3') ||
!Array.isArray(finding.via) || !finding.via.length) return false;
const path = new Set(visited).add(name);
return finding.via.every(origin => typeof origin === 'string' ? devOnlyApprovedOrigin(origin, path) :
origin?.name === 'braces' && origin.dependency === 'braces' && origin.severity === 'high' && origin.url === 'https://github.com/advisories/GHSA-vfj7-8cjw-p6xm');
};
for (const [name, finding] of Object.entries(report.vulnerabilities)) {
if (!['info', 'low', 'moderate', 'high', 'critical'].includes(finding.severity)) {
failures.push(`${name}: invalid severity`);
} else if (['high', 'critical'].includes(finding.severity)) {
// Owner-approved on 17 October: no upstream fix; trusted lint patterns only, never production dependencies.
if (Number.isFinite(now) && now < Date.parse('2026-11-01T00:00:00Z') && devOnlyApprovedOrigin(name)) accepted.push(name);
else failures.push(`${name}: ${finding.severity}`);
}
}
return { accepted, failures };
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
try {
const result = spawnSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['audit', '--json', '--audit-level=high'], {
encoding: 'utf8', maxBuffer: 8 * 1024 * 1024, shell: process.platform === 'win32',
});
fs.mkdirSync('.artifacts', { recursive: true });
fs.writeFileSync('.artifacts/frontend-audit.json', result.stdout || '');
if (result.error || result.signal || ![0, 1].includes(result.status)) throw new Error('npm audit did not complete');
const report = JSON.parse(result.stdout);
const decision = evaluateAudit(report, JSON.parse(fs.readFileSync('package-lock.json', 'utf8')));
console.log(JSON.stringify({ report, ...decision,
temporaryException: decision.accepted.length ? 'GHSA-vfj7-8cjw-p6xm: dev-only; expires after 2026-10-31 UTC' : null }, null, 2));
process.exitCode = decision.failures.length ? 1 : 0;
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
}
@@ -0,0 +1,55 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { evaluateAudit } from './audit-dependencies.mjs';
const now = Date.parse('2026-10-17T18:00:00Z');
function fixture() {
return {
report: {
metadata: { vulnerabilities: { high: 2, critical: 0 } },
vulnerabilities: {
braces: { severity: 'high', nodes: ['node_modules/braces'], via: [{ name: 'braces', dependency: 'braces', severity: 'high', url: 'https://github.com/advisories/GHSA-vfj7-8cjw-p6xm' }] },
micromatch: { severity: 'high', nodes: ['node_modules/micromatch'], via: ['braces'] },
},
},
lock: { packages: { 'node_modules/braces': { dev: true, version: '3.0.3' }, 'node_modules/micromatch': { dev: true, version: '4.0.8' } } },
};
}
test('allows only the approved dev-only advisory and its linked findings before expiry', () => {
const { report, lock } = fixture();
assert.deepEqual(evaluateAudit(report, lock, now), { accepted: ['braces', 'micromatch'], failures: [] });
});
test('blocks the same advisory if any affected copy is a production dependency', () => {
const { report, lock } = fixture(); delete lock.packages['node_modules/braces'].dev;
assert.ok(evaluateAudit(report, lock, now).failures.length);
});
test('blocks any new advisory even inside the otherwise approved chain', () => {
const { report, lock } = fixture(); report.vulnerabilities.braces.via.push({ name: 'braces', url: 'https://github.com/advisories/UNKNOWN' });
assert.ok(evaluateAudit(report, lock, now).failures.length);
});
test('critical findings cannot use the high-only exception', () => {
const { report, lock } = fixture(); report.vulnerabilities.braces.severity = 'critical';
assert.ok(evaluateAudit(report, lock, now).failures.length);
});
test('a critical underlying advisory is rejected even if its aggregate severity is inconsistent', () => {
const { report, lock } = fixture(); report.vulnerabilities.braces.via[0].severity = 'critical';
assert.ok(evaluateAudit(report, lock, now).failures.length);
});
test('expires after 31 October UTC without an environment-variable bypass', () => {
const { report, lock } = fixture();
assert.ok(evaluateAudit(report, lock, Date.parse('2026-11-01T00:00:00Z')).failures.length);
});
test('missing nodes, missing lock entries and cyclic origins fail closed', () => {
for (const change of [x => x.report.vulnerabilities.braces.nodes = [], x => delete x.lock.packages['node_modules/braces'], x => x.report.vulnerabilities.braces.via = ['micromatch']]) {
const input = fixture(); change(input); assert.ok(evaluateAudit(input.report, input.lock, now).failures.length);
}
});
test('invalid reports and unknown severities fail closed', () => {
assert.ok(evaluateAudit({ error: { message: 'Registry unavailable' } }, {}, now).failures.length);
const { report, lock } = fixture(); report.vulnerabilities.braces.severity = 'unknown';
assert.ok(evaluateAudit(report, lock, now).failures.length);
});
test('non-blocking moderate findings retain the existing audit threshold', () => {
const report = { metadata: { vulnerabilities: { moderate: 1 } }, vulnerabilities: { tool: { severity: 'moderate', via: [] } } };
assert.deepEqual(evaluateAudit(report, { packages: {} }, now), { accepted: [], failures: [] });
});