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:
@@ -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
Reference in New Issue
Block a user