feat(entry): §22.4c unreviewed/reviewed_at/reviewed_by frontmatter fields
This commit is contained in:
@@ -50,6 +50,13 @@ class Entry:
|
|||||||
# operator credentials per §18 are used. The binding is inert until
|
# operator credentials per §18 are used. The binding is inert until
|
||||||
# the named user has a funder_consents row (the hybrid two-key rule).
|
# the named user has a funder_consents row (the hybrid two-key rule).
|
||||||
funder: str | None = None
|
funder: str | None = None
|
||||||
|
# §22.4c: an `active` entry that landed without a human review gate
|
||||||
|
# carries unreviewed=True until an owner clears it. Orthogonal to
|
||||||
|
# `state`; only meaningful for active entries. reviewed_at/reviewed_by
|
||||||
|
# are the provenance of the clear, paralleling graduated_at/by.
|
||||||
|
unreviewed: bool = False
|
||||||
|
reviewed_at: str | None = None
|
||||||
|
reviewed_by: str | None = None
|
||||||
body: str = ""
|
body: str = ""
|
||||||
|
|
||||||
|
|
||||||
@@ -66,6 +73,7 @@ def parse(text: str) -> Entry:
|
|||||||
models = [str(m) for m in raw_models]
|
models = [str(m) for m in raw_models]
|
||||||
raw_funder = fm.get("funder")
|
raw_funder = fm.get("funder")
|
||||||
funder = str(raw_funder).strip() if raw_funder else None
|
funder = str(raw_funder).strip() if raw_funder else None
|
||||||
|
unreviewed = bool(fm.get("unreviewed") or False)
|
||||||
return Entry(
|
return Entry(
|
||||||
slug=str(fm.get("slug") or ""),
|
slug=str(fm.get("slug") or ""),
|
||||||
title=str(fm.get("title") or ""),
|
title=str(fm.get("title") or ""),
|
||||||
@@ -81,6 +89,9 @@ def parse(text: str) -> Entry:
|
|||||||
tags=list(fm.get("tags") or []),
|
tags=list(fm.get("tags") or []),
|
||||||
models=models,
|
models=models,
|
||||||
funder=funder,
|
funder=funder,
|
||||||
|
unreviewed=unreviewed,
|
||||||
|
reviewed_at=fm.get("reviewed_at") or None,
|
||||||
|
reviewed_by=fm.get("reviewed_by") or None,
|
||||||
body=body,
|
body=body,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -110,6 +121,14 @@ def serialize(entry: Entry) -> str:
|
|||||||
# second meaning here as with `models:`; one set of semantics.
|
# second meaning here as with `models:`; one set of semantics.
|
||||||
if entry.funder:
|
if entry.funder:
|
||||||
fm["funder"] = entry.funder
|
fm["funder"] = entry.funder
|
||||||
|
# §22.4c: emit unreviewed only when True (a super-draft / reviewed
|
||||||
|
# active entry leaves the key absent → frontmatter stays minimal).
|
||||||
|
if entry.unreviewed:
|
||||||
|
fm["unreviewed"] = True
|
||||||
|
if entry.reviewed_at:
|
||||||
|
fm["reviewed_at"] = entry.reviewed_at
|
||||||
|
if entry.reviewed_by:
|
||||||
|
fm["reviewed_by"] = entry.reviewed_by
|
||||||
yaml_text = yaml.safe_dump(fm, sort_keys=False, default_flow_style=False).rstrip()
|
yaml_text = yaml.safe_dump(fm, sort_keys=False, default_flow_style=False).rstrip()
|
||||||
body = entry.body.lstrip("\n")
|
body = entry.body.lstrip("\n")
|
||||||
if body:
|
if body:
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
"""§22.4c — the unreviewed/reviewed_at/reviewed_by entry frontmatter fields."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from app import entry as entry_mod
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_defaults_unreviewed_false_when_absent():
|
||||||
|
text = "---\nslug: ohm\ntitle: OHM\nstate: active\n---\n\nBody.\n"
|
||||||
|
e = entry_mod.parse(text)
|
||||||
|
assert e.unreviewed is False
|
||||||
|
assert e.reviewed_at is None
|
||||||
|
assert e.reviewed_by is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_reads_review_fields():
|
||||||
|
text = (
|
||||||
|
"---\nslug: ohm\ntitle: OHM\nstate: active\n"
|
||||||
|
"unreviewed: true\nreviewed_at: '2026-06-03'\nreviewed_by: ben\n---\n\nBody.\n"
|
||||||
|
)
|
||||||
|
e = entry_mod.parse(text)
|
||||||
|
assert e.unreviewed is True
|
||||||
|
assert e.reviewed_at == "2026-06-03"
|
||||||
|
assert e.reviewed_by == "ben"
|
||||||
|
|
||||||
|
|
||||||
|
def test_serialize_emits_review_fields_only_when_meaningful():
|
||||||
|
e = entry_mod.Entry(slug="a", title="A", state="super-draft")
|
||||||
|
assert "unreviewed" not in entry_mod.serialize(e)
|
||||||
|
assert "reviewed_at" not in entry_mod.serialize(e)
|
||||||
|
e2 = entry_mod.Entry(slug="b", title="B", state="active", unreviewed=True)
|
||||||
|
assert "unreviewed: true" in entry_mod.serialize(e2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_round_trip_preserves_review_fields():
|
||||||
|
e = entry_mod.Entry(
|
||||||
|
slug="b", title="B", state="active",
|
||||||
|
unreviewed=False, reviewed_at="2026-06-03", reviewed_by="ben",
|
||||||
|
)
|
||||||
|
back = entry_mod.parse(entry_mod.serialize(e))
|
||||||
|
assert back.reviewed_at == "2026-06-03"
|
||||||
|
assert back.reviewed_by == "ben"
|
||||||
|
assert back.unreviewed is False
|
||||||
Reference in New Issue
Block a user