Files
chemenu/tools/chemenu/ownership.py
T
torben 686c08bb14
CI / verify (push) Successful in 57s
Release / release (push) Successful in 35s
feat: wikitool upstream merge/verify - code procedure for taking a stack update (4.5.0-beta.1, #30)
Files changed:
- AGENTS.md
- CHANGES.md
- VERSION
- instructions/gates.md
- instructions/private-instance.md
- tools/CONTRACT.md
- tools/README.md
- tools/chemenu/cli.py
- tools/chemenu/commands/dist_cmd.py
- tools/chemenu/commands/run_budget.py
- tools/chemenu/commands/upstream_cmd.py
- tools/chemenu/ownership.py
- tools/chemenu/tests/test_upstream_cmd.py
2026-09-04 07:08:49 +02:00

65 lines
3.2 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