vesma-cortex 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vesma_cortex-0.2.1/.github/workflows/ci.yml +134 -0
- vesma_cortex-0.2.1/.github/workflows/nightly.yml +31 -0
- vesma_cortex-0.2.1/.github/workflows/release-model.yml +230 -0
- vesma_cortex-0.2.1/.github/workflows/release-py.yml +143 -0
- vesma_cortex-0.2.1/.gitignore +41 -0
- vesma_cortex-0.2.1/CHANGELOG.md +50 -0
- vesma_cortex-0.2.1/LICENSE +202 -0
- vesma_cortex-0.2.1/Makefile +44 -0
- vesma_cortex-0.2.1/PKG-INFO +126 -0
- vesma_cortex-0.2.1/README.md +109 -0
- vesma_cortex-0.2.1/artifacts/manifests/.gitkeep +0 -0
- vesma_cortex-0.2.1/artifacts/manifests/a2-store-corpus.md +81 -0
- vesma_cortex-0.2.1/artifacts/manifests/b2-targeted-export.md +115 -0
- vesma_cortex-0.2.1/artifacts/manifests/dataset-v4.md +183 -0
- vesma_cortex-0.2.1/artifacts/manifests/layer-a-evalsets.md +204 -0
- vesma_cortex-0.2.1/artifacts/manifests/mnema-cortex-v1-stage2.md +24 -0
- vesma_cortex-0.2.1/artifacts/manifests/stage1-run.md +36 -0
- vesma_cortex-0.2.1/artifacts/manifests/synth-stage1.md +28 -0
- vesma_cortex-0.2.1/artifacts/manifests/vesma-cortex-v1-ds1000.md +15 -0
- vesma_cortex-0.2.1/artifacts/runs/.gitkeep +0 -0
- vesma_cortex-0.2.1/artifacts/runs/a5-ds1000-recovery.jsonl +1 -0
- vesma_cortex-0.2.1/artifacts/runs/a5-ds1000-runlog.jsonl +1 -0
- vesma_cortex-0.2.1/artifacts/runs/b1-single-shot-runlog.jsonl +1 -0
- vesma_cortex-0.2.1/artifacts/runs/b2-single-shot-runlog.jsonl +1 -0
- vesma_cortex-0.2.1/artifacts/runs/b2p-v42-single-shot-runlog.jsonl +1 -0
- vesma_cortex-0.2.1/docs/REBRAND-RESIDUALS.md +39 -0
- vesma_cortex-0.2.1/docs/charter.md +130 -0
- vesma_cortex-0.2.1/docs/decisions/.gitkeep +0 -0
- vesma_cortex-0.2.1/docs/decisions/0001-archcom-a1-verdicts.md +309 -0
- vesma_cortex-0.2.1/docs/decisions/0002-duty-registry-v2.md +151 -0
- vesma_cortex-0.2.1/docs/decisions/0003-model-artifacts-release-and-eval.md +205 -0
- vesma_cortex-0.2.1/docs/decisions/0004-embed-train-routing.md +202 -0
- vesma_cortex-0.2.1/docs/experiments/b0-escalation.md +72 -0
- vesma_cortex-0.2.1/docs/experiments/b0-telemetry-plan.md +89 -0
- vesma_cortex-0.2.1/docs/experiments/b2p-v41-sanity-fail-diagnosis.md +70 -0
- vesma_cortex-0.2.1/docs/experiments/calibration-b1-domain-diversity.md +50 -0
- vesma_cortex-0.2.1/docs/experiments/calibration-b2-edge-neighborhood.md +106 -0
- vesma_cortex-0.2.1/docs/experiments/calibration-b2p-prereg.md +360 -0
- vesma_cortex-0.2.1/docs/experiments/calibration-b2p.md +218 -0
- vesma_cortex-0.2.1/docs/experiments/calibration-ds1000-a5.md +58 -0
- vesma_cortex-0.2.1/docs/experiments/dataset-v4-plan.md +60 -0
- vesma_cortex-0.2.1/docs/experiments/p0-export-desync-investigation.md +248 -0
- vesma_cortex-0.2.1/docs/handoff-w5d.md +96 -0
- vesma_cortex-0.2.1/docs/reports/vesma-cortex-model-report.md +192 -0
- vesma_cortex-0.2.1/docs/reports/vesma-embed-model-report.md +157 -0
- vesma_cortex-0.2.1/docs/roadmap-v2.md +151 -0
- vesma_cortex-0.2.1/docs/runbooks/operator-protocol.md +149 -0
- vesma_cortex-0.2.1/docs/runbooks/xpu-walkthrough.md +104 -0
- vesma_cortex-0.2.1/docs/specs/data-contract.md +173 -0
- vesma_cortex-0.2.1/docs/specs/eval-methodology.md +252 -0
- vesma_cortex-0.2.1/docs/specs/inference-v1.md +298 -0
- vesma_cortex-0.2.1/docs/specs/labeling-policy-b2.md +306 -0
- vesma_cortex-0.2.1/docs/specs/model-registry.md +152 -0
- vesma_cortex-0.2.1/docs/status.md +119 -0
- vesma_cortex-0.2.1/docs/synth.md +128 -0
- vesma_cortex-0.2.1/gate_contract.json +138 -0
- vesma_cortex-0.2.1/models/vesma-cortex-v1/MODEL_CARD.md +54 -0
- vesma_cortex-0.2.1/models/vesma-cortex-v1/eval-results.json +185 -0
- vesma_cortex-0.2.1/models/vesma-cortex-v1/manifest.json +28 -0
- vesma_cortex-0.2.1/models/vesma-cortex-v1/model.onnx +0 -0
- vesma_cortex-0.2.1/pyproject.toml +112 -0
- vesma_cortex-0.2.1/ruff.toml +9 -0
- vesma_cortex-0.2.1/scripts/a2_field_cosines.py +188 -0
- vesma_cortex-0.2.1/scripts/b1_pipeline.sh +18 -0
- vesma_cortex-0.2.1/scripts/b2_graph_sidecar.py +474 -0
- vesma_cortex-0.2.1/scripts/b2_targeted_export.py +185 -0
- vesma_cortex-0.2.1/scripts/build_train_manifest.py +191 -0
- vesma_cortex-0.2.1/scripts/eval_sanity_suite.py +95 -0
- vesma_cortex-0.2.1/scripts/gen_dataset_v4.py +1147 -0
- vesma_cortex-0.2.1/scripts/gen_evalsets.py +133 -0
- vesma_cortex-0.2.1/scripts/gen_evalsets_v2.py +308 -0
- vesma_cortex-0.2.1/scripts/gen_model_card.py +224 -0
- vesma_cortex-0.2.1/scripts/gen_synth_corpus.py +1134 -0
- vesma_cortex-0.2.1/scripts/labeling_pass2_jev.py +129 -0
- vesma_cortex-0.2.1/scripts/labeling_pass3_jev_native.py +131 -0
- vesma_cortex-0.2.1/scripts/run_layer_a.py +125 -0
- vesma_cortex-0.2.1/scripts/s1_synth_similarity.py +96 -0
- vesma_cortex-0.2.1/scripts/smoke_pipeline.py +626 -0
- vesma_cortex-0.2.1/scripts/stage2_run.py +1302 -0
- vesma_cortex-0.2.1/scripts/verify_dataset.py +273 -0
- vesma_cortex-0.2.1/src/cortex/__init__.py +26 -0
- vesma_cortex-0.2.1/src/cortex/artifacts/__init__.py +160 -0
- vesma_cortex-0.2.1/src/cortex/candidates/__init__.py +13 -0
- vesma_cortex-0.2.1/src/cortex/candidates/d_boost.py +429 -0
- vesma_cortex-0.2.1/src/cortex/candidates/n_head.py +447 -0
- vesma_cortex-0.2.1/src/cortex/cli/__init__.py +5 -0
- vesma_cortex-0.2.1/src/cortex/cli/main.py +940 -0
- vesma_cortex-0.2.1/src/cortex/data/__init__.py +21 -0
- vesma_cortex-0.2.1/src/cortex/data/corner_qa.py +619 -0
- vesma_cortex-0.2.1/src/cortex/data/field_cosines.py +169 -0
- vesma_cortex-0.2.1/src/cortex/data/fingerprints.py +81 -0
- vesma_cortex-0.2.1/src/cortex/data/holdout.py +137 -0
- vesma_cortex-0.2.1/src/cortex/data/store_export.py +1366 -0
- vesma_cortex-0.2.1/src/cortex/data/watchlist.py +411 -0
- vesma_cortex-0.2.1/src/cortex/eval/__init__.py +21 -0
- vesma_cortex-0.2.1/src/cortex/eval/runner.py +323 -0
- vesma_cortex-0.2.1/src/cortex/eval/sanity.py +696 -0
- vesma_cortex-0.2.1/src/cortex/evalsets/__init__.py +76 -0
- vesma_cortex-0.2.1/src/cortex/evalsets/generate.py +740 -0
- vesma_cortex-0.2.1/src/cortex/evalsets/runner.py +439 -0
- vesma_cortex-0.2.1/src/cortex/evalsets/taxonomy.py +205 -0
- vesma_cortex-0.2.1/src/cortex/evalsets/topics.py +256 -0
- vesma_cortex-0.2.1/src/cortex/features/__init__.py +34 -0
- vesma_cortex-0.2.1/src/cortex/features/graph.py +307 -0
- vesma_cortex-0.2.1/src/cortex/features/pair.py +299 -0
- vesma_cortex-0.2.1/src/cortex/pretrain/__init__.py +17 -0
- vesma_cortex-0.2.1/src/cortex/pretrain/corruption.py +356 -0
- vesma_cortex-0.2.1/src/cortex/select/__init__.py +19 -0
- vesma_cortex-0.2.1/src/cortex/select/cv.py +179 -0
- vesma_cortex-0.2.1/src/cortex/synth/__init__.py +96 -0
- vesma_cortex-0.2.1/src/cortex/synth/generate.py +1375 -0
- vesma_cortex-0.2.1/tests/synth.py +193 -0
- vesma_cortex-0.2.1/tests/test_cli.py +506 -0
- vesma_cortex-0.2.1/tests/test_corner_qa.py +559 -0
- vesma_cortex-0.2.1/tests/test_corruption.py +178 -0
- vesma_cortex-0.2.1/tests/test_cv.py +180 -0
- vesma_cortex-0.2.1/tests/test_d_boost.py +170 -0
- vesma_cortex-0.2.1/tests/test_dataset_v4_batch.py +321 -0
- vesma_cortex-0.2.1/tests/test_eval_sanity.py +590 -0
- vesma_cortex-0.2.1/tests/test_evalsets.py +874 -0
- vesma_cortex-0.2.1/tests/test_features.py +228 -0
- vesma_cortex-0.2.1/tests/test_graph_features.py +286 -0
- vesma_cortex-0.2.1/tests/test_holdout.py +129 -0
- vesma_cortex-0.2.1/tests/test_n_head.py +191 -0
- vesma_cortex-0.2.1/tests/test_runner.py +235 -0
- vesma_cortex-0.2.1/tests/test_skeleton.py +309 -0
- vesma_cortex-0.2.1/tests/test_stage2_run.py +617 -0
- vesma_cortex-0.2.1/tests/test_store_export.py +805 -0
- vesma_cortex-0.2.1/tests/test_store_export_targeted.py +666 -0
- vesma_cortex-0.2.1/tests/test_synth_corpus.py +855 -0
- vesma_cortex-0.2.1/tests/test_synth_vectors.py +71 -0
- vesma_cortex-0.2.1/tests/test_watchlist.py +367 -0
- vesma_cortex-0.2.1/uv.lock +726 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Merge-gate CI (ADR 0003 R6): lint + test matrix + smoke + deterministic
|
|
2
|
+
# model invariants. All four gate jobs (lint, test, smoke, eval-sanity)
|
|
3
|
+
# must be marked required in branch protection.
|
|
4
|
+
name: ci
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main]
|
|
9
|
+
push:
|
|
10
|
+
branches: [main]
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: ci-${{ github.ref }}
|
|
18
|
+
# Cancel superseded runs on PRs only; runs on main always complete.
|
|
19
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
lint:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
timeout-minutes: 10
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v7
|
|
27
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
28
|
+
with:
|
|
29
|
+
enable-cache: true
|
|
30
|
+
- name: ruff check + ruff format --check
|
|
31
|
+
run: make lint
|
|
32
|
+
|
|
33
|
+
test:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
timeout-minutes: 30
|
|
36
|
+
strategy:
|
|
37
|
+
fail-fast: false
|
|
38
|
+
matrix:
|
|
39
|
+
python-version: ["3.12", "3.13"]
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v7
|
|
42
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
43
|
+
with:
|
|
44
|
+
python-version: ${{ matrix.python-version }}
|
|
45
|
+
enable-cache: true
|
|
46
|
+
- name: test suite (default env, no torch)
|
|
47
|
+
run: make test
|
|
48
|
+
|
|
49
|
+
smoke:
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
timeout-minutes: 15
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/checkout@v7
|
|
54
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
55
|
+
with:
|
|
56
|
+
enable-cache: true
|
|
57
|
+
- name: CPU smoke over the full D contour
|
|
58
|
+
run: make smoke
|
|
59
|
+
|
|
60
|
+
# Path filter: does this PR touch the model-relevant surface at all?
|
|
61
|
+
changes:
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
timeout-minutes: 5
|
|
64
|
+
outputs:
|
|
65
|
+
model: ${{ steps.filter.outputs.model }}
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v7
|
|
68
|
+
with:
|
|
69
|
+
fetch-depth: 0
|
|
70
|
+
- uses: dorny/paths-filter@v4
|
|
71
|
+
id: filter
|
|
72
|
+
with:
|
|
73
|
+
filters: |
|
|
74
|
+
model:
|
|
75
|
+
- 'src/**'
|
|
76
|
+
- 'models/**'
|
|
77
|
+
- 'scripts/gen_model_card.py'
|
|
78
|
+
|
|
79
|
+
# Deterministic invariants (eval-methodology #4, gate_contract.json):
|
|
80
|
+
# they block merge. Thresholds live in gate_contract.json whose sha256
|
|
81
|
+
# is pinned in eval-methodology #6 (anti-HARKing) — verified below.
|
|
82
|
+
eval-sanity:
|
|
83
|
+
needs: changes
|
|
84
|
+
if: needs.changes.outputs.model == 'true' || github.event_name == 'workflow_dispatch'
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
timeout-minutes: 15
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@v7
|
|
89
|
+
with:
|
|
90
|
+
fetch-depth: 0
|
|
91
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
92
|
+
with:
|
|
93
|
+
enable-cache: true
|
|
94
|
+
|
|
95
|
+
- name: gate_contract.json sha256 == pin in eval-methodology #6
|
|
96
|
+
shell: bash
|
|
97
|
+
run: |
|
|
98
|
+
set -euo pipefail
|
|
99
|
+
pinned=$(grep -oE '[0-9a-f]{64}' docs/specs/eval-methodology.md | head -1)
|
|
100
|
+
actual=$(sha256sum gate_contract.json | cut -d' ' -f1)
|
|
101
|
+
echo "pinned: $pinned"
|
|
102
|
+
echo "actual: $actual"
|
|
103
|
+
if [ -z "$pinned" ]; then
|
|
104
|
+
echo "::error::could not extract the gate contract pin from docs/specs/eval-methodology.md"
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
if [ "$pinned" != "$actual" ]; then
|
|
108
|
+
echo "::error::gate_contract.json sha256 diverges from the pin in docs/specs/eval-methodology.md #6 (anti-HARKing contract). Thresholds change only via a pre-registered addendum PR that updates the pin — never silently."
|
|
109
|
+
exit 1
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
- name: adversarial sanity suite on the registry bundle
|
|
113
|
+
shell: bash
|
|
114
|
+
run: |
|
|
115
|
+
set -euo pipefail
|
|
116
|
+
uv run python scripts/eval_sanity_suite.py --bundle models/vesma-cortex-v1
|
|
117
|
+
|
|
118
|
+
# Lightweight review guard: every weights revision ships with its
|
|
119
|
+
# machine-readable eval report (ADR 0003). PR-only — on push to main
|
|
120
|
+
# the same check already passed on the PR.
|
|
121
|
+
- name: model.onnx changed -> eval-results.json must change too
|
|
122
|
+
if: github.event_name == 'pull_request'
|
|
123
|
+
shell: bash
|
|
124
|
+
run: |
|
|
125
|
+
set -euo pipefail
|
|
126
|
+
base=$(git merge-base "origin/${{ github.base_ref }}" HEAD)
|
|
127
|
+
changed=$(git diff --name-only "$base" HEAD)
|
|
128
|
+
echo "$changed"
|
|
129
|
+
if echo "$changed" | grep -q '^models/vesma-cortex-v1/model\.onnx$'; then
|
|
130
|
+
if ! echo "$changed" | grep -q '^models/vesma-cortex-v1/eval-results\.json$'; then
|
|
131
|
+
echo "::error::model.onnx changed without an updated eval-results.json — every weights revision ships with its eval report (ADR 0003)."
|
|
132
|
+
exit 1
|
|
133
|
+
fi
|
|
134
|
+
fi
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Nightly deep suite (ADR 0003 R6): full tests with the train extra
|
|
2
|
+
# (CPU torch; N tests active) + the N-ladder smoke. One axis, Python 3.12.
|
|
3
|
+
name: nightly
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: "30 2 * * *" # 02:30 UTC daily
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: nightly-${{ github.ref }}
|
|
15
|
+
cancel-in-progress: true
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
nightly:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
# The CPU torch wheel is ~200 MB and the N ladder is slow — allow an hour.
|
|
21
|
+
timeout-minutes: 60
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v7
|
|
24
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
25
|
+
with:
|
|
26
|
+
python-version: "3.12"
|
|
27
|
+
enable-cache: true
|
|
28
|
+
- name: full suite with the train extra (N tests active)
|
|
29
|
+
run: make test-train
|
|
30
|
+
- name: smoke with the N ladder end-to-end
|
|
31
|
+
run: make smoke-n
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Model release train (ADR 0003 R5/R6): tag model-v1-<fp8> -> verify the
|
|
2
|
+
# bundle (digest, sanity suite, pin drift) -> GitHub Release with assets
|
|
3
|
+
# model.onnx, model.manifest.json, SHA256SUMS.txt.
|
|
4
|
+
#
|
|
5
|
+
# Freeze: no model-v* tags before 2026-10-16 (end of the B0 window) unless
|
|
6
|
+
# the tag carries an explicit -emergency suffix (ADR 0003 R3).
|
|
7
|
+
name: release-model
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
tags: ["model-v1-*"]
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
verify:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 20
|
|
20
|
+
outputs:
|
|
21
|
+
pins-changed: ${{ steps.pins.outputs.changed }}
|
|
22
|
+
env:
|
|
23
|
+
# Pinned expectations for the current production revision (B1).
|
|
24
|
+
# Bump BOTH values in the same PR that lands the retrained bundle;
|
|
25
|
+
# any other drift routes the release through manual approval
|
|
26
|
+
# (environment model-release).
|
|
27
|
+
EXPECTED_FEATURE_SET_SHA256: dd86228f8c634f28d8a15b2d8279da1b99735d68e309be02d30f1c24698fa6af
|
|
28
|
+
EXPECTED_EMBEDDER_PIN: "nano:sha256:3b752e0671a50da5c108cb50e49630a66c160f7683afedcf879e1880d84317ba"
|
|
29
|
+
steps:
|
|
30
|
+
# On a tag push this checks out the tagged commit.
|
|
31
|
+
- uses: actions/checkout@v7
|
|
32
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
33
|
+
with:
|
|
34
|
+
enable-cache: true
|
|
35
|
+
|
|
36
|
+
- name: freeze guard (B0 window, ADR 0003 R3)
|
|
37
|
+
shell: bash
|
|
38
|
+
run: |
|
|
39
|
+
set -euo pipefail
|
|
40
|
+
# epoch of 2026-10-16T00:00:00Z — end of the B0 telemetry window
|
|
41
|
+
freeze_epoch=1792108800
|
|
42
|
+
now=$(date -u +%s)
|
|
43
|
+
if [ "$now" -lt "$freeze_epoch" ] && [[ "${GITHUB_REF_NAME}" != *-emergency ]]; then
|
|
44
|
+
echo "::error::model-v* tags are FROZEN until 2026-10-16 (end of the B0 window, ADR 0003 R3)."
|
|
45
|
+
echo "::error::For a genuine field incident, re-tag with an explicit -emergency suffix (model-v1-<fp8>-emergency) and page the owner."
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
echo "freeze check: OK (now=$(date -u +%Y-%m-%dT%H:%M:%SZ))"
|
|
49
|
+
|
|
50
|
+
- name: bundle integrity — sha256(model.onnx) == manifest sha256
|
|
51
|
+
shell: bash
|
|
52
|
+
run: |
|
|
53
|
+
set -euo pipefail
|
|
54
|
+
python3 - <<'EOF'
|
|
55
|
+
import hashlib
|
|
56
|
+
import json
|
|
57
|
+
import pathlib
|
|
58
|
+
import sys
|
|
59
|
+
|
|
60
|
+
bundle = pathlib.Path("models/vesma-cortex-v1")
|
|
61
|
+
manifest = json.loads((bundle / "manifest.json").read_text())
|
|
62
|
+
actual = hashlib.sha256((bundle / "model.onnx").read_bytes()).hexdigest()
|
|
63
|
+
if manifest.get("sha256") != actual:
|
|
64
|
+
sys.exit(f"::error::digest mismatch: manifest sha256={manifest.get('sha256')} actual={actual}")
|
|
65
|
+
print(f"bundle integrity OK: {actual}")
|
|
66
|
+
EOF
|
|
67
|
+
|
|
68
|
+
- name: adversarial sanity suite on the tagged bundle
|
|
69
|
+
shell: bash
|
|
70
|
+
run: |
|
|
71
|
+
set -euo pipefail
|
|
72
|
+
uv run python scripts/eval_sanity_suite.py --bundle models/vesma-cortex-v1
|
|
73
|
+
|
|
74
|
+
# Hard fail: manifest and eval report are two views of ONE revision —
|
|
75
|
+
# they must agree. Soft route (manual approval): pins drifted from the
|
|
76
|
+
# expectations pinned in this workflow (feature_set_sha256 growth is a
|
|
77
|
+
# model-vN line event, ADR 0003 R3).
|
|
78
|
+
- name: pin consistency and drift check
|
|
79
|
+
id: pins
|
|
80
|
+
shell: bash
|
|
81
|
+
run: |
|
|
82
|
+
set -euo pipefail
|
|
83
|
+
set +e
|
|
84
|
+
python3 - <<'EOF'
|
|
85
|
+
import json
|
|
86
|
+
import os
|
|
87
|
+
import pathlib
|
|
88
|
+
import sys
|
|
89
|
+
|
|
90
|
+
bundle = pathlib.Path("models/vesma-cortex-v1")
|
|
91
|
+
manifest = json.loads((bundle / "manifest.json").read_text())
|
|
92
|
+
report = json.loads((bundle / "eval-results.json").read_text())
|
|
93
|
+
|
|
94
|
+
prov = report["provenance"]
|
|
95
|
+
if manifest["embedder_pin"] != prov.get("embedder_pin"):
|
|
96
|
+
sys.exit("::error::manifest.json and eval-results.json disagree on embedder_pin")
|
|
97
|
+
if manifest["feature_set_sha256"] != report["artifact"].get("feature_set_sha256"):
|
|
98
|
+
sys.exit("::error::manifest.json and eval-results.json disagree on feature_set_sha256")
|
|
99
|
+
|
|
100
|
+
changed = (
|
|
101
|
+
manifest["embedder_pin"] != os.environ["EXPECTED_EMBEDDER_PIN"]
|
|
102
|
+
or manifest["feature_set_sha256"] != os.environ["EXPECTED_FEATURE_SET_SHA256"]
|
|
103
|
+
)
|
|
104
|
+
sys.exit(42 if changed else 0)
|
|
105
|
+
EOF
|
|
106
|
+
rc=$?
|
|
107
|
+
set -e
|
|
108
|
+
if [ "$rc" -eq 0 ]; then
|
|
109
|
+
changed=false
|
|
110
|
+
echo "pins: consistent with pinned expectations"
|
|
111
|
+
elif [ "$rc" -eq 42 ]; then
|
|
112
|
+
changed=true
|
|
113
|
+
echo "::warning::embedder_pin/feature_set_sha256 drifted from pinned expectations — the release now requires explicit manual approval via the model-release environment."
|
|
114
|
+
else
|
|
115
|
+
exit "$rc"
|
|
116
|
+
fi
|
|
117
|
+
echo "changed=$changed" >> "$GITHUB_OUTPUT"
|
|
118
|
+
|
|
119
|
+
# Prepare everything the release job needs: staged assets (registry
|
|
120
|
+
# asset names, model-registry.md #1), SHA256SUMS over the staged names,
|
|
121
|
+
# and human-readable notes generated from eval-results.json.
|
|
122
|
+
- name: stage assets and generate release notes
|
|
123
|
+
shell: bash
|
|
124
|
+
run: |
|
|
125
|
+
set -euo pipefail
|
|
126
|
+
stage=release-stage
|
|
127
|
+
mkdir -p "$stage"
|
|
128
|
+
cp models/vesma-cortex-v1/model.onnx "$stage/model.onnx"
|
|
129
|
+
cp models/vesma-cortex-v1/manifest.json "$stage/model.manifest.json"
|
|
130
|
+
(cd "$stage" && sha256sum model.onnx model.manifest.json > SHA256SUMS.txt)
|
|
131
|
+
python3 - <<'EOF'
|
|
132
|
+
import json
|
|
133
|
+
import os
|
|
134
|
+
import pathlib
|
|
135
|
+
|
|
136
|
+
report = json.loads(pathlib.Path("models/vesma-cortex-v1/eval-results.json").read_text())
|
|
137
|
+
art = report["artifact"]
|
|
138
|
+
b = report["layers"]["B_calibration"]
|
|
139
|
+
prov = report["provenance"]
|
|
140
|
+
fp8 = art["weights_sha256"][:8]
|
|
141
|
+
repo = os.environ["GITHUB_REPOSITORY"]
|
|
142
|
+
tag = os.environ["GITHUB_REF_NAME"]
|
|
143
|
+
report_url = f"https://github.com/{repo}/blob/{tag}/{b.get('report_ref', 'docs/experiments/')}"
|
|
144
|
+
|
|
145
|
+
notes = f"""# vesma-cortex model revision `{art['revision']}` ({fp8})
|
|
146
|
+
|
|
147
|
+
- decision: **{report['decision']}**
|
|
148
|
+
- candidate: `{art['candidate']}`
|
|
149
|
+
- weights sha256: `{art['weights_sha256']}`
|
|
150
|
+
- embedder_pin: `{prov['embedder_pin']}`
|
|
151
|
+
- trained_at: {prov['trained_at']}
|
|
152
|
+
- train corpus fingerprint: `{prov['train_corpus_fingerprint']}`
|
|
153
|
+
- gate contract sha256: `{report['gate_contract_sha256']}`
|
|
154
|
+
|
|
155
|
+
## Calibration (Layer B, holdout single-shot)
|
|
156
|
+
|
|
157
|
+
| metric | value |
|
|
158
|
+
|---|---|
|
|
159
|
+
| corpus | {b.get('corpus', 'NO-DATA')} |
|
|
160
|
+
| balanced accuracy | {b.get('ba')} (baseline {b.get('baseline_ba')}) |
|
|
161
|
+
| sensitivity / specificity | {b.get('sensitivity')} / {b.get('specificity')} |
|
|
162
|
+
| Brier | {b.get('brier')} |
|
|
163
|
+
| typed output | {b.get('typed')} |
|
|
164
|
+
|
|
165
|
+
Calibration report: {report_url}
|
|
166
|
+
"""
|
|
167
|
+
pathlib.Path("release-stage/notes.md").write_text(notes)
|
|
168
|
+
EOF
|
|
169
|
+
cat release-stage/notes.md
|
|
170
|
+
cat release-stage/SHA256SUMS.txt
|
|
171
|
+
|
|
172
|
+
- uses: actions/upload-artifact@v7
|
|
173
|
+
with:
|
|
174
|
+
name: release-bundle
|
|
175
|
+
path: release-stage/
|
|
176
|
+
|
|
177
|
+
# Pins unchanged: publish straight away.
|
|
178
|
+
release:
|
|
179
|
+
needs: verify
|
|
180
|
+
if: needs.verify.outputs.pins-changed == 'false'
|
|
181
|
+
runs-on: ubuntu-latest
|
|
182
|
+
timeout-minutes: 10
|
|
183
|
+
permissions:
|
|
184
|
+
contents: write
|
|
185
|
+
steps:
|
|
186
|
+
- uses: actions/download-artifact@v8
|
|
187
|
+
with:
|
|
188
|
+
name: release-bundle
|
|
189
|
+
path: release-stage/
|
|
190
|
+
- name: create GitHub release
|
|
191
|
+
shell: bash
|
|
192
|
+
env:
|
|
193
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
194
|
+
run: |
|
|
195
|
+
set -euo pipefail
|
|
196
|
+
gh release create "${GITHUB_REF_NAME}" \
|
|
197
|
+
--title "vesma-cortex model ${GITHUB_REF_NAME}" \
|
|
198
|
+
--notes-file release-stage/notes.md \
|
|
199
|
+
release-stage/model.onnx \
|
|
200
|
+
release-stage/model.manifest.json \
|
|
201
|
+
release-stage/SHA256SUMS.txt
|
|
202
|
+
|
|
203
|
+
# Pins changed (embedder_pin or feature_set): the same release, but gated
|
|
204
|
+
# on an explicit manual approval in the model-release environment
|
|
205
|
+
# (required reviewer = owner).
|
|
206
|
+
release-reviewed:
|
|
207
|
+
needs: verify
|
|
208
|
+
if: needs.verify.outputs.pins-changed == 'true'
|
|
209
|
+
runs-on: ubuntu-latest
|
|
210
|
+
timeout-minutes: 10
|
|
211
|
+
environment: model-release
|
|
212
|
+
permissions:
|
|
213
|
+
contents: write
|
|
214
|
+
steps:
|
|
215
|
+
- uses: actions/download-artifact@v8
|
|
216
|
+
with:
|
|
217
|
+
name: release-bundle
|
|
218
|
+
path: release-stage/
|
|
219
|
+
- name: create GitHub release (manually approved)
|
|
220
|
+
shell: bash
|
|
221
|
+
env:
|
|
222
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
223
|
+
run: |
|
|
224
|
+
set -euo pipefail
|
|
225
|
+
gh release create "${GITHUB_REF_NAME}" \
|
|
226
|
+
--title "vesma-cortex model ${GITHUB_REF_NAME}" \
|
|
227
|
+
--notes-file release-stage/notes.md \
|
|
228
|
+
release-stage/model.onnx \
|
|
229
|
+
release-stage/model.manifest.json \
|
|
230
|
+
release-stage/SHA256SUMS.txt
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Package release train (ADR 0003 R5/R6): tag py-vX.Y.Z -> build (version
|
|
2
|
+
# assert, twine check, privacy check on dist archives) -> full test suite
|
|
3
|
+
# on the tag -> publish to PyPI via trusted publishing (OIDC, NO API token
|
|
4
|
+
# stored in GitHub).
|
|
5
|
+
name: release-py
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["py-v*"]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
timeout-minutes: 20
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v7
|
|
20
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
21
|
+
with:
|
|
22
|
+
enable-cache: true
|
|
23
|
+
|
|
24
|
+
- name: assert pyproject version == tag (py-vX.Y.Z)
|
|
25
|
+
shell: bash
|
|
26
|
+
run: |
|
|
27
|
+
set -euo pipefail
|
|
28
|
+
expected="${GITHUB_REF_NAME#py-v}"
|
|
29
|
+
actual=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
30
|
+
echo "tag version: $expected | pyproject version: $actual"
|
|
31
|
+
if [ "$expected" != "$actual" ]; then
|
|
32
|
+
echo "::error::tag ${GITHUB_REF_NAME} does not match pyproject.toml version ${actual} — cut the tag from a commit where both agree."
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
- name: build sdist and wheel
|
|
37
|
+
run: uv build
|
|
38
|
+
|
|
39
|
+
- name: twine check
|
|
40
|
+
run: uvx twine@7.0.0 check dist/*
|
|
41
|
+
|
|
42
|
+
# Privacy gate (charter §6, ADR 0003 R2; MR-1 lesson: hatchling's VCS
|
|
43
|
+
# pass misses root-anchored patterns). Match EXACT top-level directory
|
|
44
|
+
# segments of each dist archive: `data/` or `datasets/` at the top
|
|
45
|
+
# level must never ship; the library package `src/cortex/data/` is
|
|
46
|
+
# contract code and is allowed.
|
|
47
|
+
- name: privacy check — no training-side top-level dirs in dist
|
|
48
|
+
shell: bash
|
|
49
|
+
run: |
|
|
50
|
+
set -euo pipefail
|
|
51
|
+
python3 - <<'EOF'
|
|
52
|
+
import pathlib
|
|
53
|
+
import sys
|
|
54
|
+
import tarfile
|
|
55
|
+
import zipfile
|
|
56
|
+
|
|
57
|
+
forbidden = {"data", "datasets"}
|
|
58
|
+
failures = []
|
|
59
|
+
|
|
60
|
+
def top_dirs(names: list[str], strip_root: bool) -> set[str]:
|
|
61
|
+
tops = set()
|
|
62
|
+
for name in names:
|
|
63
|
+
parts = name.split("/")
|
|
64
|
+
if strip_root: # sdist members start with <pkg>-<ver>/
|
|
65
|
+
parts = parts[1:]
|
|
66
|
+
if len(parts) >= 2 and parts[0]:
|
|
67
|
+
tops.add(parts[0])
|
|
68
|
+
return tops
|
|
69
|
+
|
|
70
|
+
for archive in sorted(pathlib.Path("dist").iterdir()):
|
|
71
|
+
if archive.name.endswith(".tar.gz"):
|
|
72
|
+
with tarfile.open(archive) as tf:
|
|
73
|
+
tops = top_dirs(tf.getnames(), strip_root=True)
|
|
74
|
+
elif archive.name.endswith(".whl"):
|
|
75
|
+
with zipfile.ZipFile(archive) as zf:
|
|
76
|
+
tops = top_dirs(zf.namelist(), strip_root=False)
|
|
77
|
+
else:
|
|
78
|
+
continue
|
|
79
|
+
leaked = tops & forbidden
|
|
80
|
+
print(f"{archive.name}: top-level dirs = {sorted(tops)}")
|
|
81
|
+
if leaked:
|
|
82
|
+
failures.append(f"{archive.name}: leaked top-level dirs {sorted(leaked)}")
|
|
83
|
+
|
|
84
|
+
# Product requirement (0.2.0+): the wheel ships the model bundle.
|
|
85
|
+
with zipfile.ZipFile(next(pathlib.Path("dist").glob("*.whl"))) as zf:
|
|
86
|
+
names = set(zf.namelist())
|
|
87
|
+
for required in (
|
|
88
|
+
"cortex/models/vesma-cortex-v1/model.onnx",
|
|
89
|
+
"cortex/models/vesma-cortex-v1/manifest.json",
|
|
90
|
+
):
|
|
91
|
+
if required not in names:
|
|
92
|
+
failures.append(f"wheel is missing the model bundle file {required}")
|
|
93
|
+
|
|
94
|
+
if failures:
|
|
95
|
+
for failure in failures:
|
|
96
|
+
print(f"::error::{failure}")
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
print("privacy check OK: no data/ or datasets/ at dist top level; wheel carries the model bundle")
|
|
99
|
+
EOF
|
|
100
|
+
|
|
101
|
+
- uses: actions/upload-artifact@v7
|
|
102
|
+
with:
|
|
103
|
+
name: dist
|
|
104
|
+
path: dist/
|
|
105
|
+
|
|
106
|
+
# Full suite re-run on the exact tag ref before anything is published.
|
|
107
|
+
test:
|
|
108
|
+
needs: build
|
|
109
|
+
runs-on: ubuntu-latest
|
|
110
|
+
timeout-minutes: 30
|
|
111
|
+
steps:
|
|
112
|
+
- uses: actions/checkout@v7
|
|
113
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
114
|
+
with:
|
|
115
|
+
python-version: "3.12"
|
|
116
|
+
enable-cache: true
|
|
117
|
+
- run: make test
|
|
118
|
+
|
|
119
|
+
publish:
|
|
120
|
+
needs: [build, test]
|
|
121
|
+
runs-on: ubuntu-latest
|
|
122
|
+
timeout-minutes: 10
|
|
123
|
+
environment: pypi
|
|
124
|
+
permissions:
|
|
125
|
+
# Trusted publishing: short-lived OIDC token minted by GitHub, accepted
|
|
126
|
+
# by PyPI when the project's "trusted publisher" lists this repo +
|
|
127
|
+
# workflow + environment. NO PyPI API token is stored anywhere here.
|
|
128
|
+
id-token: write
|
|
129
|
+
contents: read
|
|
130
|
+
steps:
|
|
131
|
+
# Local fallback (owner-only, when trusted publishing is unavailable):
|
|
132
|
+
# from a host checkout of the tag run
|
|
133
|
+
# uv build && uvx twine check dist/*
|
|
134
|
+
# uvx twine upload --non-interactive --repository pypi dist/*
|
|
135
|
+
# authenticating via the API token kept ONLY in the host secrets store
|
|
136
|
+
# (~/.secrets). Never paste token values into this file, CI logs, or
|
|
137
|
+
# shell history.
|
|
138
|
+
- uses: actions/download-artifact@v8
|
|
139
|
+
with:
|
|
140
|
+
name: dist
|
|
141
|
+
path: dist/
|
|
142
|
+
- name: publish to PyPI (trusted publishing, OIDC)
|
|
143
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Data hygiene: no raw store content ever enters this repo (charter §6).
|
|
2
|
+
# Root-anchored on purpose: a bare "data/" pattern would also ignore
|
|
3
|
+
# src/cortex/data/ — the fingerprints/holdout CONTRACT code (A3a lesson).
|
|
4
|
+
/data/
|
|
5
|
+
*.db
|
|
6
|
+
/datasets/*
|
|
7
|
+
# Layer A frozen eval sets (eval-methodology §3; ADR 0003 R2: synthetic
|
|
8
|
+
# probe content is committable — no store lines, fingerprinted). The
|
|
9
|
+
# corpus-v4 authored batches ride the same rule (wave D4-1: synthetic,
|
|
10
|
+
# fingerprinted, disjointness-tested). The rest of datasets/ stays closed.
|
|
11
|
+
# Negations sit AFTER the blocking pattern (same idiom as models/ below).
|
|
12
|
+
!/datasets/evalsets/
|
|
13
|
+
!/datasets/evalsets/**
|
|
14
|
+
!/datasets/corpus-v4/
|
|
15
|
+
!/datasets/corpus-v4/**
|
|
16
|
+
# Near-miss watchlist registry (la4: synthetic eval-surface provenance
|
|
17
|
+
# summaries only — no store lines, same ADR 0003 R2 rule as evalsets).
|
|
18
|
+
!/datasets/watchlist/
|
|
19
|
+
!/datasets/watchlist/**
|
|
20
|
+
/artifacts/weights/
|
|
21
|
+
*.pt
|
|
22
|
+
*.onnx
|
|
23
|
+
*.safetensors
|
|
24
|
+
|
|
25
|
+
# Model registry (ADR 0003): weights of ARTIFACT revisions are tracked
|
|
26
|
+
# under models/ — the canonical artifact lives in git (HF-style, ~98 KB,
|
|
27
|
+
# no LFS needed). Training-side weights above stay ignored. Negations sit
|
|
28
|
+
# AFTER the blocking patterns to re-include the registry bundles.
|
|
29
|
+
!models/
|
|
30
|
+
!models/**
|
|
31
|
+
|
|
32
|
+
# Python
|
|
33
|
+
__pycache__/
|
|
34
|
+
*.pyc
|
|
35
|
+
.venv/
|
|
36
|
+
.pytest_cache/
|
|
37
|
+
.ruff_cache/
|
|
38
|
+
|
|
39
|
+
# Local env / scratch
|
|
40
|
+
.local/
|
|
41
|
+
.env
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/#semantic-versioning).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.1] - 2026-10-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Zoned monotonicity v2 in the evalsets gate** (PR #19, la4): the runner
|
|
15
|
+
now applies the ratified zoned rule (gate_contract v3 `monotonicity_v2`) —
|
|
16
|
+
the razor zone (0.95, 1.0) is two-valued, steps starting strictly above its
|
|
17
|
+
low edge are not asserted, outside the zone the non-increase holds.
|
|
18
|
+
Single source for the edge constant (`RAZOR_ZONE_COS_LOW` shared with the
|
|
19
|
+
sanity suite); `REPORT_SCHEMA_VERSION` 1 -> 2 (monotonicity detail
|
|
20
|
+
semantics changed).
|
|
21
|
+
- **Near-0009 watchlist module** (`cortex.data.watchlist`, PR #19, la4):
|
|
22
|
+
near-miss observation quota per the calibration-b2p prereg §9 (SCOLET) and
|
|
23
|
+
the evalsets manifest; documented in the la4 zone-rule addenda.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **sdist privacy gate pruned recursively** (PR #18): hatchling's bare
|
|
28
|
+
directory patterns (`/datasets`) match the directory entry but do not
|
|
29
|
+
prune its children — the 0.2.0 sdist leaked `datasets/` (22 files, the
|
|
30
|
+
training corpus). All four exclude patterns now carry explicit `/**`
|
|
31
|
+
forms. Root cause is the same as the earlier `data/stage2-dry` leak.
|
|
32
|
+
(ADR 0003 R2: training-side data never ship.)
|
|
33
|
+
|
|
34
|
+
### Packaging
|
|
35
|
+
|
|
36
|
+
- 0.2.0 shipped on PyPI as a **wheel-only** release: the sdist was caught by
|
|
37
|
+
the release-engineer privacy check (datasets leak above) and was never
|
|
38
|
+
published. 0.2.1 restores the full wheel + sdist pair from the clean
|
|
39
|
+
build with the recursive gate in place.
|
|
40
|
+
|
|
41
|
+
## [0.2.0] - 2026-10-05
|
|
42
|
+
|
|
43
|
+
Initial PyPI release (wheel-only; see the sdist note under 0.2.1).
|
|
44
|
+
Decision-model development library: pair features, D/N training, selection,
|
|
45
|
+
ONNX export, single-shot eval, machine bundle registry
|
|
46
|
+
(`cortex/models/vesma-cortex-v1/`).
|
|
47
|
+
|
|
48
|
+
[Unreleased]: https://github.com/vesmaro/vesma-cortex/compare/v0.2.1...HEAD
|
|
49
|
+
[0.2.1]: https://github.com/vesmaro/vesma-cortex/compare/v0.2.0...v0.2.1
|
|
50
|
+
[0.2.0]: https://github.com/vesmaro/vesma-cortex/releases/tag/v0.2.0
|