v0.47.0 — SLICE-1: metadata sidecars — storage + dual-read + migration tool
§22.4a SLICE-1 of docs/design/2026-06-06-configurable-collection-metadata.md (§7.2). Entry metadata can live in a per-entry `<slug>.meta.yaml` sidecar with the `.md` kept as pure prose (INV-2). Additive and non-breaking — with no sidecars present every corpus stays on the legacy frontmatter path, byte-identical (N=1 unchanged). - Dual-read (app/metadata.py `read_entry`) — sidecar-else-legacy-frontmatter, identical records (INV-6); unknown/forward-compat keys ride along through parse→serialize and migration (INV-7, `Entry.extra`). A degenerate sidecar (malformed/empty/slug-less) never drops the entry — slug backstopped from the filename stem, flagged not lost (INV-3). - Migration tool (`metadata.migrate_collection`) — idempotent, one ChangeFiles commit per collection (new `gitea.change_files`). Tested as a function; its Owner-gated operator trigger is DEFERRED to SLICE-4 (write paths must become sidecar-aware first — see the design's SLICE-4 note + INV-8). No production trigger ships here, so no corpus is rewritten. - Malformed flag — migration 033 adds `cached_rfcs.metadata_malformed` (additive); the corpus mirror derives it; catalog list + entry-detail APIs surface `metadata_malformed`. - INV-7 at graduation — graduation now carries `Entry.extra` through the rebuild instead of dropping forward-compat keys. Gate: backend 575 passed (28 new: test_metadata / _migration / _cache + graduation extra-preservation). Frontend untouched. CHANGELOG 0.47.0 + upgrade-steps; VERSION + frontend/package.json -> 0.47.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -193,6 +193,40 @@ class Gitea:
|
||||
resp = await self._request("PUT", f"/repos/{owner}/{repo}/contents/{path}", json=body)
|
||||
return resp.json()
|
||||
|
||||
async def change_files(
|
||||
self,
|
||||
owner: str,
|
||||
repo: str,
|
||||
*,
|
||||
files: list[dict[str, Any]],
|
||||
message: str,
|
||||
branch: str,
|
||||
author_name: str | None = None,
|
||||
author_email: str | None = None,
|
||||
) -> dict:
|
||||
"""Create/update/delete several files in ONE commit (Gitea ChangeFiles).
|
||||
|
||||
Each `files` entry is `{"operation": "create"|"update"|"delete",
|
||||
"path": str, "content": str (for create/update), "sha": str (required
|
||||
for update/delete)}`. Plaintext `content` is base64-encoded here.
|
||||
Backs the §22.4a frontmatter→sidecar migration's "one commit per
|
||||
collection" (`metadata_migrate`).
|
||||
"""
|
||||
out_files: list[dict[str, Any]] = []
|
||||
for f in files:
|
||||
item: dict[str, Any] = {"operation": f["operation"], "path": f["path"]}
|
||||
if "content" in f and f["content"] is not None:
|
||||
item["content"] = base64.b64encode(f["content"].encode("utf-8")).decode("ascii")
|
||||
if f.get("sha"):
|
||||
item["sha"] = f["sha"]
|
||||
out_files.append(item)
|
||||
body: dict[str, Any] = {"message": message, "branch": branch, "files": out_files}
|
||||
if author_name and author_email:
|
||||
body["author"] = {"name": author_name, "email": author_email}
|
||||
body["committer"] = {"name": author_name, "email": author_email}
|
||||
resp = await self._request("POST", f"/repos/{owner}/{repo}/contents", json=body)
|
||||
return resp.json()
|
||||
|
||||
# ----- Pull requests -----
|
||||
|
||||
async def list_pulls(self, owner: str, repo: str, state: str = "open") -> list[dict]:
|
||||
|
||||
Reference in New Issue
Block a user