diff --git a/sync/deploy-clade-a2ml.sh b/sync/deploy-clade-a2ml.sh index 8da43b5..9433a8f 100755 --- a/sync/deploy-clade-a2ml.sh +++ b/sync/deploy-clade-a2ml.sh @@ -44,6 +44,9 @@ deploy_clade() { local lineage="$4" local parent="$5" local description="$6" + # Forge owner/org hosting this repo. Optional in repos.a2ml; parse-repos.sh + # defaults it to "hyperpolymath", so existing entries are unaffected. + local owner="${7:-hyperpolymath}" local repo_path="$REPOS_DIR/$repo_name" @@ -68,17 +71,52 @@ deploy_clade() { return fi - # Skip if CLADE.a2ml already exists - if [[ -f "$repo_path/.machine_readable/CLADE.a2ml" ]]; then - echo "ALREADY: $repo_name" - ((ALREADY++)) + # Skip if CLADE.a2ml already exists. + # + # Check BOTH known layouts. Two are in use across the estate and the canon + # does not yet agree which is authoritative: + # .machine_readable/CLADE.a2ml — this script + the CLADE-001 contractile + # .machine_readable/descriptiles/CLADE.a2ml — rsr-template-repo + chronicles-of-slavia + # Checking only the first meant a repo using the descriptiles layout looked + # like it had no CLADE at all, so this script would write a SECOND one — two + # files, two identities, no error. That disagreement is unresolved and is the + # owner's to settle; until then, refusing to write over an existing + # declaration is the safe reading. + local existing + for existing in "$repo_path/.machine_readable/CLADE.a2ml" \ + "$repo_path/.machine_readable/descriptiles/CLADE.a2ml"; do + if [[ -f "$existing" ]]; then + echo "ALREADY: $repo_name (${existing#$repo_path/})" + ((ALREADY++)) + return + fi + done + + # Generate deterministic UUID v5. + # + # The owner segment is part of the derived name, so it is part of the + # IDENTITY — get it wrong and the repo gets a uuid that belongs to nothing. + # It was hardcoded to "hyperpolymath", which silently produced a wrong uuid + # for any repo hosted elsewhere (e.g. the metadatastician org). Now sourced + # from the registry entry, still defaulting to "hyperpolymath" so every + # existing entry derives byte-identically. + local uuid + uuid=$(uuidgen --sha1 --namespace @url --name "github.com/$owner/$repo_name") + + # Fail loudly if the registry disagrees with the repo's actual remote — a + # silently wrong owner means a silently wrong identity, which is worse than + # no identity at all. + local remote_owner + remote_owner=$(git -C "$repo_path" remote get-url origin 2>/dev/null \ + | sed -nE 's#^(https?://[^/]+/|[^@]+@[^:]+:)([^/]+)/.*$#\2#p') + if [[ -n "$remote_owner" && "$remote_owner" != "$owner" ]]; then + echo "WARN: $repo_name — registry says owner='$owner' but origin says '$remote_owner'." >&2 + echo " The uuid is derived FROM the owner, so one of them is wrong." >&2 + echo " Set 'owner = \"$remote_owner\"' in repos.a2ml, or fix the remote. Skipping." >&2 + ((FAILED++)) return fi - # Generate deterministic UUID v5 - local uuid - uuid=$(uuidgen --sha1 --namespace @url --name "github.com/hyperpolymath/$repo_name") - # Determine prefixed name local prefixed="${primary}-${repo_name}" @@ -94,7 +132,7 @@ deploy_clade() { [identity] uuid = "$uuid" primary-forge = "github" -primary-owner = "hyperpolymath" +primary-owner = "$owner" canonical-name = "$repo_name" prefixed-name = "$prefixed" @@ -105,9 +143,9 @@ assigned = "2026-03-16" rationale = "$description" [forges] -github = "hyperpolymath/$repo_name" -gitlab = "hyperpolymath/$repo_name" -bitbucket = "hyperpolymath/$repo_name" +github = "$owner/$repo_name" +gitlab = "$owner/$repo_name" +bitbucket = "$owner/$repo_name" [lineage] type = "$lineage" @@ -148,8 +186,8 @@ echo "Repos dir: $REPOS_DIR" [[ "$DRY_RUN" == "--dry-run" ]] && echo "MODE: DRY RUN" echo "" -while IFS=$'\t' read -r name primary secondary lineage parent description; do - deploy_clade "$name" "$primary" "$secondary" "$lineage" "$parent" "$description" +while IFS=$'\t' read -r name primary secondary lineage parent description owner; do + deploy_clade "$name" "$primary" "$secondary" "$lineage" "$parent" "$description" "$owner" done < <(parse_repos) echo "" diff --git a/sync/export-json.sh b/sync/export-json.sh index c8f5e4c..7399a36 100755 --- a/sync/export-json.sh +++ b/sync/export-json.sh @@ -23,12 +23,19 @@ BEGIN { printf "[\n" } NR > 1 { printf ",\n" } { name = $1; primary = $2; secondary = $3; lineage = $4; parent = $5; desc = $6 + owner = ($7 != "" ? $7 : "hyperpolymath") gsub(/"/, "\\\"", desc) - # Generate UUID deterministically - cmd = "uuidgen --sha1 --namespace @url --name \"github.com/hyperpolymath/" name "\"" + # Generate UUID deterministically. The owner segment is part of the derived + # name, so it is part of the identity; it was hardcoded to "hyperpolymath", + # which produced a wrong uuid for any repo hosted elsewhere. Defaults to + # "hyperpolymath", so entries without an explicit owner are unchanged. + cmd = "uuidgen --sha1 --namespace @url --name \"github.com/" owner "/" name "\"" cmd | getline uuid close(cmd) - 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 + # NB: no separate "owner" key — the schema is unchanged on purpose. The + # github field already carries it as "owner/name", so entries without an + # explicit owner serialise byte-identically to before this change. + 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 } END { printf "\n]\n" } ' > "$OUT/repos.json" diff --git a/sync/parse-repos.sh b/sync/parse-repos.sh index d2588e5..dfdf13f 100755 --- a/sync/parse-repos.sh +++ b/sync/parse-repos.sh @@ -1,6 +1,19 @@ #!/usr/bin/env bash # SPDX-License-Identifier: MPL-2.0 -# Parse repos.a2ml and emit TSV: name\tprimary\tsecondary\tlineage\tparent\tdescription +# Parse repos.a2ml and emit TSV: +# name\tprimary\tsecondary\tlineage\tparent\tdescription\towner +# +# `owner` is the forge owner/org that hosts the repo. It is OPTIONAL in +# repos.a2ml and defaults to "hyperpolymath", so every existing entry parses +# exactly as before and derives exactly the same uuid. Set it per-entry only for +# repos hosted elsewhere: +# +# [repo.cadastra] +# primary = "rm" +# owner = "metadatastician" +# +# It is the LAST column: consumers that read only the first six fields are +# unaffected by its presence. # Default to this repo's seed file (script lives in sync/, seed in verisim/seed/). SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -10,14 +23,14 @@ awk ' BEGIN { FS="=" } /^\[repo\./ { if (current_repo != "") { - printf "%s\t%s\t%s\t%s\t%s\t%s\n", current_repo, primary, secondary, lineage, parent, description + printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", current_repo, primary, secondary, lineage, parent, description, owner } line = $0 gsub(/^\[repo\./, "", line) gsub(/\]$/, "", line) gsub(/"/, "", line) current_repo = line - primary = ""; secondary = "[]"; description = ""; lineage = "standalone"; parent = "" + primary = ""; secondary = "[]"; description = ""; lineage = "standalone"; parent = ""; owner = "hyperpolymath" } current_repo != "" && /^primary / { val = $2 @@ -44,9 +57,14 @@ current_repo != "" && /^parent / { gsub(/^ *"/, "", val); gsub(/".*$/, "", val) parent = val } +current_repo != "" && /^owner / { + val = $2 + gsub(/^ *"/, "", val); gsub(/".*$/, "", val) + owner = val +} END { if (current_repo != "") { - printf "%s\t%s\t%s\t%s\t%s\t%s\n", current_repo, primary, secondary, lineage, parent, description + printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", current_repo, primary, secondary, lineage, parent, description, owner } } ' "$SEED_FILE"