policies.py python
264 lines 9.8 KB
Raw
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 32 days ago
1 """Knowtation Harmony merge policies — the six-row default policy table.
2
3 A *Harmony policy* is a named, semantically-motivated merge rule that maps a
4 (path_glob, dimension, strategy) triple onto a priority. Six default policies
5 cover the common Knowtation vault layouts without requiring the user to author
6 custom ``.museattributes`` rules from scratch.
7
8 Policy table (in evaluation order, highest priority first)
9 ----------------------------------------------------------
10
11 +---------+----------------------+-------------+------------------+----------+
12 | Name | Path glob | Dimension | Strategy | Priority |
13 +=========+======================+=============+==================+==========+
14 | archive | ``archive/**`` | ``*`` | ``ours`` | 50 |
15 +---------+----------------------+-------------+------------------+----------+
16 | inbox | ``inbox/**`` | ``*`` | ``prefer-newer- | 40 |
17 | | | | date`` | |
18 +---------+----------------------+-------------+------------------+----------+
19 | capture | ``captures/**`` | ``*`` | ``prefer-newer- | 35 |
20 | | | | date`` | |
21 +---------+----------------------+-------------+------------------+----------+
22 | tags | ``**/*.md`` | ``frontmat- | ``union-sorted`` | 30 |
23 | | | ter`` | | |
24 +---------+----------------------+-------------+------------------+----------+
25 | project | ``projects/**`` | ``*`` | ``knowtation- | 20 |
26 | | | | 3way`` | |
27 +---------+----------------------+-------------+------------------+----------+
28 | default | ``*`` | ``*`` | ``auto`` | 0 |
29 +---------+----------------------+-------------+------------------+----------+
30
31 Rationale
32 ---------
33
34 ``archive`` (priority 50)
35 Archived notes are immutable. ``ours`` prevents any remote change from
36 re-opening resolved content.
37
38 ``inbox`` (priority 40)
39 Captured notes are time-ordered. ``prefer-newer-date`` resolves conflicts
40 by retaining the most recent capture without losing earlier timestamps.
41
42 ``capture`` (priority 35)
43 Import-pipeline output in ``captures/`` follows the same logic as inbox.
44 Lower priority than ``inbox`` so path-specific inbox rules take precedence.
45
46 ``tags`` (priority 30)
47 Frontmatter tag/entity/attachment sets are independent across branches.
48 ``union-sorted`` accumulates additions deterministically. Applies to the
49 ``frontmatter`` dimension only; body/section conflicts escalate to
50 ``knowtation-3way`` via the ``project`` rule.
51
52 ``project`` (priority 20)
53 Project notes are collaborative. ``knowtation-3way`` applies the full
54 four-layer structured merge; falls back to ``auto`` until Phase 2.4 lands.
55
56 ``default`` (priority 0)
57 Catch-all. Defers all unmatched paths to the engine.
58
59 Public API
60 ----------
61 - :data:`HARMONY_POLICIES` — ordered list of :class:`HarmonyPolicy` objects.
62 - :func:`to_attribute_rules` — convert all policies to sorted ``AttributeRule`` list.
63 - :func:`write_harmony_museattributes` — write the policy table to ``.museattributes``.
64 - :func:`resolve_harmony_strategy` — look up the strategy for a (path, dimension).
65 """
66
67 from __future__ import annotations
68
69 import pathlib
70 from dataclasses import dataclass, field
71
72 from muse.core.attributes import AttributeRule, resolve_strategy
73
74
75 # ---------------------------------------------------------------------------
76 # HarmonyPolicy dataclass
77 # ---------------------------------------------------------------------------
78
79
80 @dataclass(frozen=True)
81 class HarmonyPolicy:
82 """A single Harmony merge policy.
83
84 Attributes:
85 name: Short identifier used in documentation and audit logs.
86 path_pattern: ``fnmatch`` glob matched against vault-relative paths.
87 dimension: Knowtation dimension name (e.g. ``"frontmatter"``) or
88 ``"*"`` to match any dimension.
89 strategy: Resolution strategy string (must be in
90 :data:`muse.core.attributes.VALID_STRATEGIES`).
91 priority: Evaluation order weight; higher-priority policies are
92 tested before lower-priority ones.
93 comment: Human-readable rationale written to ``.museattributes``.
94 """
95
96 name: str
97 path_pattern: str
98 dimension: str
99 strategy: str
100 priority: int
101 comment: str = field(default="")
102
103
104 # ---------------------------------------------------------------------------
105 # The six canonical Harmony policies
106 # ---------------------------------------------------------------------------
107
108 HARMONY_POLICIES: tuple[HarmonyPolicy, ...] = (
109 HarmonyPolicy(
110 name="archive",
111 path_pattern="archive/**",
112 dimension="*",
113 strategy="ours",
114 priority=50,
115 comment=(
116 "Archived notes are immutable. "
117 "Prefer the local version and discard any remote change."
118 ),
119 ),
120 HarmonyPolicy(
121 name="inbox",
122 path_pattern="inbox/**",
123 dimension="*",
124 strategy="prefer-newer-date",
125 priority=40,
126 comment=(
127 "Inbox captures are time-ordered. "
128 "Keep the note with the later 'date' frontmatter field."
129 ),
130 ),
131 HarmonyPolicy(
132 name="capture",
133 path_pattern="captures/**",
134 dimension="*",
135 strategy="prefer-newer-date",
136 priority=35,
137 comment=(
138 "Import-pipeline output in captures/ follows the same rule as inbox: "
139 "the note with the later date wins."
140 ),
141 ),
142 HarmonyPolicy(
143 name="tags",
144 path_pattern="**/*.md",
145 dimension="frontmatter",
146 strategy="union-sorted",
147 priority=30,
148 comment=(
149 "Frontmatter tag, entity, and attachment sets are independent across "
150 "branches. Accumulate additions and sort alphabetically."
151 ),
152 ),
153 HarmonyPolicy(
154 name="project",
155 path_pattern="projects/**",
156 dimension="*",
157 strategy="knowtation-3way",
158 priority=20,
159 comment=(
160 "Project notes are collaborative. "
161 "Apply the full Knowtation four-layer structured merge."
162 ),
163 ),
164 HarmonyPolicy(
165 name="default",
166 path_pattern="*",
167 dimension="*",
168 strategy="auto",
169 priority=0,
170 comment="Catch-all: defer unmatched paths to the engine's three-way algorithm.",
171 ),
172 )
173
174
175 # ---------------------------------------------------------------------------
176 # Conversion helpers
177 # ---------------------------------------------------------------------------
178
179
180 def to_attribute_rules() -> list[AttributeRule]:
181 """Convert all Harmony policies to a priority-sorted ``AttributeRule`` list.
182
183 The returned list is sorted by priority (descending), then by declaration
184 order (ascending), matching the contract of
185 :func:`~muse.core.attributes.load_attributes_full`.
186
187 Returns:
188 List of :class:`~muse.core.attributes.AttributeRule` objects.
189 """
190 rules: list[AttributeRule] = []
191 for idx, policy in enumerate(HARMONY_POLICIES):
192 rules.append(
193 AttributeRule(
194 path_pattern=policy.path_pattern,
195 dimension=policy.dimension,
196 strategy=policy.strategy,
197 comment=policy.comment,
198 priority=policy.priority,
199 source_index=idx,
200 )
201 )
202 rules.sort(key=lambda r: -r.priority)
203 return rules
204
205
206 def resolve_harmony_strategy(path: str, dimension: str = "*") -> str:
207 """Return the first matching Harmony strategy for *path* and *dimension*.
208
209 A convenience wrapper over :func:`~muse.core.attributes.resolve_strategy`
210 using the pre-built Harmony rule list. Returns ``"auto"`` when no rule
211 matches (the ``default`` policy ensures this never happens in practice).
212
213 Args:
214 path: Vault-relative POSIX path (e.g. ``"inbox/note.md"``).
215 dimension: Dimension name (e.g. ``"frontmatter"``), or ``"*"``.
216
217 Returns:
218 Strategy string.
219 """
220 return resolve_strategy(to_attribute_rules(), path, dimension)
221
222
223 def write_harmony_museattributes(root: pathlib.Path, *, overwrite: bool = False) -> None:
224 """Write the six Harmony policies to ``.museattributes`` in *root*.
225
226 The generated file is valid TOML and can be edited by the user after
227 generation. An existing file is not overwritten unless ``overwrite=True``.
228
229 Args:
230 root: Repository root directory.
231 overwrite: When ``False`` (default), raise ``FileExistsError`` if
232 ``.museattributes`` already exists.
233
234 Raises:
235 FileExistsError: When the file exists and ``overwrite=False``.
236 """
237 dest = root / ".museattributes"
238 if dest.exists() and not overwrite:
239 raise FileExistsError(
240 f"{dest} already exists. Pass overwrite=True to replace it."
241 )
242
243 lines: list[str] = [
244 "# .museattributes — Knowtation Harmony merge policies.",
245 "# Generated by muse.plugins.knowtation.policies.",
246 "# Edit freely; these rules are the starting point, not a constraint.",
247 "",
248 "[meta]",
249 'domain = "knowtation"',
250 "",
251 ]
252
253 for policy in HARMONY_POLICIES:
254 lines.append(f"# Policy: {policy.name} (priority {policy.priority})")
255 lines.append("[[rules]]")
256 lines.append(f'path = "{policy.path_pattern}"')
257 lines.append(f'dimension = "{policy.dimension}"')
258 lines.append(f'strategy = "{policy.strategy}"')
259 if policy.comment:
260 lines.append(f'comment = "{policy.comment}"')
261 lines.append(f"priority = {policy.priority}")
262 lines.append("")
263
264 dest.write_text("\n".join(lines), encoding="utf-8")
File History 2 commits
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 32 days ago
sha256:d8316ffae901be06347e16ab55be11868eb519dd16ade3e8aa16a99e662f7e62 baseline: rc14 re-baseline after rc3 store corruption recovery Human patch 32 days ago