Record the project membership diagnosis and repair requirements

Why: a saved host-project membership has no registered RDM project or binding,
so member discovery and private conversation creation cannot resolve it.

Evidence: metadata-only read probes and forge scans are preserved as SELF
receipts in the task card; HTTP and browser replay remain NOT_RUN.

Impact: task documentation and read-only diagnostic helpers only; product
code, runtime databases and deployments are unchanged pending stage review.

Refs: .task/feature-project-member-access.md; policy/delivery.md
This commit is contained in:
2026-10-13 07:42:41 +00:00
parent 71b226438c
commit 80c9674dbb
3 changed files with 443 additions and 22 deletions
@@ -0,0 +1,32 @@
"""Read forge metadata without emitting token values or PR bodies."""
import json
from pathlib import Path
import re
import urllib.request
base = "https://git-home.u1680832.nyat.app:30319"
secret = (Path.home() / ".secrets/gitea").read_text()
match = re.search(r"(?m)^[ -]*ACCESS_TOKEN=(\S+)\s*$", secret)
if not match:
raise SystemExit("credential_available=false")
def get(path, authenticated=True):
request = urllib.request.Request(base + "/api/v1" + path)
if authenticated:
request.add_header("Authorization", "token " + match[1])
with urllib.request.urlopen(request, timeout=15) as response:
return json.load(response)
print("forge_version", get("/version", False)["version"])
get("/user")
print("credential_authenticated=true")
pattern = re.compile(r"project|member|privacy|binding|项目|成员|隐私|绑定", re.I)
for repo in ("Agent-System-RDM", "Agent-System-Backend", "Agent-System-Frontend", "Agent-System-Dev"):
pulls = get("/repos/weichen/" + repo + "/pulls?state=open&limit=50")
matches = [{"number": p["number"], "title": p["title"],
"url": p["html_url"], "head": p["head"]["sha"]}
for p in pulls if pattern.search(p["title"])]
print("open_pull_scan", json.dumps({"repo": repo, "scanned": len(pulls),
"limit": 50, "matches": matches}, ensure_ascii=False))
@@ -0,0 +1,91 @@
"""Read-only, metadata-only replay of the reported 8788 membership defect."""
import argparse
import hashlib
import json
from pathlib import Path
import sqlite3
import subprocess
def revision(path):
return subprocess.check_output(
["git", "-C", str(path), "rev-parse", "HEAD"], text=True
).strip()
def ref_revision(path, ref):
return subprocess.check_output(
["git", "-C", str(path), "rev-parse", ref], text=True
).strip()
def readonly(path):
db = sqlite3.connect(path.resolve().as_uri() + "?mode=ro", uri=True)
db.execute("PRAGMA query_only=ON")
return db
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--dev", type=Path, required=True)
parser.add_argument("--rdm", type=Path, required=True)
parser.add_argument("--assert-ready", action="store_true")
args = parser.parse_args()
runtime = args.dev / "runtime/dev"
release = (runtime / "current").resolve()
binary = release / "bin/agent-system"
binary_hash = hashlib.sha256(binary.read_bytes()).hexdigest()
print("source_objects", json.dumps({
"dev": revision(args.dev),
"backend": revision(args.dev / "modules/backend"),
"frontend": revision(args.dev / "modules/frontend"),
"rdm_source_checkout": revision(args.rdm),
"rdm_fetched_origin_main": ref_revision(args.rdm, "origin/main"),
"runtime_release": json.loads((release / "release.json").read_text())["source"],
"runtime_binary_sha256": binary_hash,
}, sort_keys=True))
a = readonly(runtime / "data/agent-system.db")
b = readonly(runtime / "data/rdm.db")
projects = a.execute("SELECT id FROM projects WHERE owner=?", ("weichen",)).fetchall()
observations = []
for (host_id,) in projects:
member = b.execute(
"SELECT 1 FROM rdm_members WHERE project_id=? AND owner=?", (host_id, "test")
).fetchone() is not None
if not member:
continue
binding = b.execute(
"SELECT rdm_project,state FROM rdm_host_project_map WHERE host_project=?", (host_id,)
).fetchone()
direct_project = b.execute("SELECT 1 FROM rdm_projects WHERE id=?", (host_id,)).fetchone()
resolved = binding[0] if binding and binding[1] == "ready" else host_id if direct_project else None
allowed = bool(resolved and b.execute(
"SELECT 1 FROM rdm_members WHERE project_id=? AND owner=?", (resolved, "test")
).fetchone())
observations.append({"target_member_row_present": member,
"host_binding_ready": bool(binding and binding[1] == "ready"),
"direct_rdm_project_exists": bool(direct_project),
"member_authorization_from_current_source": allowed})
print("incident_metadata", json.dumps(observations, sort_keys=True))
print("member_command_audit", json.dumps(a.execute(
"SELECT command_id,state,time FROM command_audit WHERE owner=? "
"AND command_id='rdm.project.member.add' ORDER BY id DESC LIMIT 2", ("weichen",)
).fetchall()))
print("creator_is_rdm_admin", bool(b.execute(
"SELECT 1 FROM rdm_admins WHERE owner=?", ("weichen",)
).fetchone()))
print("host_name_unique_indexes", json.dumps([
(row[1], [col[2] for col in a.execute('PRAGMA index_info("' + row[1].replace('"', '""') + '")')])
for row in a.execute("PRAGMA index_list(projects)") if row[2]
]))
a.close()
b.close()
if release != (runtime / "current").resolve() or binary_hash != hashlib.sha256(binary.read_bytes()).hexdigest():
raise SystemExit("UNSTABLE_RUNTIME: repeat against a fixed release")
print("privacy_scope: no credentials, session tokens, chat content, documents or original uploads read")
print("http_browser_replay: NOT_RUN; this probe verifies stored incident metadata and source authorization rules")
if args.assert_ready:
if not observations:
raise SystemExit("NO_TARGET_MEMBER_ROWS: incident no longer matches the recorded setup")
if not all(row["member_authorization_from_current_source"] for row in observations):
raise SystemExit("FAIL membership_resolves_to_registered_project: stored membership cannot authorize the host project")
print("PASS membership_resolves_to_registered_project")
File diff suppressed because one or more lines are too long