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.
Files changed (133) hide show
  1. vesma_cortex-0.2.1/.github/workflows/ci.yml +134 -0
  2. vesma_cortex-0.2.1/.github/workflows/nightly.yml +31 -0
  3. vesma_cortex-0.2.1/.github/workflows/release-model.yml +230 -0
  4. vesma_cortex-0.2.1/.github/workflows/release-py.yml +143 -0
  5. vesma_cortex-0.2.1/.gitignore +41 -0
  6. vesma_cortex-0.2.1/CHANGELOG.md +50 -0
  7. vesma_cortex-0.2.1/LICENSE +202 -0
  8. vesma_cortex-0.2.1/Makefile +44 -0
  9. vesma_cortex-0.2.1/PKG-INFO +126 -0
  10. vesma_cortex-0.2.1/README.md +109 -0
  11. vesma_cortex-0.2.1/artifacts/manifests/.gitkeep +0 -0
  12. vesma_cortex-0.2.1/artifacts/manifests/a2-store-corpus.md +81 -0
  13. vesma_cortex-0.2.1/artifacts/manifests/b2-targeted-export.md +115 -0
  14. vesma_cortex-0.2.1/artifacts/manifests/dataset-v4.md +183 -0
  15. vesma_cortex-0.2.1/artifacts/manifests/layer-a-evalsets.md +204 -0
  16. vesma_cortex-0.2.1/artifacts/manifests/mnema-cortex-v1-stage2.md +24 -0
  17. vesma_cortex-0.2.1/artifacts/manifests/stage1-run.md +36 -0
  18. vesma_cortex-0.2.1/artifacts/manifests/synth-stage1.md +28 -0
  19. vesma_cortex-0.2.1/artifacts/manifests/vesma-cortex-v1-ds1000.md +15 -0
  20. vesma_cortex-0.2.1/artifacts/runs/.gitkeep +0 -0
  21. vesma_cortex-0.2.1/artifacts/runs/a5-ds1000-recovery.jsonl +1 -0
  22. vesma_cortex-0.2.1/artifacts/runs/a5-ds1000-runlog.jsonl +1 -0
  23. vesma_cortex-0.2.1/artifacts/runs/b1-single-shot-runlog.jsonl +1 -0
  24. vesma_cortex-0.2.1/artifacts/runs/b2-single-shot-runlog.jsonl +1 -0
  25. vesma_cortex-0.2.1/artifacts/runs/b2p-v42-single-shot-runlog.jsonl +1 -0
  26. vesma_cortex-0.2.1/docs/REBRAND-RESIDUALS.md +39 -0
  27. vesma_cortex-0.2.1/docs/charter.md +130 -0
  28. vesma_cortex-0.2.1/docs/decisions/.gitkeep +0 -0
  29. vesma_cortex-0.2.1/docs/decisions/0001-archcom-a1-verdicts.md +309 -0
  30. vesma_cortex-0.2.1/docs/decisions/0002-duty-registry-v2.md +151 -0
  31. vesma_cortex-0.2.1/docs/decisions/0003-model-artifacts-release-and-eval.md +205 -0
  32. vesma_cortex-0.2.1/docs/decisions/0004-embed-train-routing.md +202 -0
  33. vesma_cortex-0.2.1/docs/experiments/b0-escalation.md +72 -0
  34. vesma_cortex-0.2.1/docs/experiments/b0-telemetry-plan.md +89 -0
  35. vesma_cortex-0.2.1/docs/experiments/b2p-v41-sanity-fail-diagnosis.md +70 -0
  36. vesma_cortex-0.2.1/docs/experiments/calibration-b1-domain-diversity.md +50 -0
  37. vesma_cortex-0.2.1/docs/experiments/calibration-b2-edge-neighborhood.md +106 -0
  38. vesma_cortex-0.2.1/docs/experiments/calibration-b2p-prereg.md +360 -0
  39. vesma_cortex-0.2.1/docs/experiments/calibration-b2p.md +218 -0
  40. vesma_cortex-0.2.1/docs/experiments/calibration-ds1000-a5.md +58 -0
  41. vesma_cortex-0.2.1/docs/experiments/dataset-v4-plan.md +60 -0
  42. vesma_cortex-0.2.1/docs/experiments/p0-export-desync-investigation.md +248 -0
  43. vesma_cortex-0.2.1/docs/handoff-w5d.md +96 -0
  44. vesma_cortex-0.2.1/docs/reports/vesma-cortex-model-report.md +192 -0
  45. vesma_cortex-0.2.1/docs/reports/vesma-embed-model-report.md +157 -0
  46. vesma_cortex-0.2.1/docs/roadmap-v2.md +151 -0
  47. vesma_cortex-0.2.1/docs/runbooks/operator-protocol.md +149 -0
  48. vesma_cortex-0.2.1/docs/runbooks/xpu-walkthrough.md +104 -0
  49. vesma_cortex-0.2.1/docs/specs/data-contract.md +173 -0
  50. vesma_cortex-0.2.1/docs/specs/eval-methodology.md +252 -0
  51. vesma_cortex-0.2.1/docs/specs/inference-v1.md +298 -0
  52. vesma_cortex-0.2.1/docs/specs/labeling-policy-b2.md +306 -0
  53. vesma_cortex-0.2.1/docs/specs/model-registry.md +152 -0
  54. vesma_cortex-0.2.1/docs/status.md +119 -0
  55. vesma_cortex-0.2.1/docs/synth.md +128 -0
  56. vesma_cortex-0.2.1/gate_contract.json +138 -0
  57. vesma_cortex-0.2.1/models/vesma-cortex-v1/MODEL_CARD.md +54 -0
  58. vesma_cortex-0.2.1/models/vesma-cortex-v1/eval-results.json +185 -0
  59. vesma_cortex-0.2.1/models/vesma-cortex-v1/manifest.json +28 -0
  60. vesma_cortex-0.2.1/models/vesma-cortex-v1/model.onnx +0 -0
  61. vesma_cortex-0.2.1/pyproject.toml +112 -0
  62. vesma_cortex-0.2.1/ruff.toml +9 -0
  63. vesma_cortex-0.2.1/scripts/a2_field_cosines.py +188 -0
  64. vesma_cortex-0.2.1/scripts/b1_pipeline.sh +18 -0
  65. vesma_cortex-0.2.1/scripts/b2_graph_sidecar.py +474 -0
  66. vesma_cortex-0.2.1/scripts/b2_targeted_export.py +185 -0
  67. vesma_cortex-0.2.1/scripts/build_train_manifest.py +191 -0
  68. vesma_cortex-0.2.1/scripts/eval_sanity_suite.py +95 -0
  69. vesma_cortex-0.2.1/scripts/gen_dataset_v4.py +1147 -0
  70. vesma_cortex-0.2.1/scripts/gen_evalsets.py +133 -0
  71. vesma_cortex-0.2.1/scripts/gen_evalsets_v2.py +308 -0
  72. vesma_cortex-0.2.1/scripts/gen_model_card.py +224 -0
  73. vesma_cortex-0.2.1/scripts/gen_synth_corpus.py +1134 -0
  74. vesma_cortex-0.2.1/scripts/labeling_pass2_jev.py +129 -0
  75. vesma_cortex-0.2.1/scripts/labeling_pass3_jev_native.py +131 -0
  76. vesma_cortex-0.2.1/scripts/run_layer_a.py +125 -0
  77. vesma_cortex-0.2.1/scripts/s1_synth_similarity.py +96 -0
  78. vesma_cortex-0.2.1/scripts/smoke_pipeline.py +626 -0
  79. vesma_cortex-0.2.1/scripts/stage2_run.py +1302 -0
  80. vesma_cortex-0.2.1/scripts/verify_dataset.py +273 -0
  81. vesma_cortex-0.2.1/src/cortex/__init__.py +26 -0
  82. vesma_cortex-0.2.1/src/cortex/artifacts/__init__.py +160 -0
  83. vesma_cortex-0.2.1/src/cortex/candidates/__init__.py +13 -0
  84. vesma_cortex-0.2.1/src/cortex/candidates/d_boost.py +429 -0
  85. vesma_cortex-0.2.1/src/cortex/candidates/n_head.py +447 -0
  86. vesma_cortex-0.2.1/src/cortex/cli/__init__.py +5 -0
  87. vesma_cortex-0.2.1/src/cortex/cli/main.py +940 -0
  88. vesma_cortex-0.2.1/src/cortex/data/__init__.py +21 -0
  89. vesma_cortex-0.2.1/src/cortex/data/corner_qa.py +619 -0
  90. vesma_cortex-0.2.1/src/cortex/data/field_cosines.py +169 -0
  91. vesma_cortex-0.2.1/src/cortex/data/fingerprints.py +81 -0
  92. vesma_cortex-0.2.1/src/cortex/data/holdout.py +137 -0
  93. vesma_cortex-0.2.1/src/cortex/data/store_export.py +1366 -0
  94. vesma_cortex-0.2.1/src/cortex/data/watchlist.py +411 -0
  95. vesma_cortex-0.2.1/src/cortex/eval/__init__.py +21 -0
  96. vesma_cortex-0.2.1/src/cortex/eval/runner.py +323 -0
  97. vesma_cortex-0.2.1/src/cortex/eval/sanity.py +696 -0
  98. vesma_cortex-0.2.1/src/cortex/evalsets/__init__.py +76 -0
  99. vesma_cortex-0.2.1/src/cortex/evalsets/generate.py +740 -0
  100. vesma_cortex-0.2.1/src/cortex/evalsets/runner.py +439 -0
  101. vesma_cortex-0.2.1/src/cortex/evalsets/taxonomy.py +205 -0
  102. vesma_cortex-0.2.1/src/cortex/evalsets/topics.py +256 -0
  103. vesma_cortex-0.2.1/src/cortex/features/__init__.py +34 -0
  104. vesma_cortex-0.2.1/src/cortex/features/graph.py +307 -0
  105. vesma_cortex-0.2.1/src/cortex/features/pair.py +299 -0
  106. vesma_cortex-0.2.1/src/cortex/pretrain/__init__.py +17 -0
  107. vesma_cortex-0.2.1/src/cortex/pretrain/corruption.py +356 -0
  108. vesma_cortex-0.2.1/src/cortex/select/__init__.py +19 -0
  109. vesma_cortex-0.2.1/src/cortex/select/cv.py +179 -0
  110. vesma_cortex-0.2.1/src/cortex/synth/__init__.py +96 -0
  111. vesma_cortex-0.2.1/src/cortex/synth/generate.py +1375 -0
  112. vesma_cortex-0.2.1/tests/synth.py +193 -0
  113. vesma_cortex-0.2.1/tests/test_cli.py +506 -0
  114. vesma_cortex-0.2.1/tests/test_corner_qa.py +559 -0
  115. vesma_cortex-0.2.1/tests/test_corruption.py +178 -0
  116. vesma_cortex-0.2.1/tests/test_cv.py +180 -0
  117. vesma_cortex-0.2.1/tests/test_d_boost.py +170 -0
  118. vesma_cortex-0.2.1/tests/test_dataset_v4_batch.py +321 -0
  119. vesma_cortex-0.2.1/tests/test_eval_sanity.py +590 -0
  120. vesma_cortex-0.2.1/tests/test_evalsets.py +874 -0
  121. vesma_cortex-0.2.1/tests/test_features.py +228 -0
  122. vesma_cortex-0.2.1/tests/test_graph_features.py +286 -0
  123. vesma_cortex-0.2.1/tests/test_holdout.py +129 -0
  124. vesma_cortex-0.2.1/tests/test_n_head.py +191 -0
  125. vesma_cortex-0.2.1/tests/test_runner.py +235 -0
  126. vesma_cortex-0.2.1/tests/test_skeleton.py +309 -0
  127. vesma_cortex-0.2.1/tests/test_stage2_run.py +617 -0
  128. vesma_cortex-0.2.1/tests/test_store_export.py +805 -0
  129. vesma_cortex-0.2.1/tests/test_store_export_targeted.py +666 -0
  130. vesma_cortex-0.2.1/tests/test_synth_corpus.py +855 -0
  131. vesma_cortex-0.2.1/tests/test_synth_vectors.py +71 -0
  132. vesma_cortex-0.2.1/tests/test_watchlist.py +367 -0
  133. 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