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:
@@ -52,7 +52,8 @@ MAX_DESCRIPTION = 500 # plugin-marketplace cap: plugin.json "description" must
|
||||
# (it has silently regressed past the limit before — fail the build now)
|
||||
|
||||
# field name -> the constant assigned in chorus_config.py
|
||||
_CONSTS = {"owner": "DEFAULT_OWNER", "endpoint": "DEFAULT_BASE", "key": "DEFAULT_KEY"}
|
||||
_CONSTS = {"group": "DEFAULT_GROUP", "member": "DEFAULT_MEMBER",
|
||||
"endpoint": "DEFAULT_BASE", "key": "DEFAULT_KEY"}
|
||||
|
||||
|
||||
def _const_re(const: str) -> re.Pattern:
|
||||
@@ -113,8 +114,8 @@ def build(out: Path, files: list[Path], bake: dict | None, strip_key: bool) -> i
|
||||
|
||||
|
||||
def bake_values(args) -> dict:
|
||||
"""Resolve owner/endpoint/key for --bake-key from --owner/--endpoint/--key,
|
||||
else --from <config.json>, else CHORUS_OWNER/CHORUS_BASE/CHORUS_KEY env."""
|
||||
"""Resolve group/member/endpoint/key for --bake-key from the CLI flags,
|
||||
else --from <config.json>, else CHORUS_GROUP/CHORUS_MEMBER/CHORUS_BASE/CHORUS_KEY env."""
|
||||
src = {}
|
||||
if args.from_file:
|
||||
fp = Path(args.from_file).expanduser()
|
||||
@@ -127,28 +128,33 @@ def bake_values(args) -> dict:
|
||||
if isinstance(loaded, dict):
|
||||
src = loaded
|
||||
vals = {
|
||||
"owner": args.owner or src.get("owner") or os.environ.get("CHORUS_OWNER") or "",
|
||||
"group": args.group or src.get("group") or os.environ.get("CHORUS_GROUP") or "",
|
||||
"member": args.member or src.get("member") or os.environ.get("CHORUS_MEMBER") or "",
|
||||
"endpoint": (args.endpoint or src.get("endpoint") or os.environ.get("CHORUS_BASE") or "").rstrip("/"),
|
||||
"key": args.key or src.get("key") or os.environ.get("CHORUS_KEY") or "",
|
||||
}
|
||||
missing = [f for f in ("endpoint", "key") if not vals[f]]
|
||||
missing = [f for f in ("endpoint", "key", "member") if not vals[f]]
|
||||
if missing:
|
||||
raise RuntimeError(
|
||||
"build: --bake-key needs a real endpoint and key — missing "
|
||||
+ ", ".join(missing) + ". Provide --from <config.json>, the --owner/"
|
||||
"--endpoint/--key flags, or set CHORUS_OWNER/CHORUS_BASE/CHORUS_KEY.")
|
||||
if "your-obsidian-rest-endpoint" in vals["endpoint"] or vals["key"].startswith("<"):
|
||||
"build: --bake-key needs a real endpoint, key, and member — missing "
|
||||
+ ", ".join(missing) + ". Provide --from <config.json>, the --group/--member/"
|
||||
"--endpoint/--key flags, or set CHORUS_GROUP/CHORUS_MEMBER/CHORUS_BASE/CHORUS_KEY.")
|
||||
if "your-obsidian-rest-endpoint" in vals["endpoint"] or vals["key"].startswith("<") \
|
||||
or vals["member"].startswith("<"):
|
||||
raise RuntimeError("build: --bake-key got the template placeholders, not real values.")
|
||||
if not re.match(r"^[a-z0-9][a-z0-9-]{0,31}$", vals["member"]):
|
||||
raise RuntimeError(f"build: baked member must be a kebab-case slug, got {vals['member']!r}")
|
||||
return vals
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
ap = argparse.ArgumentParser(description="Build the chorus-memory .plugin artifact")
|
||||
ap.add_argument("--bake-key", action="store_true",
|
||||
help="bake a user's owner/endpoint/key into the artifact (secret-bearing — never commit)")
|
||||
help="bake a member's group/member/endpoint/key into the artifact (secret-bearing — never commit)")
|
||||
ap.add_argument("--from", dest="from_file", metavar="FILE",
|
||||
help="config.json to read owner/endpoint/key from when baking")
|
||||
ap.add_argument("--owner", help="baked owner (overrides --from / env)")
|
||||
help="config.json to read group/member/endpoint/key from when baking")
|
||||
ap.add_argument("--group", help="baked group name (overrides --from / env)")
|
||||
ap.add_argument("--member", help="baked member id (kebab-case; overrides --from / env)")
|
||||
ap.add_argument("--endpoint", help="baked endpoint (overrides --from / env)")
|
||||
ap.add_argument("--key", help="baked key (overrides --from / env)")
|
||||
ap.add_argument("--label", help="suffix for the artifact name, e.g. --label alice")
|
||||
@@ -206,12 +212,12 @@ def main(argv: list[str] | None = None) -> int:
|
||||
print(f"built {out.name} ({len(files)} entries, {size:,} bytes)")
|
||||
|
||||
if bake:
|
||||
who = bake.get("owner") or "(no owner)"
|
||||
print(f"note: --bake-key baked credentials for {who} into {CONFIG_PY}.")
|
||||
who = bake.get("member") or "(no member)"
|
||||
print(f"note: --bake-key baked credentials for member '{who}' into {CONFIG_PY}.")
|
||||
print("WARNING: this artifact carries a vault key — deliver it directly to that one "
|
||||
"user and NEVER commit, push, or publish it (dist/ is gitignored).")
|
||||
"member and NEVER commit, push, or publish it (dist/ is gitignored).")
|
||||
elif args.strip_key:
|
||||
print("note: --strip-key set — artifact is token-free; user configures owner/endpoint/key at runtime.")
|
||||
print("note: --strip-key set — artifact is token-free; member configures group/member/endpoint/key at runtime.")
|
||||
elif token_present(SRC / "skills" / "chorus-memory" / "scripts" / "chorus_config.py"):
|
||||
print("WARNING: a DEFAULT_* constant in chorus_config.py is non-empty — this artifact carries a "
|
||||
"secret. Do not commit it. Build from a clean source tree, or use --strip-key.")
|
||||
|
||||
Reference in New Issue
Block a user