Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d343dc94b | ||
|
|
8414c02e10 | ||
|
|
abfbca1d45 | ||
|
|
b37101435a | ||
|
|
7fc3457b63 |
No files matched your search
@@ -2,6 +2,8 @@ name: CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches: [develop, main]
|
||||||
|
tags: ['v*']
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, reopened, synchronize, edited]
|
types: [opened, reopened, synchronize, edited]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|||||||
@@ -46,3 +46,7 @@ Thumbs.db
|
|||||||
|
|
||||||
# Auto-generated i18n files
|
# Auto-generated i18n files
|
||||||
/src/app/i18n-registry.ts
|
/src/app/i18n-registry.ts
|
||||||
|
|
||||||
|
# Claude Code local files (.claude is a symlink to .agents/)
|
||||||
|
.agents/settings.local.json
|
||||||
|
.agents/worktrees/
|
||||||
@@ -249,13 +249,13 @@ The server answers `GET` and `HEAD` only (other methods get 405). It serves file
|
|||||||
|
|
||||||
[`.gitea/workflows/ci.yml`](.gitea/workflows/ci.yml) defines Gitea Actions validation, functional coverage, production builds, security scans, and Docker image publication. [Issue #268](https://gitea.nessar.fr/nessar/vac-optimizer/issues/268) tracks activation. Actions is enabled with owner approval; publisher credentials and live workflow validation are still pending. The [activation guide](docs/gitea-actions.md) records runner, credential, permission, and merge-gate requirements.
|
[`.gitea/workflows/ci.yml`](.gitea/workflows/ci.yml) defines Gitea Actions validation, functional coverage, production builds, security scans, and Docker image publication. [Issue #268](https://gitea.nessar.fr/nessar/vac-optimizer/issues/268) tracks activation. Actions is enabled with owner approval; publisher credentials and live workflow validation are still pending. The [activation guide](docs/gitea-actions.md) records runner, credential, permission, and merge-gate requirements.
|
||||||
|
|
||||||
The workflow runs on branch/tag pushes, PR opening/reopening/synchronization/edits, and manual dispatch. Manual runs validate without publishing images. Required jobs cover policy regression tests, GitFlow and Conventional Commits, ESLint, Prettier, `bun check` type checks, Bun coverage, the production build, Semgrep SAST, and Trivy dependency/secret scans. Coverage, build output, and SARIF reports are retained for seven days.
|
The workflow runs on pushes to `develop` and `main`, `v*` tag pushes, PR opening/reopening/synchronization/edits, and manual dispatch. Other branches run CI only through their PR, so a branch without a PR gets no automatic CI or image. Manual runs validate without publishing images. Required jobs cover policy regression tests, GitFlow and Conventional Commits, ESLint, Prettier, `bun check` type checks, Bun coverage, the production build, Semgrep SAST, and Trivy dependency/secret scans. Coverage, build output, and SARIF reports are retained for seven days.
|
||||||
|
|
||||||
Jobs that run project commands install Bun 1.4.3 with `scripts/install-bun.sh` (a checksum-verified download, no npm) and run their steps with a `PATH` that holds Bun but no Node. The job containers keep Node only for Gitea's checkout and artifact actions, which are JavaScript actions the runner executes itself; see the [activation guide](docs/gitea-actions.md#runners-and-permissions).
|
Jobs that run project commands install Bun 1.4.3 with `scripts/install-bun.sh` (a checksum-verified download, no npm) and run their steps with a `PATH` that holds Bun but no Node. The job containers keep Node only for Gitea's checkout and artifact actions, which are JavaScript actions the runner executes itself; see the [activation guide](docs/gitea-actions.md#runners-and-permissions).
|
||||||
|
|
||||||
Functional tests run with `bun test --isolate`; coverage is line-level only (Bun reports no branch or function data). The separate required benchmark job runs without coverage in a container limited to 3 GiB (JavaScriptCore has no heap-size flag, so this replaces the former 2 GiB Node heap cap) and keeps its 240-second hang guard, one warm-up and two measured repetitions of all seven scenarios. [#78](https://gitea.nessar.fr/nessar/vac-optimizer/issues/78) restored this gate after a complete local pass in 298.6 seconds; see [the measurements and hardware limits](docs/benchmark/issue-78.md). After [#302](docs/benchmark/issue-302.md) the local protocol takes about 41 seconds. Benchmark success is required for image publication and the aggregate `required` job. The lowered hang guard and tightened multipliers still need confirmation on the CI runners.
|
Functional tests run with `bun test --isolate`; coverage is line-level only (Bun reports no branch or function data). The separate required benchmark job runs without coverage in a container limited to 3 GiB (JavaScriptCore has no heap-size flag, so this replaces the former 2 GiB Node heap cap) and keeps its 240-second hang guard, one warm-up and two measured repetitions of all seven scenarios. [#78](https://gitea.nessar.fr/nessar/vac-optimizer/issues/78) restored this gate after a complete local pass in 298.6 seconds; see [the measurements and hardware limits](docs/benchmark/issue-78.md). After [#302](docs/benchmark/issue-302.md) the local protocol takes about 41 seconds. Benchmark success is required for image publication and the aggregate `required` job. The lowered hang guard and tightened multipliers still need confirmation on the CI runners.
|
||||||
|
|
||||||
The `required` job aggregates successful validation and expected image publication, accepting the image job's intentional skip for fork PRs and manual runs. Image jobs require successful validation and are limited to pushes and same-repository PRs. Registry credentials are confined to the publishing step.
|
The `required` job aggregates successful validation and expected image publication, accepting the image job's intentional skip for fork PRs and manual runs. Image jobs require successful validation and are limited to pushes to `develop`, `main` and release tags, and same-repository PRs. Registry credentials are confined to the publishing step.
|
||||||
|
|
||||||
Both protected branches still require `gitlab-to-gitea/ci-conversion-required` until the owner approves the first merge message and a successful Gitea PR run, then replaces the migration gate with the actual pull-request context for `required`. Local validation does not satisfy this status. Historical CI reports and artifacts remain in administrator rollback backups; the original configuration is retained in Git history.
|
Both protected branches still require `gitlab-to-gitea/ci-conversion-required` until the owner approves the first merge message and a successful Gitea PR run, then replaces the migration gate with the actual pull-request context for `required`. Local validation does not satisfy this status. Historical CI reports and artifacts remain in administrator rollback backups; the original configuration is retained in Git history.
|
||||||
|
|
||||||
@@ -271,7 +271,7 @@ The image publisher implements this tag contract in `gitea.nessar.fr/nessar/vac-
|
|||||||
|
|
||||||
| Trigger | Published image tags |
|
| Trigger | Published image tags |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Successful push to any branch | `branch-<normalized branch slug>` and `sha-<full commit SHA>` |
|
| Successful push to `develop` or `main`, or same-repository PR | `branch-<normalized branch slug>` and `sha-<full commit SHA>` |
|
||||||
| Explicit release tag `vX.Y.Z` | `vX.Y.Z` and `latest`, using the existing SHA image |
|
| Explicit release tag `vX.Y.Z` | `vX.Y.Z` and `latest`, using the existing SHA image |
|
||||||
|
|
||||||
For example, `feature/calendar` uses `branch-feature-calendar`; integration uses `branch-develop`. Preserve the previous slug normalization: lowercase the branch name, replace characters other than `a-z` and `0-9` with `-`, truncate to 63 bytes, and remove leading/trailing `-`. Choose branch names with distinct slugs. Only valid release tags can update `latest`. Automatic image publication and tag promotion begin after the owner provisions publisher credentials and validates publication permissions.
|
For example, `feature/calendar` uses `branch-feature-calendar`; integration uses `branch-develop`. Preserve the previous slug normalization: lowercase the branch name, replace characters other than `a-z` and `0-9` with `-`, truncate to 63 bytes, and remove leading/trailing `-`. Choose branch names with distinct slugs. Only valid release tags can update `latest`. Automatic image publication and tag promotion begin after the owner provisions publisher credentials and validates publication permissions.
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ Before the protocol, in the same process, the spec times the calibration engine
|
|||||||
a871f8f) on the `two years` scenario, best of three, and prints the result. This
|
a871f8f) on the `two years` scenario, best of three, and prints the result. This
|
||||||
bundle never changes, so ratios stay comparable with the recorded history; the
|
bundle never changes, so ratios stay comparable with the recorded history; the
|
||||||
differential check's frozen bundle can be rebuilt independently.
|
differential check's frozen bundle can be rebuilt independently.
|
||||||
|
The spec repeats the calibration after the protocol and uses the slower of the two
|
||||||
|
values, so load that starts during the protocol slows the yardstick too; the second
|
||||||
|
value can only loosen the budgets. When it does, the log prints it and the summary
|
||||||
|
table again with the budgets actually applied.
|
||||||
Each scenario's budget is its multiplier times that calibration; the mean
|
Each scenario's budget is its multiplier times that calibration; the mean
|
||||||
`planningMs` of its two measured repetitions must not exceed it. A scenario
|
`planningMs` of its two measured repetitions must not exceed it. A scenario
|
||||||
without a multiplier fails too. #286 tightened the multipliers once #302 met
|
without a multiplier fails too. #286 tightened the multipliers once #302 met
|
||||||
|
|||||||
+34
-29
@@ -11,33 +11,38 @@ message.
|
|||||||
|
|
||||||
## Jobs and triggers
|
## Jobs and triggers
|
||||||
|
|
||||||
Every branch and tag push runs CI. Pull requests run on opening, reopening,
|
Pushes to `develop` and `main` and `v*` tag pushes run CI. Other branches
|
||||||
synchronization, and title/body edits. Manual dispatch runs validation without
|
(feature, renovate, release, hotfix) run it only through their pull request; a
|
||||||
publishing images. There is no schedule or deployment job in the preserved
|
branch without a PR gets no automatic CI or image, so open a draft PR to get
|
||||||
configuration; the Compose files remain manual deployment options.
|
one. Pull requests run on opening, reopening, synchronization, and title/body
|
||||||
|
edits. Manual dispatch runs validation without publishing images. There is no
|
||||||
|
schedule or deployment job in the preserved configuration; the Compose files
|
||||||
|
remain manual deployment options.
|
||||||
|
|
||||||
| Job | Behavior |
|
| Job | Behavior |
|
||||||
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `policy` | Tests the policy and publisher with Bun, then validates the checked-out commit, GitFlow, PR title, commits, and stable release version. |
|
| `policy` | Tests the policy and publisher with Bun, then validates the checked-out commit, GitFlow, PR title, commits, and stable release version. |
|
||||||
| `validate` | Runs ESLint, Prettier (application, `scripts/ci-*.mjs` and workflow files) and `bun check` type checks of application and test code, all on Bun with the frozen lockfile. |
|
| `validate` | Runs ESLint, Prettier (application, `scripts/ci-*.mjs` and workflow files) and `bun check` type checks of application and test code, all on Bun with the frozen lockfile. |
|
||||||
| `test` | Runs functional coverage with `bun test --isolate` (line coverage only), converts it to HTML and Cobertura, and uploads coverage for seven days even on failure. |
|
| `test` | Runs functional coverage with `bun test --isolate` (line coverage only), converts it to HTML and Cobertura, and uploads coverage for seven days even on failure. |
|
||||||
| `benchmark` | Required seven-scenario protocol (one warm-up, two measured repetitions) outside coverage in a container limited to 3 GiB. It first times the frozen engine on `two years` (best of three) and fails any scenario whose mean planning time exceeds its multiplier times that calibration; the 240-second timeout is only a hang guard. |
|
| `benchmark` | Required seven-scenario protocol (one warm-up, two measured repetitions) outside coverage in a container limited to 3 GiB. It times the frozen engine on `two years` (best of three) before and after the protocol, and fails any scenario whose mean planning time exceeds its multiplier times that calibration; the 240-second timeout is only a hang guard. |
|
||||||
| `build` | Builds the client-only Angular production bundle (the only check of Angular templates; `bun check` does not replace it) and the Bun static-server bundle, and retains `dist` for seven days. |
|
| `build` | Builds the client-only Angular production bundle (the only check of Angular templates; `bun check` does not replace it) and the Bun static-server bundle, and retains `dist` for seven days. |
|
||||||
| `security` | Runs Semgrep JavaScript/TypeScript SAST and Trivy dependency/secret scans; retains SARIF reports for seven days. Scanner execution must succeed; findings remain reports rather than a new severity gate. The scanners are not project JavaScript, so this job installs no Bun. |
|
| `security` | Runs Semgrep JavaScript/TypeScript SAST and Trivy dependency/secret scans; retains SARIF reports for seven days. Scanner execution must succeed; findings remain reports rather than a new severity gate. The scanners are not project JavaScript, so this job installs no Bun. |
|
||||||
| `images` | Publishes branch/SHA images after required validation, including the benchmark, or promotes an existing SHA image for a stable tag, running the publisher with Bun. Runs only for pushes and same-repository PRs. |
|
| `images` | Publishes branch/SHA images after required validation, including the benchmark, or promotes an existing SHA image for a stable tag, running the publisher with Bun. Runs only for pushes and same-repository PRs. |
|
||||||
| `required` | Fails on failed, cancelled, or skipped required validation, including the benchmark. Requires image publication when expected; accepts its intentional skip for fork PRs and manual dispatch. It is shell only and needs no Bun. |
|
| `required` | Fails on failed, cancelled, or skipped required validation, including the benchmark. Requires image publication when expected; accepts its intentional skip for fork PRs and manual dispatch. It is shell only and needs no Bun. |
|
||||||
|
|
||||||
The benchmark runs on both push and pull_request events: the push run gates image
|
The benchmark gates image publication (`images.needs` includes `benchmark`) and
|
||||||
publication (`images.needs` includes `benchmark`) and the `required` aggregate.
|
the `required` aggregate. The relative gate absorbs a slower runner and load
|
||||||
The relative gate absorbs a slower runner and load that lasts through the whole
|
that lasts through the whole job, because calibration runs in the same job. It
|
||||||
job, because calibration runs in the same job. It does not absorb load that
|
calibrates before and after the protocol and judges against the slower value,
|
||||||
starts or stops during the protocol, such as an overlapping push and
|
so it also absorbs load that starts during the protocol, such as another run
|
||||||
pull_request run of the same commit; the 240-second hang guard still bounds
|
sharing the runner. It does not absorb load that ends before the second
|
||||||
those runs.
|
calibration; the 240-second hang guard still bounds those runs.
|
||||||
|
|
||||||
Push and PR runs are separate so PR title and rebase checks run on the PR event.
|
Because branch pushes outside `develop` and `main` do not trigger CI, a commit
|
||||||
New runs cancel older runs for the same event/ref. Image jobs serialize per
|
on a PR branch runs only on PR events, which carry the PR title and rebase
|
||||||
branch slug; stable promotions share one group and are not cancelled in progress.
|
checks. New runs cancel older runs for the same event/ref. Image jobs serialize
|
||||||
|
per branch slug; stable promotions share one group and are not cancelled in
|
||||||
|
progress.
|
||||||
|
|
||||||
## Runners and permissions
|
## Runners and permissions
|
||||||
|
|
||||||
@@ -111,10 +116,10 @@ only one place fails CI instead of drifting.
|
|||||||
## Images and releases
|
## Images and releases
|
||||||
|
|
||||||
Images use the [Gitea container registry](https://docs.gitea.com/usage/packages/container/)
|
Images use the [Gitea container registry](https://docs.gitea.com/usage/packages/container/)
|
||||||
at `gitea.nessar.fr/nessar/vac-optimizer`. Branch pushes and same-repository PRs
|
at `gitea.nessar.fr/nessar/vac-optimizer`. Pushes to `develop` and `main` and
|
||||||
publish `branch-<slug>` and `sha-<full commit SHA>`. Slugs retain the previous
|
same-repository PRs publish `branch-<slug>` and `sha-<full commit SHA>`. Slugs
|
||||||
lowercase, non-alphanumeric-to-hyphen, 63-byte normalization; choose distinct
|
retain the previous lowercase, non-alphanumeric-to-hyphen, 63-byte
|
||||||
branch slugs.
|
normalization; choose distinct branch slugs.
|
||||||
|
|
||||||
Only stable `vX.Y.Z` tags matching the tagged commit's `package.json` version
|
Only stable `vX.Y.Z` tags matching the tagged commit's `package.json` version
|
||||||
and reachable from `main` may promote images. Promotion first pulls the existing
|
and reachable from `main` may promote images. Promotion first pulls the existing
|
||||||
@@ -147,7 +152,7 @@ secrets. Complete the remaining activation checks in this order:
|
|||||||
`git show 0d900fd72d1a405d257f5a566c55cc0cb3d2bc61:.gitlab-ci.yml`.
|
`git show 0d900fd72d1a405d257f5a566c55cc0cb3d2bc61:.gitlab-ci.yml`.
|
||||||
2. Publish the reviewed feature branch and open its PR to register the workflow.
|
2. Publish the reviewed feature branch and open its PR to register the workflow.
|
||||||
Once registered, manual dispatch verifies validation and artifacts without
|
Once registered, manual dispatch verifies validation and artifacts without
|
||||||
publishing images. Push and PR validation can also run now; their image and
|
publishing images. PR validation can also run now; its image and
|
||||||
aggregate checks remain blocked until publisher secrets are provisioned.
|
aggregate checks remain blocked until publisher secrets are provisioned.
|
||||||
3. Verify runner isolation and Docker service support, then provision the two
|
3. Verify runner isolation and Docker service support, then provision the two
|
||||||
publisher secrets. Review the package publisher's permissions and registry
|
publisher secrets. Review the package publisher's permissions and registry
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { calculatePlanningAsyncInternal } from '../services/frozen-engine/leave-
|
|||||||
import { createPlanningCalculation } from '../services/leave-planner.service';
|
import { createPlanningCalculation } from '../services/leave-planner.service';
|
||||||
import {
|
import {
|
||||||
BENCHMARK_BUDGET_MULTIPLIERS,
|
BENCHMARK_BUDGET_MULTIPLIERS,
|
||||||
|
type BenchmarkMeasurement,
|
||||||
benchmarkReport,
|
benchmarkReport,
|
||||||
benchmarkScenarios,
|
benchmarkScenarios,
|
||||||
benchmarkSummaryHeader,
|
benchmarkSummaryHeader,
|
||||||
@@ -71,23 +72,43 @@ describe('leave planner benchmark protocol', () => {
|
|||||||
}
|
}
|
||||||
// Calibration engine (frozen at a871f8f) on 'two years', best of three: the runner-speed yardstick.
|
// Calibration engine (frozen at a871f8f) on 'two years', best of three: the runner-speed yardstick.
|
||||||
const calibrationRequest = scenarios.find((item) => item.name === 'two years')!.request;
|
const calibrationRequest = scenarios.find((item) => item.name === 'two years')!.request;
|
||||||
let calibrationMs = Infinity;
|
const calibrate = async () => {
|
||||||
for (let run = 0; run < 3; run++) {
|
let best = Infinity;
|
||||||
const request = structuredClone(calibrationRequest);
|
for (let run = 0; run < 3; run++) {
|
||||||
const start = performance.now();
|
const request = structuredClone(calibrationRequest);
|
||||||
await calculatePlanningAsyncInternal({ ...request, calculationDate: '2024-01-01' });
|
const start = performance.now();
|
||||||
calibrationMs = Math.min(calibrationMs, performance.now() - start);
|
await calculatePlanningAsyncInternal({
|
||||||
}
|
...request,
|
||||||
|
calculationDate: '2024-01-01',
|
||||||
|
});
|
||||||
|
best = Math.min(best, performance.now() - start);
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
};
|
||||||
|
const calibrationBefore = await calibrate();
|
||||||
// Direct stdout writes show each row as soon as its scenario finishes.
|
// Direct stdout writes show each row as soon as its scenario finishes.
|
||||||
const print = (text: string) => process.stdout.write(`${text}\n`);
|
const print = (text: string) => process.stdout.write(`${text}\n`);
|
||||||
print(
|
print(
|
||||||
`\nCalibration: frozen engine on "two years", best of 3 = ${calibrationMs.toFixed(1)} ms` +
|
`\nCalibration: frozen engine on "two years", best of 3 = ${calibrationBefore.toFixed(1)} ms` +
|
||||||
'\nBudget = limit x calibration; ratio = mean / calibration.\n',
|
'\nBudget = limit x calibration; ratio = mean / calibration.\n',
|
||||||
);
|
);
|
||||||
print(benchmarkSummaryHeader());
|
print(benchmarkSummaryHeader());
|
||||||
const measurements = await runLeavePlannerBenchmark(2, (rows) =>
|
const scenarioRows: BenchmarkMeasurement[][] = [];
|
||||||
print(benchmarkSummaryRow(rows, calibrationMs)),
|
const measurements = await runLeavePlannerBenchmark(2, (rows) => {
|
||||||
);
|
scenarioRows.push(rows);
|
||||||
|
print(benchmarkSummaryRow(rows, calibrationBefore));
|
||||||
|
});
|
||||||
|
// Load that starts after the first calibration slows the scenarios but not the yardstick;
|
||||||
|
// calibrating again and judging against the slower value absorbs it. Never tighter than before.
|
||||||
|
const calibrationAfter = await calibrate();
|
||||||
|
const calibrationMs = Math.max(calibrationBefore, calibrationAfter);
|
||||||
|
if (calibrationMs !== calibrationBefore) {
|
||||||
|
print(
|
||||||
|
`\nCalibration after the protocol = ${calibrationAfter.toFixed(1)} ms; budgets use the slower: ${calibrationMs.toFixed(1)} ms\n`,
|
||||||
|
);
|
||||||
|
print(benchmarkSummaryHeader());
|
||||||
|
for (const rows of scenarioRows) print(benchmarkSummaryRow(rows, calibrationMs));
|
||||||
|
}
|
||||||
for (const [name, placements] of [
|
for (const [name, placements] of [
|
||||||
['two years', 60],
|
['two years', 60],
|
||||||
['three years', 90],
|
['three years', 90],
|
||||||
|
|||||||
Reference in new issue
Block a user