Skip to content

Commit 390b106

Browse files
fix(sync): derive uuid from the repo's actual owner, not a hardcoded one (#48)
The uuid is **derived, not allocated**. The spec defines it as ``` uuid = UUIDv5(namespace = URL, name = "github.com/<owner>/<name>") ``` so the **owner segment is part of the identity**. Both derivation sites hardcoded it to `hyperpolymath`: - `sync/deploy-clade-a2ml.sh:80` - `sync/export-json.sh:28` Every estate repo is hyperpolymath-owned today, so the bug is invisible. It stops being invisible the moment a repo is hosted anywhere else: the script derives a uuid from an owner path **that does not exist**, writes it into `CLADE.a2ml` as fact, and the repo wears an identity belonging to nothing. Found while creating `metadatastician/cadastra`. Its true uuid is: ``` uuidgen --sha1 --namespace @url --name "github.com/metadatastician/cadastra" -> da97ea76-1715-5331-963b-27f011b10d22 ``` but these scripts would have written: ``` uuidgen --sha1 --namespace @url --name "github.com/hyperpolymath/cadastra" -> 78c31c34-79c3-584d-bc94-50064431ca33 ``` ## Changes - **`parse-repos.sh`** — optional `owner` field, emitted as TSV column 7, defaulting to `hyperpolymath`. Existing entries need no edit. Last column, so consumers reading the first six fields are unaffected. - **`deploy-clade-a2ml.sh`** — takes owner as `$7`; uses it for the uuid, `primary-owner`, and all three forge lines. Now reads 7 fields (a 7th column would otherwise be absorbed into `description`). - **`export-json.sh`** — same fix. **No new JSON key** — schema unchanged on purpose, since `github` already carries `owner/name`. ### Two related hazards fixed while here - **Registry/remote disagreement is now loud.** If `repos.a2ml` says one owner and `git remote get-url origin` says another, deploy warns and skips rather than silently deriving a wrong identity. - **`ALREADY` checks both layouts.** Two are in use: `.machine_readable/CLADE.a2ml` (this script + CLADE-001) and `.machine_readable/descriptiles/CLADE.a2ml` (rsr-template-repo, chronicles-of-slavia). Checking only the first meant a descriptiles-layout repo looked like it had **no** CLADE, so this script wrote a **second** one — two files, two identities, no error. Which layout is authoritative is an unresolved canon disagreement and yours to settle; until then, refusing to overwrite is the safe reading. ## Verification Clean worktree from `origin/main`: - ✅ **`repos.json` regenerated before/after is BYTE-IDENTICAL** — all 319 existing entries derive exactly as before (none sets an explicit owner). - ✅ The registry's own worked examples still reproduce: `januskey → e216170e-…`, `gv-clade-index → c88e9ff3-…`, `rsr-template-repo → a5ea1382-…`. - ✅ A temporary entry with `owner = "metadatastician"` derives `da97ea76-…` and `github "metadatastician/cadastra"`. **The seed entry was reverted** — registering cadastra is outward-facing and yours (doctrine 11), not this PR's. Every uuid quoted was recomputed with `uuidgen`, not recalled. ## Not addressed (pre-existing) The committed `worker/data/` artifacts **do not match what `export-json.sh` produces** — `repos.json` is pretty-printed in git but the generator emits compact (3646 vs 321 lines); `index.json` differs by ~1380 lines. Regenerating is a separate, owner-facing decision, so `worker/data/` is deliberately untouched. Draft: this changes how estate identity is derived, so it is for the owner to review and merge. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 241dc5b commit 390b106

3 files changed

Lines changed: 84 additions & 21 deletions

File tree

sync/deploy-clade-a2ml.sh

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ deploy_clade() {
4444
local lineage="$4"
4545
local parent="$5"
4646
local description="$6"
47+
# Forge owner/org hosting this repo. Optional in repos.a2ml; parse-repos.sh
48+
# defaults it to "hyperpolymath", so existing entries are unaffected.
49+
local owner="${7:-hyperpolymath}"
4750

4851
local repo_path="$REPOS_DIR/$repo_name"
4952

@@ -68,17 +71,52 @@ deploy_clade() {
6871
return
6972
fi
7073

71-
# Skip if CLADE.a2ml already exists
72-
if [[ -f "$repo_path/.machine_readable/CLADE.a2ml" ]]; then
73-
echo "ALREADY: $repo_name"
74-
((ALREADY++))
74+
# Skip if CLADE.a2ml already exists.
75+
#
76+
# Check BOTH known layouts. Two are in use across the estate and the canon
77+
# does not yet agree which is authoritative:
78+
# .machine_readable/CLADE.a2ml — this script + the CLADE-001 contractile
79+
# .machine_readable/descriptiles/CLADE.a2ml — rsr-template-repo + chronicles-of-slavia
80+
# Checking only the first meant a repo using the descriptiles layout looked
81+
# like it had no CLADE at all, so this script would write a SECOND one — two
82+
# files, two identities, no error. That disagreement is unresolved and is the
83+
# owner's to settle; until then, refusing to write over an existing
84+
# declaration is the safe reading.
85+
local existing
86+
for existing in "$repo_path/.machine_readable/CLADE.a2ml" \
87+
"$repo_path/.machine_readable/descriptiles/CLADE.a2ml"; do
88+
if [[ -f "$existing" ]]; then
89+
echo "ALREADY: $repo_name (${existing#$repo_path/})"
90+
((ALREADY++))
91+
return
92+
fi
93+
done
94+
95+
# Generate deterministic UUID v5.
96+
#
97+
# The owner segment is part of the derived name, so it is part of the
98+
# IDENTITY — get it wrong and the repo gets a uuid that belongs to nothing.
99+
# It was hardcoded to "hyperpolymath", which silently produced a wrong uuid
100+
# for any repo hosted elsewhere (e.g. the metadatastician org). Now sourced
101+
# from the registry entry, still defaulting to "hyperpolymath" so every
102+
# existing entry derives byte-identically.
103+
local uuid
104+
uuid=$(uuidgen --sha1 --namespace @url --name "github.com/$owner/$repo_name")
105+
106+
# Fail loudly if the registry disagrees with the repo's actual remote — a
107+
# silently wrong owner means a silently wrong identity, which is worse than
108+
# no identity at all.
109+
local remote_owner
110+
remote_owner=$(git -C "$repo_path" remote get-url origin 2>/dev/null \
111+
| sed -nE 's#^(https?://[^/]+/|[^@]+@[^:]+:)([^/]+)/.*$#\2#p')
112+
if [[ -n "$remote_owner" && "$remote_owner" != "$owner" ]]; then
113+
echo "WARN: $repo_name — registry says owner='$owner' but origin says '$remote_owner'." >&2
114+
echo " The uuid is derived FROM the owner, so one of them is wrong." >&2
115+
echo " Set 'owner = \"$remote_owner\"' in repos.a2ml, or fix the remote. Skipping." >&2
116+
((FAILED++))
75117
return
76118
fi
77119

78-
# Generate deterministic UUID v5
79-
local uuid
80-
uuid=$(uuidgen --sha1 --namespace @url --name "github.com/hyperpolymath/$repo_name")
81-
82120
# Determine prefixed name
83121
local prefixed="${primary}-${repo_name}"
84122

@@ -94,7 +132,7 @@ deploy_clade() {
94132
[identity]
95133
uuid = "$uuid"
96134
primary-forge = "github"
97-
primary-owner = "hyperpolymath"
135+
primary-owner = "$owner"
98136
canonical-name = "$repo_name"
99137
prefixed-name = "$prefixed"
100138
@@ -105,9 +143,9 @@ assigned = "2026-03-16"
105143
rationale = "$description"
106144
107145
[forges]
108-
github = "hyperpolymath/$repo_name"
109-
gitlab = "hyperpolymath/$repo_name"
110-
bitbucket = "hyperpolymath/$repo_name"
146+
github = "$owner/$repo_name"
147+
gitlab = "$owner/$repo_name"
148+
bitbucket = "$owner/$repo_name"
111149
112150
[lineage]
113151
type = "$lineage"
@@ -148,8 +186,8 @@ echo "Repos dir: $REPOS_DIR"
148186
[[ "$DRY_RUN" == "--dry-run" ]] && echo "MODE: DRY RUN"
149187
echo ""
150188

151-
while IFS=$'\t' read -r name primary secondary lineage parent description; do
152-
deploy_clade "$name" "$primary" "$secondary" "$lineage" "$parent" "$description"
189+
while IFS=$'\t' read -r name primary secondary lineage parent description owner; do
190+
deploy_clade "$name" "$primary" "$secondary" "$lineage" "$parent" "$description" "$owner"
153191
done < <(parse_repos)
154192

155193
echo ""

sync/export-json.sh

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,19 @@ BEGIN { printf "[\n" }
2323
NR > 1 { printf ",\n" }
2424
{
2525
name = $1; primary = $2; secondary = $3; lineage = $4; parent = $5; desc = $6
26+
owner = ($7 != "" ? $7 : "hyperpolymath")
2627
gsub(/"/, "\\\"", desc)
27-
# Generate UUID deterministically
28-
cmd = "uuidgen --sha1 --namespace @url --name \"github.com/hyperpolymath/" name "\""
28+
# Generate UUID deterministically. The owner segment is part of the derived
29+
# name, so it is part of the identity; it was hardcoded to "hyperpolymath",
30+
# which produced a wrong uuid for any repo hosted elsewhere. Defaults to
31+
# "hyperpolymath", so entries without an explicit owner are unchanged.
32+
cmd = "uuidgen --sha1 --namespace @url --name \"github.com/" owner "/" name "\""
2933
cmd | getline uuid
3034
close(cmd)
31-
printf " {\"name\":\"%s\",\"uuid\":\"%s\",\"clade\":\"%s\",\"secondary\":%s,\"lineage\":\"%s\",\"parent\":\"%s\",\"description\":\"%s\",\"prefixed\":\"%s-%s\",\"github\":\"hyperpolymath/%s\"}", name, uuid, primary, secondary, lineage, parent, desc, primary, name, name
35+
# NB: no separate "owner" key — the schema is unchanged on purpose. The
36+
# github field already carries it as "owner/name", so entries without an
37+
# explicit owner serialise byte-identically to before this change.
38+
printf " {\"name\":\"%s\",\"uuid\":\"%s\",\"clade\":\"%s\",\"secondary\":%s,\"lineage\":\"%s\",\"parent\":\"%s\",\"description\":\"%s\",\"prefixed\":\"%s-%s\",\"github\":\"%s/%s\"}", name, uuid, primary, secondary, lineage, parent, desc, primary, name, owner, name
3239
}
3340
END { printf "\n]\n" }
3441
' > "$OUT/repos.json"

sync/parse-repos.sh

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
#!/usr/bin/env bash
22
# SPDX-License-Identifier: MPL-2.0
3-
# Parse repos.a2ml and emit TSV: name\tprimary\tsecondary\tlineage\tparent\tdescription
3+
# Parse repos.a2ml and emit TSV:
4+
# name\tprimary\tsecondary\tlineage\tparent\tdescription\towner
5+
#
6+
# `owner` is the forge owner/org that hosts the repo. It is OPTIONAL in
7+
# repos.a2ml and defaults to "hyperpolymath", so every existing entry parses
8+
# exactly as before and derives exactly the same uuid. Set it per-entry only for
9+
# repos hosted elsewhere:
10+
#
11+
# [repo.cadastra]
12+
# primary = "rm"
13+
# owner = "metadatastician"
14+
#
15+
# It is the LAST column: consumers that read only the first six fields are
16+
# unaffected by its presence.
417

518
# Default to this repo's seed file (script lives in sync/, seed in verisim/seed/).
619
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -10,14 +23,14 @@ awk '
1023
BEGIN { FS="=" }
1124
/^\[repo\./ {
1225
if (current_repo != "") {
13-
printf "%s\t%s\t%s\t%s\t%s\t%s\n", current_repo, primary, secondary, lineage, parent, description
26+
printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", current_repo, primary, secondary, lineage, parent, description, owner
1427
}
1528
line = $0
1629
gsub(/^\[repo\./, "", line)
1730
gsub(/\]$/, "", line)
1831
gsub(/"/, "", line)
1932
current_repo = line
20-
primary = ""; secondary = "[]"; description = ""; lineage = "standalone"; parent = ""
33+
primary = ""; secondary = "[]"; description = ""; lineage = "standalone"; parent = ""; owner = "hyperpolymath"
2134
}
2235
current_repo != "" && /^primary / {
2336
val = $2
@@ -44,9 +57,14 @@ current_repo != "" && /^parent / {
4457
gsub(/^ *"/, "", val); gsub(/".*$/, "", val)
4558
parent = val
4659
}
60+
current_repo != "" && /^owner / {
61+
val = $2
62+
gsub(/^ *"/, "", val); gsub(/".*$/, "", val)
63+
owner = val
64+
}
4765
END {
4866
if (current_repo != "") {
49-
printf "%s\t%s\t%s\t%s\t%s\t%s\n", current_repo, primary, secondary, lineage, parent, description
67+
printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", current_repo, primary, secondary, lineage, parent, description, owner
5068
}
5169
}
5270
' "$SEED_FILE"

0 commit comments

Comments
 (0)