1
0
forked from jason/echo

Phase 2: identity plumbing — group/member config, author: attribution

Config schema {owner,endpoint,key} -> {group, member, endpoint, key}:
- member (kebab-case slug, validated) is REQUIRED — who this machine
  writes as; group is the descriptive team name. CHORUS_GROUP/
  CHORUS_MEMBER env, config set --group/--member, doctor + config show
  both with sources, NOT-CONFIGURED (78) without a valid member.
- capture stamps author: <member> on every note (_build_note); bootstrap
  gains {{MEMBER}} substitution and stamps the seeded anchors; all 8
  scaffold templates + canonical frontmatter docs gain author:.
- New missing-author lint check: agent_written notes must carry author:
  (bootstrap marker exempt — plugin-owned).
- Lock owner is now <member>-<client>-<pid> (auto_owner); offline-queue
  records carry a member field for audit.
- build.py --bake-key bakes group/member/endpoint/key (--group/--member;
  member required + slug-validated) for per-member artifacts.
- Manifest -> 2.0.0-alpha.2; rebuilt chorus-memory.plugin.

Verified: 25/25 unit (+config/member checks), scaffold suite (+6 new
member/config assertions), routing-sync, 4 mock e2e (+capture-stamps-
author), run_eval metrics unchanged, +8 phase-2 smoke checks green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 14:00:59 -05:00
parent d920f8821f
commit b3bc5272d5
35 changed files with 304 additions and 144 deletions
+1 -1
View File
@@ -76,7 +76,7 @@ class Harness:
return None if body == "<<MISSING>>" else body
def run(self, script, *args, env_extra=None, base=None):
env = dict(os.environ, CHORUS_BASE=base or self.base, CHORUS_KEY=KEY,
env = dict(os.environ, CHORUS_BASE=base or self.base, CHORUS_KEY=KEY, CHORUS_MEMBER="eval-member",
CHORUS_VERIFY="1", CHORUS_TODAY=TODAY, CHORUS_STATE_DIR=self.state_dir,
**(env_extra or {}))
return subprocess.run([sys.executable, str(script), *args],
+7 -5
View File
@@ -52,7 +52,7 @@ class Harness:
return None if body == "<<MISSING>>" else body
def chorus(self, *args):
env = dict(os.environ, CHORUS_BASE=self.base, CHORUS_KEY=KEY, CHORUS_VERIFY="1",
env = dict(os.environ, CHORUS_BASE=self.base, CHORUS_KEY=KEY, CHORUS_MEMBER="eval-member", CHORUS_VERIFY="1",
CHORUS_TODAY="2026-06-21")
return subprocess.run([sys.executable, str(args[0]), *args[1:]],
capture_output=True, text=True, env=env)
@@ -80,6 +80,8 @@ def main():
note = h.ground("resources/people/bob-smith.md")
check("capture person creates note", note is not None and "type: person" in note, r.stderr)
check("capture stamps agent_written", note and "agent_written: true" in note)
check("capture stamps author with configured member",
note and "author: eval-member" in note)
idx = h.ground("_agent/index/entities.json")
check("index records entity", idx and "bob-smith" in idx and '"bob"' in idx)
@@ -91,7 +93,7 @@ def main():
# 3. capture a company whose body mentions Bob Smith -> auto bidirectional link
r = subprocess.run([sys.executable, str(CHORUS), "capture", "MPM", "--kind", "company", "-"],
input="Bob Smith is the principal here.\n", capture_output=True, text=True,
env=dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_TODAY="2026-06-21"))
env=dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_MEMBER="eval-member", CHORUS_TODAY="2026-06-21"))
mpm = h.ground("resources/companies/mpm.md")
bob = h.ground("resources/people/bob-smith.md")
check("auto-link forward (mpm -> bob)", mpm and "[[resources/people/bob-smith]]" in mpm, r.stdout + r.stderr)
@@ -140,7 +142,7 @@ def main():
'{"schema":1,"updated":"2026-06-21","entities":{"ext-entity":'
'{"path":"resources/concepts/ext-entity.md","kind":"concept","title":"Ext",'
'"aliases":[],"last_seen":"2026-06-21"}}}')
env = dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_TODAY="2026-06-21", PYTHONPATH=str(SCRIPTS))
env = dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_MEMBER="eval-member", CHORUS_TODAY="2026-06-21", PYTHONPATH=str(SCRIPTS))
snippet = ("import chorus_index as ix, chorus_concurrency as c; "
"c.atomic_index_update(lambda d: ix.upsert(d,'mine',"
"'resources/concepts/mine.md','concept','Mine'))")
@@ -184,7 +186,7 @@ def main():
r = subprocess.run([sys.executable, str(CHORUS), "capture", "Bob Smith", "-", "--kind", "person"],
input="met at expo\nsecond line survives\nthird line too\n",
capture_output=True, text=True,
env=dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_TODAY="2026-06-21"))
env=dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_MEMBER="eval-member", CHORUS_TODAY="2026-06-21"))
bob3 = h.ground("resources/people/bob-smith.md") or ""
check("v1.5 update keeps the dated bullet", "- 2026-06-21: met at expo" in bob3, r.stdout + r.stderr)
check("v1.5 update keeps EVERY body line",
@@ -304,7 +306,7 @@ def main():
transcript = tdir / "t.jsonl"
turns = [json.dumps({"type": "user", "message": {"content": f"user turn {i}"}}) for i in range(6)]
transcript.write_text("\n".join(turns), encoding="utf-8")
hook_env = dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_STATE_DIR=str(tdir))
hook_env = dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_MEMBER="eval-member", CHORUS_STATE_DIR=str(tdir))
payload = json.dumps({"session_id": "s1", "transcript_path": str(transcript),
"stop_hook_active": False})
HOOK_STOP = SCRIPTS / "chorus_hook_stop.py"
+1 -1
View File
@@ -59,7 +59,7 @@ def main():
state = tempfile.mkdtemp()
def chorus(base, *args):
env = dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_STATE_DIR=state,
env = dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_MEMBER="eval-member", CHORUS_STATE_DIR=state,
CHORUS_VERIFY="0", CHORUS_TODAY="2026-06-22")
return subprocess.run([sys.executable, str(CHORUS), *args], capture_output=True, text=True, env=env)
+1 -1
View File
@@ -59,7 +59,7 @@ def main():
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
def chorus(*args):
env = dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_VERIFY="0")
env = dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_MEMBER="eval-member", CHORUS_VERIFY="0")
return subprocess.run([sys.executable, str(CHORUS), *args], capture_output=True, text=True, env=env)
def ground(path):
+1 -1
View File
@@ -51,7 +51,7 @@ def main():
return getattr(e, "code", 0), ""
def chorus(*args, stdin=None):
env = dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_VERIFY="1", CHORUS_TODAY="2026-06-22")
env = dict(os.environ, CHORUS_BASE=base, CHORUS_KEY=KEY, CHORUS_MEMBER="eval-member", CHORUS_VERIFY="1", CHORUS_TODAY="2026-06-22")
return subprocess.run([sys.executable, str(CHORUS), *args], input=stdin,
capture_output=True, text=True, env=env)