feat(fase6): gate-promote approved verdicts back into the OKF wiki (Steg 8)
Close the last agentic-loop seam (målbilde §3/§6/§7/§11 step 6): an APPROVED verdict is promoted from the raw output layer into the context layer (the OKF bundle) as a navigable `type: verdict` concept file, so human/persona-approved knowledge reaches the next run's hypothesis. - okf.py (pure stdlib, MAF-free): render_frontmatter / write_concept_file / link_in_index — the D7-portable OKF write counterpart of navigate. - verdicts.py: promote_verdict + PromotionRefused gate (fail-closed; only approved decisions enter the wiki, never raw agent output), provenance stamp (who/experiment/when; timestamp a required kwarg), neutral index label (signal reaches a prompt only via the gated ExpeL fold, never bundle_context), _safe_filename_token (id sanitised for path/link). - R4 = optional+gated: a public opt-in primitive, NOT wired into run_project (mirrors write_verdict — the system reads, the gate promotes). - Load-bearing trio (test_step8_promotion_loadbearing.py): gate refuses a non-approved verdict, approved verdict is navigable, promoted signal stays out of the read-context — all proven RED-on-detach. Suite 144->148. Design hardened by an adversarial plan-critic (12 findings; the BLOCKER — index-link leak into bundle_context via index_summary — closed by the neutral label + a no-leak test). Honesty limits documented: promoted file is minimal (signal as prose only), and the learning-key id means same-candidate approvals share a filename (last-write-wins). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHR8iKxJRxDiDfNw8HZmWE
This commit is contained in:
parent
e2861cac0c
commit
6b645ad32a
6 changed files with 385 additions and 2 deletions
|
|
@ -143,6 +143,44 @@ def bundle_context(bundle: Bundle) -> str:
|
|||
return "\n\n".join(s for s in sections if s.strip())
|
||||
|
||||
|
||||
def render_frontmatter(frontmatter: dict[str, str]) -> str:
|
||||
"""Render a frontmatter dict as ``key: value`` lines (the inverse direction of
|
||||
``parse_frontmatter``, used by the Step-8 promotion writer). Scalar values are **single-lined**
|
||||
(every newline/CR collapses to a space) because ``parse_frontmatter`` is line-oriented and stops
|
||||
at the first ``---`` line — a multi-line value would otherwise corrupt the block or terminate it
|
||||
early. NOT a bijection: this only guarantees that the single-line fields it writes re-parse to
|
||||
the same strings; ``parse_frontmatter`` keeps quotes and treats ``tags: [...]`` as a literal
|
||||
string, so callers pass already-formatted values. Keys are emitted in insertion order."""
|
||||
return "\n".join(f"{key}: {' '.join(str(value).split())}" for key, value in frontmatter.items())
|
||||
|
||||
|
||||
def write_concept_file(bundle_dir: str, name: str, frontmatter: dict[str, str], body: str) -> Path:
|
||||
"""Write a typed OKF concept file (``---`` frontmatter + markdown body) into ``bundle_dir``,
|
||||
path-safe via ``safe_resolve`` (fail-closed: a ``name`` escaping the bundle raises
|
||||
``PathSecurityError``). Pure stdlib — the D7-portable counterpart of ``navigate_bundle``'s read.
|
||||
Returns the written path."""
|
||||
resolved = Path(safe_resolve(bundle_dir, name))
|
||||
resolved.parent.mkdir(parents=True, exist_ok=True)
|
||||
resolved.write_text(f"---\n{render_frontmatter(frontmatter)}\n---\n\n{body}", encoding="utf-8")
|
||||
return resolved
|
||||
|
||||
|
||||
def link_in_index(bundle_dir: str, target_name: str, label: str) -> bool:
|
||||
"""Append an intra-bundle cross-link ``- [label](target_name)`` to ``index.md`` so
|
||||
``navigate_bundle`` (which follows ONLY index cross-links) reaches a newly written file.
|
||||
Idempotent: if a link to ``target_name`` already exists the index is left untouched. Returns
|
||||
whether a link was added. ``label`` is supplied by the caller and ends up in ``index_summary``
|
||||
(hence ``bundle_context``) verbatim, so the promotion policy passes a NEUTRAL label carrying no
|
||||
verdict signal (målbilde §3/§6). Known MVP limitation: the read-modify-write is not atomic."""
|
||||
resolved = Path(safe_resolve(bundle_dir, _INDEX_NAME))
|
||||
body = resolved.read_text(encoding="utf-8")
|
||||
if f"]({target_name})" in body:
|
||||
return False
|
||||
prefix = body if body.endswith("\n") else body + "\n"
|
||||
resolved.write_text(f"{prefix}- [{label}]({target_name})\n", encoding="utf-8")
|
||||
return True
|
||||
|
||||
|
||||
def load_ir_projection(bundle_dir: str, name: str = _IR_PROJECTION) -> dict[str, Any]:
|
||||
"""Load the bundle's IR projection (``validator-input.json`` by default): the candidate
|
||||
measure's cost-IR (``measure``, ``affected_items``, ``claimed_saving_nok``) — the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue