cd81ba3d4f
Files changed: - CHANGES.md - INSTALL.md - VERSION - tools/CONTRACT.md - tools/chemenu/commands/dist_cmd.py - tools/chemenu/kb_state.py - tools/chemenu/ownership.py - tools/chemenu/tests/test_dist_upgrade.py
88 lines
4.3 KiB
Python
88 lines
4.3 KiB
Python
"""The ownership boundary for a path under a content stage: does it belong to
|
|
the *stack* (ships with every distribution, wins over local content when a
|
|
private instance merges from a public upstream) or to the *instance* (never
|
|
ships filled, wins over the upstream's version)?
|
|
|
|
One predicate, so `dist_cmd.py` (export) and `upstream_cmd.py` (merge/verify)
|
|
answer the same question about the same paths instead of each keeping its own
|
|
literal list that can drift out of sync with the other - see AGENTS.md
|
|
invariant 8, and Gitea #30 for the incident that made the drift concrete
|
|
(the private-instance merge procedure hardcoded a three-path list that
|
|
`dist_cmd.py` had already outgrown).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
# The stages whose content belongs to *this instance*, not the stack. Mirrors
|
|
# the sentence .gitignore already makes about raw/, kb/ and work/ being the
|
|
# repo's content, plus reports/ - only reports/CONTRACT.md is tracked there,
|
|
# the rest is gitignored, so restoring it is a no-op today. It stays in the
|
|
# set anyway: a set that is "almost" this one is the beginning of the same
|
|
# drift this module exists to end.
|
|
CONTENT_STAGES = ("kb", "raw", "work", "reports")
|
|
|
|
# Bare filenames `dist export` overwrites with a fresh stub rather than
|
|
# shipping the stack's own copy. Not stack-owned: an upstream merge takes the
|
|
# *local* side for these (they are the instance's own log/placeholder),
|
|
# while `dist export` writes a brand-new one regardless of either side.
|
|
EXPORT_STUB_NAMES = ("log.md", ".gitkeep")
|
|
|
|
# The single machinery filename directly under a content stage's own root.
|
|
_STAGE_CONTRACT_NAME = "CONTRACT.md"
|
|
|
|
|
|
def is_stack_owned(relative: str) -> bool:
|
|
"""Whether `relative` - a path under a content stage, e.g. "kb/CONTRACT.md"
|
|
or "kb/entities/COLLECTION.md.template" - is machinery: it ships with
|
|
every distribution, and it is the side an upstream merge keeps.
|
|
|
|
True for exactly two shapes:
|
|
|
|
- `<stage>/CONTRACT.md`, directly under a content stage's own root. Not
|
|
recursive: `kb/<collection>/COLLECTION.md` sits one level deeper and is
|
|
instance-owned (see kb/CONTRACT.md's collection-ownership split).
|
|
- Any path under a content stage ending in `.template` - by construction
|
|
the stack's own copy of something the instance adopts by renaming
|
|
(`kb/CONVENTIONS.md.template` and every `kb/<name>/COLLECTION.md.template`
|
|
today; a future stack-owned template under a content stage falls under
|
|
this rule automatically, with no code change here).
|
|
|
|
False for everything else under a content stage, `EXPORT_STUB_NAMES`
|
|
included - those are handled separately by whichever caller cares about
|
|
them, because the two callers disagree about which side wins for a stub.
|
|
"""
|
|
parts = relative.split("/")
|
|
if len(parts) < 2 or parts[0] not in CONTENT_STAGES:
|
|
return False
|
|
if relative.endswith(".template"):
|
|
return True
|
|
return len(parts) == 2 and parts[1] == _STAGE_CONTRACT_NAME
|
|
|
|
|
|
def is_export_stub(name: str) -> bool:
|
|
"""Whether `name` (a bare filename, not a path) is one `dist export`
|
|
overwrites with a fresh stub of its own rather than shipping verbatim."""
|
|
return name in EXPORT_STUB_NAMES
|
|
|
|
|
|
# Root-relative paths `dist export` seeds once, from a template it owns, and
|
|
# which the instance owns exclusively from that point on. `dist upgrade`
|
|
# (Gitea #7) must never overwrite them, even though they sit in the release
|
|
# stamp's `files` block like any other planned file - the same shape as
|
|
# `EXPORT_STUB_NAMES` above, but keyed by full path rather than bare filename,
|
|
# since nothing else at the repo root gets this treatment and a bare-filename
|
|
# match would be too broad here.
|
|
#
|
|
# `.wikitool-kb.json` is `migrate done`'s state file: an upgrade that resets it
|
|
# declares a content shape nobody actually produced. `CHANGES.md` is the
|
|
# instance's own changelog, not the stack's - `dist export` seeds it from a
|
|
# blank template (`dist_templates/CHANGES.md`) the same way it seeds
|
|
# `kb/log.md`, and overwriting it on upgrade would erase every entry the
|
|
# instance ever wrote for itself.
|
|
UPGRADE_PRESERVED_PATHS = (".wikitool-kb.json", "CHANGES.md")
|
|
|
|
|
|
def is_upgrade_preserved(relative: str) -> bool:
|
|
"""Whether `relative` (a plan-relative path from the repo root, e.g.
|
|
"CHANGES.md") is one `dist upgrade` must never write."""
|
|
return relative in UPGRADE_PRESERVED_PATHS
|