1
0
forked from jason/echo
This commit is contained in:
jason
2026-06-22 09:27:36 -05:00
parent d404f6e96f
commit 1c0c2ea66e
34 changed files with 2530 additions and 149 deletions
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env python3
"""mock_olrapi_hifi.py — H4: higher-fidelity mock of the Obsidian Local REST API.
The shipped mock's PATCH is a "naive append": it appends the body to EOF instead of
inserting/replacing under the target heading, so the 0.9 surface (capture's
_append_to_existing, scope set's replace, the daily Agent-Log insert) is only ever
validated against behavior the real API does NOT exhibit. This subclass models the
real PATCH semantics so the eval can catch heading-insert / replace regressions.
Over the base mock (eval/mock_olrapi.py) this overrides do_PATCH only:
* heading append -> insert at the END of the target section's body (before the next
sibling/parent heading), NOT at EOF.
* heading prepend -> insert at the START of the section body (right after the heading).
* heading replace -> replace the section body, keep the heading line.
* heading missing -> 400 errorCode 40080 (the silent-write-loss trigger). [unchanged]
* frontmatter replace -> parse the YAML block and set/replace the scalar; a replace on a
MISSING field returns 400 40080 (matches the real API, per SKILL.md).
Everything else (GET/PUT/POST/DELETE, directory listing, document-map, flaky/phantom
fault injection, debug reset) is inherited unchanged from the base handler.
"""
from __future__ import annotations
import argparse
import json
import re
from http.server import ThreadingHTTPServer
import mock_olrapi
from mock_olrapi import H as BaseH, STATE
def _heading(line: str):
m = re.match(r"^(#{1,6})\s+(.*)$", line)
return (len(m.group(1)), m.group(2).strip()) if m else (0, None)
def section_span(lines, target):
"""(start, end, level) for the section under `target` (an "A::B" path; matched on its
leaf). Body lines are lines[start+1:end]. None if the heading is absent."""
leaf = target.split("::")[-1].strip()
start = level = None
for i, line in enumerate(lines):
lv, txt = _heading(line)
if lv and txt == leaf:
start, level = i, lv
break
if start is None:
return None
end = len(lines)
for j in range(start + 1, len(lines)):
lv, _ = _heading(lines[j])
if lv and lv <= level:
end = j
break
return start, end, level
def set_frontmatter(text: str, field: str, raw_value: str):
"""Return (new_text, existed). raw_value is the JSON the client PATCHed."""
try:
value = json.loads(raw_value)
except Exception:
value = raw_value.strip().strip('"')
rendered = value if isinstance(value, str) else json.dumps(value)
new_line = f"{field}: {rendered}"
if not text.startswith("---"):
return f"---\n{new_line}\n---\n\n{text}", False
end = text.find("\n---", 3)
head = text[4:end] if end != -1 else text
rest = text[end:] if end != -1 else "\n---\n"
out, existed = [], False
for line in head.splitlines():
if re.match(rf"^{re.escape(field)}\s*:", line):
out.append(new_line)
existed = True
else:
out.append(line)
if not existed:
out.append(new_line)
return "---\n" + "\n".join(out) + rest, existed
class H(BaseH):
def do_PATCH(self):
vp = self._vpath()
body = self._read_body()
if vp is None:
return self._json(400, {"message": "bad path"})
ttype = self.headers.get("Target-Type", "")
op = self.headers.get("Operation", "append")
target = self.headers.get("Target", "")
cur = STATE.get(vp, "")
if ttype == "heading":
lines = cur.split("\n")
span = section_span(lines, target)
if span is None:
return self._json(400, {"errorCode": 40080, "message": "invalid-target"})
start, end, _ = span
payload = body.strip("\n").split("\n") if body.strip("\n") else [""]
if op == "replace":
lines[start + 1:end] = payload
elif op == "prepend":
lines[start + 1:start + 1] = payload
else: # append at the tail of the section body (before the next heading)
ins = end
while ins > start + 1 and lines[ins - 1].strip() == "":
ins -= 1
lines[ins:ins] = payload
STATE[vp] = "\n".join(lines)
return self._send(200, "")
if ttype == "frontmatter":
new, existed = set_frontmatter(cur, target, body)
if op == "replace" and not existed:
return self._json(400, {"errorCode": 40080, "message": "invalid-target"})
STATE[vp] = new
return self._send(200, "")
STATE[vp] = cur + "\n" + body
return self._send(200, "")
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--port", type=int, default=8800)
a = ap.parse_args()
srv = ThreadingHTTPServer(("127.0.0.1", a.port), H)
print(f"mock-olrapi-hifi listening on http://127.0.0.1:{a.port}", flush=True)
srv.serve_forever()
if __name__ == "__main__":
main()