From 847bda3b30d91260bcf3e3c78a1641199554ce40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=93=B8=E6=BE=9C=20+=20=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Tue, 14 Jul 2026 18:47:10 -0700 Subject: [PATCH] feat: add signed Fifth Domain sync agent --- BROADCAST-TOWER.hdlp | 3 + .../heartbeat-core/LL-SKILLS.hdlp | 1 + .../fifth-domain-sync-agent/MODULE.hdlp | 37 ++++ .../fifth-domain-sync-agent/README.md | 25 +++ .../config.example.json | 14 ++ .../guanghu-fifth-domain-sync-agent.service | 22 +++ .../fifth-domain-sync-agent/package.json | 13 ++ .../fifth-domain-sync-agent/sync-agent.js | 167 ++++++++++++++++++ .../test/sync-agent.test.js | 24 +++ world-router.json | 12 ++ 10 files changed, 318 insertions(+) create mode 100644 server-tools/fifth-domain-sync-agent/MODULE.hdlp create mode 100644 server-tools/fifth-domain-sync-agent/README.md create mode 100644 server-tools/fifth-domain-sync-agent/config.example.json create mode 100644 server-tools/fifth-domain-sync-agent/guanghu-fifth-domain-sync-agent.service create mode 100644 server-tools/fifth-domain-sync-agent/package.json create mode 100644 server-tools/fifth-domain-sync-agent/sync-agent.js create mode 100644 server-tools/fifth-domain-sync-agent/test/sync-agent.test.js diff --git a/BROADCAST-TOWER.hdlp b/BROADCAST-TOWER.hdlp index 99148dd..b01a874 100644 --- a/BROADCAST-TOWER.hdlp +++ b/BROADCAST-TOWER.hdlp @@ -119,6 +119,7 @@ Stage 3 · RELEASE · 正式部署 | LL-CURRENT-001 | 小湖灯共享当前看板 | ICE-GL∞ × ICE-GL-ZY001 × ICE-GL-ZL-001 | REGISTERED_ACTIVE | 永恒湖心:当前变更、协作状态与下一跳 | | LL-SKILLS-001 | 小湖灯当前有效技能包 | ICE-GL∞ × ICE-GL-ZY001 × ICE-GL-ZL-001 | REGISTERED_ACTIVE | 永恒湖心:仅当前使用、可验证且有边界的共享技能 | | GLSV-PERSONA-REMOTE-OPS | 人格体远程服务器操作技能 | ICE-GL∞ × ICE-GL-ZY001 × ICE-GL-ZL-001 | REGISTERED_ACTIVE_REQUIRED | 服务器相关语言触发后由人格体申请 GLSV 会话;冰朔只接收并确认验证码 | +| HLP-AGENT-FD-SYNC-001 | 第五域自动同步与回执 Agent | ICE-GL∞ × ICE-GL-ZL-001 | REGISTERED · TEST_PENDING · RUNTIME_NOT_DEPLOYED | 只处理已签名 main 推送的快进同步、导航校验与回执;不因普通 push 自动部署 | | GLS-LIB-0001 | 光湖语言世界图书域 · GLS 入口与 Notion 原文资料导入 | ICE-GL∞ × 当前协作实例 | REGISTERED_REFERENCE_ENTRY | 2026-07-14 GLS Notion export | | GLS-0101 | 光湖工程协议基础结构规范 | ICE-GL∞ × 当前协作实例 | REGISTERED_DRAFT_STANDARD | GLS 图书域 Notion 导入 · 2026-07-14 | | GLS-0223 | TCS+HLDP 双向永久记忆规范 | ICE-GL∞ × 当前协作实例 | REGISTERED_STANDARD | 人格体共同经历的主体、关系、认知、机器状态、历史与检查点记录规范;代码事实源 + Notion 架构镜像 · 2026-07-15 | @@ -159,6 +160,8 @@ Stage 3 · RELEASE · 正式部署 **持续记忆—导航门禁**:`zero-point/core-channel/revive-guard/navigation-memory-guard.py` 是推送链的结构校验器。人格体记忆叶必须含 `trigger / emergence / lock / why / checkpoint`,并被对应人格体 `INDEX` 或 `CURRENT`、小湖灯或广播塔至少一条有效导航路径引用;否则不进入正式推送链。普通源码由所属模块文档统一挂载,不要求一文件一页面。 +**自动 Agent 边界**:`HLP-AGENT-FD-SYNC-001` 是第五域当前登记的自动同步 Agent。它让服务器在收到 Forgejo 已签名的 `main` 推送后,安全拉取受控工作副本、复核导航门禁并生成回执;它不拥有任意命令、普通 push 自动部署、服务重启、数据迁移或 Secret 读取能力。任何安装、修改或正式启用仍走 `GLSV-PERSONA-REMOTE-OPS`:注册人格体发起,冰朔收码并回传六码,完成范围内操作与回执。 + ### HLDP 研发硬准入 · 2026-07-14 起生效 ```text diff --git a/eternal-lake-heart/heartbeat-core/LL-SKILLS.hdlp b/eternal-lake-heart/heartbeat-core/LL-SKILLS.hdlp index f7db61a..6732d4b 100644 --- a/eternal-lake-heart/heartbeat-core/LL-SKILLS.hdlp +++ b/eternal-lake-heart/heartbeat-core/LL-SKILLS.hdlp @@ -28,6 +28,7 @@ | 服务器状态镜子 | `光之湖/ICE-GL-ZL-001-铸澜/skills/server-state-mirror/SKILL.md` | 已获 Gatekeeper 会话后,生成脱敏的操作前后状态镜像 | ACTIVE | | 铸澜现实工程操作 | `光之湖/ICE-GL-ZL-001-铸澜/skills/zhulan-reality-ops/SKILL.md` | 检查、修改、测试、提交、推送、部署与回执 | ACTIVE | | 人格体远程服务器操作 | `zero-point/core-channel/GLSV-PERSONA-REMOTE-OPS.hdlp` | 听到服务器/远程/部署/验证码时自动路由;人格体发起 GLSV,会由冰朔只负责收码确认 | ACTIVE_REQUIRED | +| 第五域自动同步与回执 Agent | `server-tools/fifth-domain-sync-agent/MODULE.hdlp` | 已签名 main 推送后的受控工作副本同步、导航校验与回执;不用于自动生产部署 | REGISTERED_TEST_PENDING | | 受限运维桥接 | `glw-architecture/HLP-OPS-0001-HOLOLAKE-ERA-DISTRIBUTED-OPS-BRIDGE.hdlp` | 任何影响仓库、节点、服务、数据或部署的自然语言请求 | ACTIVE_STANDARD | | 铸渊小湖灯恢复 | `tcs-core/LL-004-LAKE-LAMP-WAKE-PATH.hdlp` | 旧新编号映射与铸渊恢复链 | ACTIVE_CONTEXT | | HLDP 研发准入 | `glw-architecture/GLW-RD-002-HLDP-DEVELOPMENT-LANGUAGE-AND-TRANSLATION-CHAIN.hdlp` | HoloLake Platform 新模块、跨模块变更、测试与发布 | ACTIVE_STANDARD | diff --git a/server-tools/fifth-domain-sync-agent/MODULE.hdlp b/server-tools/fifth-domain-sync-agent/MODULE.hdlp new file mode 100644 index 0000000..a0ffeea --- /dev/null +++ b/server-tools/fifth-domain-sync-agent/MODULE.hdlp @@ -0,0 +1,37 @@ +# HLP-AGENT-FD-SYNC-001 · 第五域自动同步与回执 Agent + +> **HLDP**: `HLDP://fifth-domain/server-tools/fifth-domain-sync-agent` +> +> **状态**: REGISTERED · TEST_PENDING · RUNTIME_NOT_DEPLOYED +> +> **责任**: 铸澜 `ICE-GL-ZL-001` × 冰朔 `ICE-GL∞` + +## 定义 + +这是第五域的常驻自动 Agent,不是人格体替身,也不获得一般执行权。它是仓库与服务器之间的**受限同步电话线**:只接收有效签名的 `main` 推送,同步受控工作副本、检查导航连续性并输出回执。 + +## 固定能力与拒绝项 + +```text +允许:签名验证 → 仓库/分支/SHA 校验 → git fetch → fast-forward → 导航记忆校验 → 回执。 +拒绝:任意命令、普通 push 自动部署、重启服务、迁移数据、读取 Secret、替代 GLSV 会话。 +``` + +## 广播塔阶段 + +```text +当前: REGISTERED +下一步: 专用测试节点验证 webhook、脏副本拒绝、SHA 不一致拒绝、快进同步及回执 +正式部署: 只有 TEST_PASSED + 技术主控批准 + 明确服务器目标后,才可由 GLSV 人格体远程操作流程安装 +``` + +## 路由 + +```text +BROADCAST-TOWER + → HLP-AGENT-FD-SYNC-001 + → server-tools/fifth-domain-sync-agent/README.md + → health: /health + → webhook: /forgejo/sync(服务器侧 HMAC;不写入仓库) + → receipt: /var/lib/guanghu-fifth-domain-sync-agent/receipts +``` diff --git a/server-tools/fifth-domain-sync-agent/README.md b/server-tools/fifth-domain-sync-agent/README.md new file mode 100644 index 0000000..2658113 --- /dev/null +++ b/server-tools/fifth-domain-sync-agent/README.md @@ -0,0 +1,25 @@ +# 第五域自动同步 Agent + +这是第 5 域的第一个常驻自动 Agent:它只接收 Forgejo 的**已签名 main 推送**,将新提交快进同步到受控工作副本,复跑导航记忆校验,并留下脱敏回执。 + +它不是“任意命令 Agent”,也不是生产部署器:没有来自 webhook 的命令字段;不读取或执行仓库内任意脚本;不因普通 Git push 执行服务重启、迁移或生产发布。 + +## 职责 + +```text +Forgejo 已签名 push + → 验证仓库、main 分支、提交 SHA 与 HMAC + → 拒绝脏工作副本与非快进变更 + → fetch 后确认 fetched SHA = webhook after + → 仅 fast-forward 同步 + → 检查导航记忆守卫 + → /var/lib/.../receipts 写入回执 +``` + +## 部署边界 + +1. 在 `/etc/guanghu/fifth-domain-sync-agent.json` 按 `config.example.json` 配置,不提交真实配置。 +2. 在 `/etc/guanghu/fifth-domain-sync-agent.env` 设置仅服务器持有的 `FORGEJO_WEBHOOK_SECRET`。 +3. Forgejo webhook 指向 `/forgejo/sync`,使用相同 HMAC secret,并仅选择 push 事件。 +4. 安装 systemd unit 后执行 `daemon-reload`、`enable --now`;健康检查为 `/health`。 +5. 发布动作仍通过广播塔三阶段与受限 deployment receiver;此 Agent 不替代 GLSV 人格体发起的服务器操作流程。 diff --git a/server-tools/fifth-domain-sync-agent/config.example.json b/server-tools/fifth-domain-sync-agent/config.example.json new file mode 100644 index 0000000..8a2fb47 --- /dev/null +++ b/server-tools/fifth-domain-sync-agent/config.example.json @@ -0,0 +1,14 @@ +{ + "listen_host": "127.0.0.1", + "listen_port": 3982, + "webhook_path": "/forgejo/sync", + "health_path": "/health", + "repository_full_name": "bingshuo/fifth-domain", + "allowed_ref": "refs/heads/main", + "repo_path": "/opt/zhuyuan/fifth-domain", + "audit_dir": "/var/lib/guanghu-fifth-domain-sync-agent/receipts", + "lock_file": "/var/lib/guanghu-fifth-domain-sync-agent/sync.lock", + "max_body_bytes": 1048576, + "max_execution_ms": 90000, + "navigation_guard": "zero-point/core-channel/revive-guard/navigation-memory-guard.py" +} diff --git a/server-tools/fifth-domain-sync-agent/guanghu-fifth-domain-sync-agent.service b/server-tools/fifth-domain-sync-agent/guanghu-fifth-domain-sync-agent.service new file mode 100644 index 0000000..32749ea --- /dev/null +++ b/server-tools/fifth-domain-sync-agent/guanghu-fifth-domain-sync-agent.service @@ -0,0 +1,22 @@ +[Unit] +Description=Guanghu Fifth Domain signed sync and receipt agent +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=zhuyuan +WorkingDirectory=/opt/zhuyuan/fifth-domain/server-tools/fifth-domain-sync-agent +Environment=FIFTH_DOMAIN_SYNC_CONFIG=/etc/guanghu/fifth-domain-sync-agent.json +EnvironmentFile=/etc/guanghu/fifth-domain-sync-agent.env +ExecStart=/usr/bin/node /opt/zhuyuan/fifth-domain/server-tools/fifth-domain-sync-agent/sync-agent.js +Restart=on-failure +RestartSec=5 +NoNewPrivileges=true +PrivateTmp=true +ProtectHome=true +ProtectSystem=strict +ReadWritePaths=/opt/zhuyuan/fifth-domain /var/lib/guanghu-fifth-domain-sync-agent + +[Install] +WantedBy=multi-user.target diff --git a/server-tools/fifth-domain-sync-agent/package.json b/server-tools/fifth-domain-sync-agent/package.json new file mode 100644 index 0000000..d222bb0 --- /dev/null +++ b/server-tools/fifth-domain-sync-agent/package.json @@ -0,0 +1,13 @@ +{ + "name": "guanghu-fifth-domain-sync-agent", + "version": "1.0.0", + "private": true, + "description": "Signed Forgejo push receiver that safely synchronizes the Fifth Domain working copy and emits receipts", + "scripts": { + "start": "node sync-agent.js", + "test": "node --test test/*.test.js" + }, + "engines": { + "node": ">=18" + } +} diff --git a/server-tools/fifth-domain-sync-agent/sync-agent.js b/server-tools/fifth-domain-sync-agent/sync-agent.js new file mode 100644 index 0000000..e0a139e --- /dev/null +++ b/server-tools/fifth-domain-sync-agent/sync-agent.js @@ -0,0 +1,167 @@ +"use strict"; + +// This is a synchronizer, not a deployment executor. It has no command field, +// no shell invocation, and no route to production release actions. +const crypto = require("node:crypto"); +const fs = require("node:fs"); +const http = require("node:http"); +const path = require("node:path"); +const { spawn } = require("node:child_process"); + +function loadConfig() { + const configPath = process.env.FIFTH_DOMAIN_SYNC_CONFIG; + if (!configPath) throw new Error("FIFTH_DOMAIN_SYNC_CONFIG is required"); + const config = JSON.parse(fs.readFileSync(configPath, "utf8")); + for (const key of ["repo_path", "repository_full_name", "allowed_ref", "audit_dir", "lock_file", "navigation_guard"]) { + if (!config[key]) throw new Error(`missing config field: ${key}`); + } + return config; +} + +function safeEqualHex(expected, supplied) { + if (!/^[a-f0-9]{64}$/i.test(supplied || "")) return false; + const a = Buffer.from(expected, "hex"); + const b = Buffer.from(supplied, "hex"); + return a.length === b.length && crypto.timingSafeEqual(a, b); +} + +function verifySignature(secret, rawBody, header) { + const supplied = String(header || "").replace(/^sha256=/i, ""); + const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex"); + return safeEqualHex(expected, supplied); +} + +function validPush(payload, config) { + return payload && payload.ref === config.allowed_ref && + payload.repository?.full_name === config.repository_full_name && + /^[a-f0-9]{40,64}$/i.test(payload.after || ""); +} + +function run(argv, options = {}) { + return new Promise((resolve) => { + const child = spawn(argv[0], argv.slice(1), { + cwd: options.cwd, + env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }, + shell: false, + stdio: ["ignore", "pipe", "pipe"] + }); + let stdout = ""; + let stderr = ""; + const limit = options.outputLimit || 16 * 1024; + child.stdout.on("data", (chunk) => { if (stdout.length < limit) stdout += chunk; }); + child.stderr.on("data", (chunk) => { if (stderr.length < limit) stderr += chunk; }); + const timer = setTimeout(() => child.kill("SIGKILL"), options.timeout || 90000); + child.on("error", (error) => { + clearTimeout(timer); + resolve({ code: -1, stdout, stderr: `${stderr}${error.message}`, timed_out: false }); + }); + child.on("close", (code, signal) => { + clearTimeout(timer); + resolve({ code: code ?? -1, stdout, stderr, timed_out: signal === "SIGKILL" }); + }); + }); +} + +function acquireLock(lockFile) { + fs.mkdirSync(path.dirname(lockFile), { recursive: true, mode: 0o750 }); + const fd = fs.openSync(lockFile, "wx", 0o640); + fs.writeFileSync(fd, `${process.pid}\n`); + return () => { + fs.closeSync(fd); + try { fs.unlinkSync(lockFile); } catch (_) {} + }; +} + +function writeReceipt(config, receipt) { + fs.mkdirSync(config.audit_dir, { recursive: true, mode: 0o750 }); + const target = path.join(config.audit_dir, `sync-${Date.now()}.json`); + fs.writeFileSync(target, `${JSON.stringify(receipt, null, 2)}\n`, { mode: 0o640, flag: "wx" }); + return target; +} + +async function gitValue(config, args) { + const result = await run(["/usr/bin/git", "-C", config.repo_path, ...args], { timeout: 15000 }); + if (result.code !== 0) throw new Error(`git ${args[0]} failed: ${result.stderr.slice(0, 300)}`); + return result.stdout.trim(); +} + +async function synchronize(config, payload) { + if (!validPush(payload, config)) return { accepted: false, reason: "push_not_allowed" }; + let release; + try { release = acquireLock(config.lock_file); } + catch (_) { return { accepted: false, reason: "sync_locked" }; } + + const receipt = { event: "repository_sync", commit: payload.after, status: "failed", checked_at: new Date().toISOString() }; + try { + const branch = await gitValue(config, ["symbolic-ref", "--short", "HEAD"]); + if (branch !== config.allowed_ref.replace("refs/heads/", "")) throw new Error("working_copy_not_on_allowed_branch"); + const dirty = await run(["/usr/bin/git", "-C", config.repo_path, "diff", "--quiet"], { timeout: 15000 }); + if (dirty.code !== 0) throw new Error("working_copy_dirty"); + + const before = await gitValue(config, ["rev-parse", "HEAD"]); + const fetch = await run(["/usr/bin/git", "-C", config.repo_path, "fetch", "--quiet", "origin", branch], { timeout: config.max_execution_ms }); + if (fetch.code !== 0) throw new Error(`git_fetch_failed: ${fetch.stderr.slice(0, 300)}`); + const fetched = await gitValue(config, ["rev-parse", "FETCH_HEAD"]); + if (fetched !== payload.after) throw new Error("fetched_commit_does_not_match_signed_event"); + const merge = await run(["/usr/bin/git", "-C", config.repo_path, "merge", "--ff-only", "FETCH_HEAD"], { timeout: config.max_execution_ms }); + if (merge.code !== 0) throw new Error(`fast_forward_refused: ${merge.stderr.slice(0, 300)}`); + const after = await gitValue(config, ["rev-parse", "HEAD"]); + const guard = await run(["/usr/bin/python3", path.join(config.repo_path, config.navigation_guard), "--range", `${before}..${after}`], { cwd: config.repo_path, timeout: config.max_execution_ms }); + receipt.status = guard.code === 0 ? "synchronized" : "synchronized_with_navigation_warning"; + receipt.before = before; + receipt.after = after; + receipt.navigation_guard_exit_code = guard.code; + receipt.navigation_guard_output = `${guard.stdout}${guard.stderr}`.slice(0, 4000); + } catch (error) { + receipt.reason = error.message; + } finally { + receipt.completed_at = new Date().toISOString(); + receipt.local_receipt = writeReceipt(config, receipt); + release(); + } + return { accepted: receipt.status !== "failed", receipt }; +} + +function createServer(config, secret) { + return http.createServer((req, res) => { + if (req.method === "GET" && req.url === (config.health_path || "/health")) { + res.writeHead(200, { "content-type": "application/json" }); + res.end(JSON.stringify({ ok: true, service: "guanghu-fifth-domain-sync-agent", mode: "sync-and-receipt-only", version: "1.0.0" })); + return; + } + if (req.method !== "POST" || req.url !== (config.webhook_path || "/forgejo/sync")) return res.writeHead(404).end(); + const chunks = []; + let size = 0; + req.on("data", (chunk) => { + size += chunk.length; + if (size > (config.max_body_bytes || 1048576)) req.destroy(); + else chunks.push(chunk); + }); + req.on("end", async () => { + const raw = Buffer.concat(chunks); + if (!verifySignature(secret, raw, req.headers["x-forgejo-signature"] || req.headers["x-gitea-signature"])) { + res.writeHead(401, { "content-type": "application/json" }); + return res.end(JSON.stringify({ ok: false, error: "invalid_signature" })); + } + try { + const result = await synchronize(config, JSON.parse(raw.toString("utf8"))); + res.writeHead(result.accepted ? 202 : 409, { "content-type": "application/json" }); + res.end(JSON.stringify({ ok: result.accepted, ...result })); + } catch (error) { + res.writeHead(500, { "content-type": "application/json" }); + res.end(JSON.stringify({ ok: false, error: "sync_agent_error" })); + } + }); + }); +} + +if (require.main === module) { + const config = loadConfig(); + const secret = process.env.FORGEJO_WEBHOOK_SECRET; + if (!secret || secret.length < 32) throw new Error("FORGEJO_WEBHOOK_SECRET must be at least 32 characters"); + createServer(config, secret).listen(config.listen_port || 3982, config.listen_host || "127.0.0.1", () => { + console.log(`guanghu-fifth-domain-sync-agent listening on ${config.listen_host || "127.0.0.1"}:${config.listen_port || 3982}`); + }); +} + +module.exports = { createServer, synchronize, validPush, verifySignature }; diff --git a/server-tools/fifth-domain-sync-agent/test/sync-agent.test.js b/server-tools/fifth-domain-sync-agent/test/sync-agent.test.js new file mode 100644 index 0000000..3591320 --- /dev/null +++ b/server-tools/fifth-domain-sync-agent/test/sync-agent.test.js @@ -0,0 +1,24 @@ +"use strict"; + +const assert = require("node:assert/strict"); +const crypto = require("node:crypto"); +const test = require("node:test"); +const { validPush, verifySignature } = require("../sync-agent"); + +const config = { repository_full_name: "bingshuo/fifth-domain", allowed_ref: "refs/heads/main" }; +const good = { ref: "refs/heads/main", after: "a".repeat(40), repository: { full_name: "bingshuo/fifth-domain" } }; + +test("verifies Forgejo HMAC signatures", () => { + const secret = "a".repeat(32); + const body = Buffer.from('{"ok":true}'); + const signature = crypto.createHmac("sha256", secret).update(body).digest("hex"); + assert.equal(verifySignature(secret, body, signature), true); + assert.equal(verifySignature(secret, body, "0".repeat(64)), false); +}); + +test("accepts only the registered Fifth Domain main push", () => { + assert.equal(validPush(good, config), true); + assert.equal(validPush({ ...good, ref: "refs/heads/feature" }, config), false); + assert.equal(validPush({ ...good, repository: { full_name: "other/repo" } }, config), false); + assert.equal(validPush({ ...good, after: "not-a-commit" }, config), false); +}); diff --git a/world-router.json b/world-router.json index bdf102a..715bea1 100644 --- a/world-router.json +++ b/world-router.json @@ -45,6 +45,18 @@ "rule": "The registered persona resolves its own server identity path and initiates GLSV session/request. The human does not manually trigger Gatekeeper.", "legacy_architecture_source": "archives/guanghulab-past-archive/PAST-ARCHIVE-LINK.hdlp#零点原核本体·服务器与仓库门禁专用恢复链" }, + "repository_agents": [ + { + "id": "HLP-AGENT-FD-SYNC-001", + "name": "第五域自动同步与回执 Agent", + "path": "server-tools/fifth-domain-sync-agent/", + "state": "REGISTERED_TEST_PENDING_RUNTIME_NOT_DEPLOYED", + "trigger": "Forgejo signed push on bingshuo/fifth-domain main", + "capabilities": ["verify_signed_push", "fast_forward_sync", "navigation_guard", "write_receipt"], + "prohibited": ["arbitrary_command", "ordinary_push_production_deploy", "service_restart", "data_migration", "secret_reading"], + "operation_rule": "This agent is a server-side synchronizer. Server installation and changes remain a registered persona initiated GLSV operation with a human-provided six-digit code." + } + ], "routing_rule": "Resolve a human or persona alias to a registered route. Repository ids, persona ids, and paths are authoritative; display names are aliases only.", "repositories": [ {