mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
release: web/package-lock.json is a version carrier
The ESLint layer added an npm lockfile that repeats the web package version at its root (the root object and packages[""]); npm ci tolerates a drift there, the P9 "carriers in sync" contract does not. The lockfile joins VERSION_CARRIER_SPANS, sync_release_metadata rewrites both root entries and version_carrier_desyncs checks them; dependency versions inside the lockfile are untouched. ARCHITECTURE and DEVELOPMENT name the new carrier. Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
This commit is contained in:
parent
83f64a2d85
commit
841cf121c9
4 changed files with 58 additions and 2 deletions
|
|
@ -1601,7 +1601,7 @@ Bounded foreground `run_command`/`run_script` processes ride the in-memory `_act
|
|||
## 10. Key Invariants
|
||||
|
||||
1. **Constitution and identity persist.** `BIBLE.md` is never deleted; `identity.md` remains a physical file even when its content evolves.
|
||||
2. **Release metadata has one projection.** `VERSION` is canonical; `ouroboros/tools/release_sync.py::version_carrier_desyncs()` and `sync_release_metadata()` keep the PEP 440 form in `pyproject.toml` and the editable root entry in `uv.lock`, plus the author-facing version in `web/package.json`, `web/modules/api_types.js::GATEWAY_CONTRACT_VERSION`, the README badge, and this document's header. Changelog prose remains deliberate. Pull requests into `ouroboros` leave these carriers byte-identical to their target; integration assigns the release version.
|
||||
2. **Release metadata has one projection.** `VERSION` is canonical; `ouroboros/tools/release_sync.py::version_carrier_desyncs()` and `sync_release_metadata()` keep the PEP 440 form in `pyproject.toml` and the editable root entry in `uv.lock`, plus the author-facing version in `web/package.json` and both root entries of `web/package-lock.json`, `web/modules/api_types.js::GATEWAY_CONTRACT_VERSION`, the README badge, and this document's header. Changelog prose remains deliberate. Pull requests into `ouroboros` leave these carriers byte-identical to their target; integration assigns the release version.
|
||||
3. **Configuration and messaging have single owners.** `ouroboros/config.py` is the one IMPORT surface for paths and settings, and the vocabularies live in its leaves — `settings_defaults` (keys, shipped defaults and the retired-key lists), `settings_scales` (the closed clamps), `model_slots` (slot resolution and the frozen model target), `review_model_routes` (the API-pinned reviewer lists), `runtime_limits` (numeric knobs and their clamps) and `settings_integrity` (the verified read primitive) — so a new key belongs to a leaf, never to the facade; messages go through `supervisor/message_bus.py`; concurrent state transitions use the owning file lock.
|
||||
4. **The attempt ledger is monetary authority.** `state/usage_attempts.jsonl` records every physical model send. State, task, event, and UI totals are projections carrying attempt identity; unknown or unresolved cost never becomes false zero.
|
||||
5. **Packaged bootstrap is manifest-bound.** A packaged install verifies `repo.bundle` and its manifest once, then runs the managed checkout. Restart preserves its local tip; only explicit update applies an approved exact SHA.
|
||||
|
|
|
|||
|
|
@ -1043,7 +1043,7 @@ preserve authorship, and run the normal final exact-candidate gate.
|
|||
|
||||
A pull request into `ouroboros` leaves every version carrier byte-identical to
|
||||
its target: `VERSION`, `pyproject.toml`, the editable root version in
|
||||
`uv.lock`, `web/package.json`,
|
||||
`uv.lock`, `web/package.json`, `web/package-lock.json` (both root entries),
|
||||
`web/modules/api_types.js::GATEWAY_CONTRACT_VERSION`, the README badge and
|
||||
latest Version History row, the named direct-download links in README and both
|
||||
install pages, and the Architecture header. At integration,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,14 @@ _ARCH_HEADER_RE = re.compile(
|
|||
re.MULTILINE | re.IGNORECASE,
|
||||
)
|
||||
|
||||
# web/package-lock.json (npm lockfileVersion 3) repeats the package version twice at the
|
||||
# top: the root object and its packages[""] entry; both are carriers (npm ci tolerates a
|
||||
# drift, the P9 "carriers in sync" contract does not).
|
||||
_WEB_LOCK_VERSION_RE = re.compile(
|
||||
r'(^\{\s*"name"\s*:\s*"[^"\n]*",\s*"version"\s*:\s*")([^"\n]*)(")'
|
||||
r'|(^\s*""\s*:\s*\{\s*"name"\s*:\s*"[^"\n]*",\s*"version"\s*:\s*")([^"\n]*)(")',
|
||||
re.MULTILINE,
|
||||
)
|
||||
_UV_LOCK_ROOT_RE = re.compile(
|
||||
r'^(\[\[package\]\]\nname = "ouroboros"\nversion = ")([^"]+)'
|
||||
r'("\nsource = \{ editable = "\." \})',
|
||||
|
|
@ -166,6 +174,9 @@ VERSION_CARRIER_SPANS: Tuple[VersionCarrierSpan, ...] = (
|
|||
"web_package_version", "web/package.json",
|
||||
re.compile(r'^\s*"version"\s*:\s*"[^"\n]*"', re.MULTILINE),
|
||||
),
|
||||
VersionCarrierSpan(
|
||||
"web_package_lock_version", "web/package-lock.json", _WEB_LOCK_VERSION_RE,
|
||||
),
|
||||
VersionCarrierSpan(
|
||||
"gateway_contract_version", "web/modules/api_types.js",
|
||||
re.compile(r"GATEWAY_CONTRACT_VERSION\s*=\s*'[^'\n]*'"),
|
||||
|
|
@ -426,6 +437,7 @@ def version_carrier_desyncs(
|
|||
pyproject_text: str = "",
|
||||
uv_lock_text: str = "",
|
||||
web_package_text: str = "",
|
||||
web_package_lock_text: str = "",
|
||||
readme_text: str = "",
|
||||
arch_text: str = "",
|
||||
api_types_text: str = "",
|
||||
|
|
@ -453,6 +465,11 @@ def version_carrier_desyncs(
|
|||
match = re.search(r'"version"\s*:\s*"([^"]+)"', web_package_text)
|
||||
if not match or match.group(1).strip() != version:
|
||||
desync.append(f'web/package.json (expected "version": "{version}")' if detailed else "web/package.json")
|
||||
if web_package_lock_text:
|
||||
found = [m.group(2) or m.group(5) for m in _WEB_LOCK_VERSION_RE.finditer(web_package_lock_text)]
|
||||
if len(found) != 2 or any(v.strip() != version for v in found):
|
||||
desync.append(f'web/package-lock.json (expected both root "version" entries = "{version}")'
|
||||
if detailed else "web/package-lock.json")
|
||||
if readme_text:
|
||||
badge_token = f"version-{_shields_escape(version)}-green"
|
||||
if extract_readme_badge_version(readme_text) != version or badge_token not in readme_text:
|
||||
|
|
@ -502,6 +519,7 @@ def check_worktree_version_sync(repo_dir) -> str:
|
|||
pyproject_text=_read("pyproject.toml"),
|
||||
uv_lock_text=_read("uv.lock"),
|
||||
web_package_text=_read("web/package.json"),
|
||||
web_package_lock_text=_read("web/package-lock.json"),
|
||||
readme_text=_read("README.md"),
|
||||
arch_text=_read("docs/ARCHITECTURE.md"),
|
||||
api_types_text=_read("web/modules/api_types.js"),
|
||||
|
|
@ -571,6 +589,18 @@ def sync_release_metadata(repo_dir: str) -> List[str]:
|
|||
web_package.write_text(new_text, encoding="utf-8")
|
||||
changed.append("web/package.json")
|
||||
|
||||
web_lock = root / "web" / "package-lock.json"
|
||||
if web_lock.exists():
|
||||
text = web_lock.read_text(encoding="utf-8")
|
||||
new_text = _WEB_LOCK_VERSION_RE.sub(
|
||||
lambda m: (f"{m.group(1)}{version}{m.group(3)}" if m.group(1) is not None
|
||||
else f"{m.group(4)}{version}{m.group(6)}"),
|
||||
text,
|
||||
)
|
||||
if new_text != text:
|
||||
web_lock.write_text(new_text, encoding="utf-8")
|
||||
changed.append("web/package-lock.json")
|
||||
|
||||
api_types = root / "web" / "modules" / "api_types.js"
|
||||
if api_types.exists():
|
||||
text = api_types.read_text(encoding="utf-8")
|
||||
|
|
|
|||
|
|
@ -63,6 +63,13 @@ def _make_repo(tmp_path: Path, version: str = "4.99.1") -> Path:
|
|||
'{\n "name": "ouroboros-web",\n "version": "0.0.0"\n}\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
(web / "package-lock.json").write_text(
|
||||
'{\n "name": "ouroboros-web",\n "version": "0.0.0",\n "lockfileVersion": 3,\n'
|
||||
' "packages": {\n "": {\n "name": "ouroboros-web",\n "version": "0.0.0",\n'
|
||||
' "devDependencies": {"eslint": "10.10.0"}\n },\n "node_modules/eslint": {\n'
|
||||
' "version": "10.10.0"\n }\n }\n}\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
modules = web / "modules"
|
||||
modules.mkdir()
|
||||
(modules / "api_types.js").write_text(
|
||||
|
|
@ -160,6 +167,25 @@ class TestSyncReleaseMetadata:
|
|||
text = (repo / "web" / "package.json").read_text()
|
||||
assert '"version": "1.2.3-rc.4"' in text
|
||||
|
||||
def test_syncs_web_package_lock_root_versions_only(self, tmp_path):
|
||||
repo = _make_repo(tmp_path, "1.2.3-rc.4")
|
||||
changed = sync_release_metadata(str(repo))
|
||||
assert "web/package-lock.json" in changed
|
||||
text = (repo / "web" / "package-lock.json").read_text(encoding="utf-8")
|
||||
assert text.count('"version": "1.2.3-rc.4"') == 2 and '"version": "10.10.0"' in text
|
||||
|
||||
def test_web_package_lock_participates_in_desync_checks(self, tmp_path):
|
||||
repo = _make_repo(tmp_path, "1.2.3-rc.4")
|
||||
sync_release_metadata(str(repo))
|
||||
lock = repo / "web" / "package-lock.json"
|
||||
lock.write_text(lock.read_text(encoding="utf-8").replace('"version": "1.2.3-rc.4"', '"version": "1.2.3-rc.3"', 1),
|
||||
encoding="utf-8")
|
||||
desync = version_carrier_desyncs(
|
||||
"1.2.3-rc.4", detailed=True,
|
||||
web_package_lock_text=lock.read_text(encoding="utf-8"),
|
||||
)
|
||||
assert desync == ['web/package-lock.json (expected both root "version" entries = "1.2.3-rc.4")']
|
||||
|
||||
def test_web_package_json_participates_in_desync_checks(self, tmp_path):
|
||||
repo = _make_repo(tmp_path, "1.2.3-rc.4")
|
||||
sync_release_metadata(str(repo))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue