Skip to content

Commit 58df095

Browse files
feat(aggregator): estate organization — manifest, thread runner, generated superproject (#63)
## What Establishes the **one-aggregator** organization for the ~300-repo estate so the dual-layout confusion (flat clones vs. stale submodule superproject) is removed. **The rule:** flat clones in `$REPOS_DIR` = single source of truth · `reposystem` = coordination layer · `repos-monorepo` = generated output (Pages/mirror/governance keep working). ## Changes - `repos.toml` — generated manifest of all 297 estate repos (`just repos-manifest`) - `repos.groups.toml` — hand-maintained epic/group index, survives regeneration - `scripts/thread.sh` — cross-repo thread runner; each repo merges via its own PR - `scripts/sync-aggregator.sh` — regenerates `repos-monorepo` as generated artifact; per-submodule walk survives the URL-less `.git-private-farm` entry - `scripts/gen-repos-manifest.sh` — `.gitmodules` → `repos.toml` generator - `ESTATE-ORGANIZATION.adoc` — the rule + migration checklist - `Justfile` — new recipes; **fixes a pre-existing parse-breaking dedent** in `doctor` that was breaking the entire Justfile ## Notes - No `STATE`/`contractiles`/`.bot_directives` changes. - Verified: `just --list` parses; `just repos-manifest` → 297 entries; `sync-aggregator` dry-run no longer aborts on `.git-private-farm`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6a9bf91 commit 58df095

10 files changed

Lines changed: 2446 additions & 8 deletions

File tree

.machine_readable/6a2/STATE.a2ml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ repo-batcher = "2026-05-18 — scaffoldia/repo-batcher: real `just e2e` ran agai
3737
[recent-changes]
3838
- 2026-05-18: repo-batcher #56 from-scratch build-out — L1–L4 ATS2 core (just tc-ats2=0), L5 self-contained C archive (ATS_DYNLOADFLAG 0 link fix, just ats2-lib=0), L6 Idris2 typed ABI w/ machine-checked theorems (just abi-check=0), L7 patscc-linked Zig CLI binding the genuine ATS2 C ABI, L8 fixture-backed just e2e=0. Independently fiction-reviewed from a clean clone. PR #59 merged (4c2d29f); issue #56 CLOSED-COMPLETED.
3939
- 2026-05-18: docs-truth — README/IMPLEMENTATION-STATUS/ARCHITECTURE corrected from the inverse anti-fiction defect ("still a stub") to the verified L1–L8 reality (PR #61). "No V ever" retained as true history.
40+
- 2026-05-18: Estate-aggregator organization landed (PR #63, branch feat/estate-aggregator-organization)
41+
- repos.toml (generated, 297 repos) + repos.groups.toml (hand-maintained epic index)
42+
- scripts/{gen-repos-manifest,thread,sync-aggregator,fix-stale-submodule-urls}.sh
43+
- Justfile: repos-manifest/thread/sync-aggregator/aggregator-drift recipes + fixed
44+
pre-existing parse-breaking dedent in the doctor recipe
45+
- ESTATE-ORGANIZATION.adoc (linked from README) defines the source-of-truth rule:
46+
flat clones authoritative, reposystem coordinates, repos-monorepo is generated
4047
- 2026-04-04: Added comprehensive test suite (E2E, property-based, aspect, benchmarks)
4148
- E2E tests: 20 tests covering CLI smoke, scan operations, config, security
4249
- Property tests: 12 tests for determinism, consistency, graph properties
@@ -50,6 +57,11 @@ repo-batcher = "2026-05-18 — scaffoldia/repo-batcher: real `just e2e` ran agai
5057
# #56 CLOSED-COMPLETED). Docs-truth follow-up PR #61 merged.
5158
# STATE.a2ml full refresh — factual fields updated; CRG grade and
5259
# test count NOT fabricated (formal audit / re-run still required).
60+
# 2026-05-18: Estate-aggregator organization. One-source-of-truth rule (flat
61+
# clones), repos.toml manifest + thread runner + generated
62+
# repos-monorepo via sync-aggregator. PR #63. Pointer reconcile
63+
# (sync-aggregator --push) executed; snif->snifs rename class found,
64+
# fix-stale-submodule-urls.sh added. ml-dsa Dependabot bump queued.
5365
# 2026-04-25: D→C blocker sweep. EXPLAINME.adoc rewritten (15 fabricated paths
5466
# removed, 31 verified-real paths added). 24 per-directory README.adoc
5567
# files added across all subprojects.

ESTATE-ORGANIZATION.adoc

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
= Estate Organization: one aggregator, threads off it
4+
:toc: preamble
5+
6+
== The rule
7+
8+
There are three layers and exactly one is the source of truth.
9+
10+
[cols="1,3,3",options="header"]
11+
|===
12+
| Layer | Path | Role
13+
| Flat clone root | `$REPOS_DIR` (default `~/dev/repos/<repo>`) | *Single source of truth.* All work and branches happen here, in ordinary independent clones. Per-repo PRs/issues unchanged.
14+
| Aggregator | `reposystem` (this repo) | Coordination layer over the clones: the `repos.toml` index + the `thread` runner. Owns no code history; pins nothing.
15+
| Published artifact | `repos-monorepo` | *Generated output.* Submodule pointers are an auto-bumped index that keeps GitHub Pages, the GitLab/Bitbucket mirror, and the governance workflows working.
16+
|===
17+
18+
Never `cd` into `repos-monorepo` to edit code. Never hand-resolve a submodule
19+
there. It is regenerated, not maintained.
20+
21+
== Files
22+
23+
* `repos.toml` — *generated* from `repos-monorepo/.gitmodules` by
24+
`scripts/gen-repos-manifest.sh`. Regenerate with `just repos-manifest`.
25+
NOTE: `0-AI-MANIFEST.a2ml` prefers generated files out of source control;
26+
`repos.toml` is a *deliberate exception* — it is the coordination index and
27+
must be present on a fresh clone / in CI without a regeneration step. It
28+
carries an SPDX header and is fully reproducible from the documented command.
29+
* `repos.groups.toml` — *hand-maintained*. Names the repo sets a cross-repo
30+
thread spans (epics). Survives manifest regeneration.
31+
32+
== Cross-repo threads (the merge model)
33+
34+
A thread is one logical change across N repos. Each repo still merges through
35+
its *own normal PR* — the runner only orchestrates the fan-out.
36+
37+
[source,sh]
38+
----
39+
just thread resolve --kind julia # who is in scope
40+
just thread start standards-130 --repos echo-types,affinescript # branch per clone
41+
just thread pr standards-130 --refs standards#130 # one PR per repo
42+
just thread status standards-130 --repos echo-types,affinescript # CI/merge state
43+
just thread land standards-130 --repos echo-types,affinescript --yes
44+
----
45+
46+
Selectors: `--group <name>` (from `repos.groups.toml`), `--kind <kind>` (from
47+
`repos.toml`), or `--repos a,b,c`. `start` skips dirty worktrees; `land`
48+
refuses without `--yes` and only merges PRs GitHub reports `MERGEABLE`.
49+
50+
== Keeping the published artifact current
51+
52+
[source,sh]
53+
----
54+
just sync-aggregator # DRY-RUN: show pointers that would bump
55+
just sync-aggregator --commit # record the bump
56+
just sync-aggregator --push # record + trigger Pages deploy + mirror
57+
----
58+
59+
Submodules are updated one at a time (a single `git submodule update
60+
--recursive` aborts on the URL-less `.git-private-farm` entry — see
61+
`scripts/gitlink-drift.jl`). Run after `thread land`, or on a schedule, so the
62+
artifact never drifts.
63+
64+
== Drift alarm
65+
66+
[source,sh]
67+
----
68+
just aggregator-drift # classify gitlink drift across the estate (A2ML)
69+
----
70+
71+
== Warned submodules after a sync
72+
73+
`sync-aggregator` leaves un-fetchable submodules as `warn ... (left as-is)`
74+
rather than aborting. `scripts/fix-stale-submodule-urls.sh` classifies them
75+
into four classes and remediates only on explicit, separately-gated flags:
76+
77+
[cols="1,3,2",options="header"]
78+
|===
79+
| Class | Cause | Remediation
80+
| `RENAMED` | Repo renamed; `.gitmodules` URL stale (API redirect resolves elsewhere). | `--apply-renames` rewrites the URL.
81+
| `NONEXISTENT` | `.gitmodules` over-declares a repo that was never created (API 404). URL is already basename-correct, so a rewrite cannot help. | `--prune-nonexistent` removes the declaration (*destructive*).
82+
| `WIKI` | `.wikis/<repo>.wiki` entry with no enabled wiki — not tracked content. | `--prune-nonexistent` (labelled `WIKI`).
83+
| `TRANSIENT` | Repo exists and URL is canonical — a fetch blip. | Re-run `just sync-aggregator`.
84+
|===
85+
86+
[source,sh]
87+
----
88+
sh scripts/fix-stale-submodule-urls.sh # classify (dry-run)
89+
sh scripts/fix-stale-submodule-urls.sh --apply-renames # safe: URL rewrites
90+
sh scripts/fix-stale-submodule-urls.sh --prune-nonexistent # destructive: prune
91+
----
92+
93+
It refuses to run while `sync-aggregator` holds the aggregator git lock, and
94+
never commits/pushes — re-run `just sync-aggregator --push` afterwards.
95+
96+
== Migration status
97+
98+
. [x] Rule declared (this document).
99+
. [ ] One-shot pointer reconcile: `just sync-aggregator --push` (fixes every
100+
stale/empty gitlink — incl. `echo-types` — in one commit). *Run deliberately;
101+
it fetches ~300 repos.*
102+
. [x] `repos.toml` generated from `.gitmodules` (297 repos).
103+
. [x] `repos.groups.toml` stub + `thread` / `sync-aggregator` /
104+
`aggregator-drift` recipes.
105+
. [ ] Schedule `sync-aggregator --push` (cron/CI) so the artifact self-heals.
106+
. [ ] Optional: have `reposystem scan` assert manifest⇔clone parity.

Justfile

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -414,14 +414,14 @@ doctor:
414414
check "git" git "2.40"
415415
check "Rust (cargo)" cargo "1.80"
416416
check "Zig" zig "0.13"
417-
# Optional tools
418-
if command -v panic-attack >/dev/null 2>&1; then
419-
echo " [OK] panic-attack — available"
420-
PASS=$((PASS + 1))
421-
else
422-
echo " [WARN] panic-attack — not found (pre-commit scanner)"
423-
WARN=$((WARN + 1))
424-
fi
417+
# Optional tools
418+
if command -v panic-attack >/dev/null 2>&1; then
419+
echo " [OK] panic-attack — available"
420+
PASS=$((PASS + 1))
421+
else
422+
echo " [WARN] panic-attack — not found (pre-commit scanner)"
423+
WARN=$((WARN + 1))
424+
fi
425425
echo ""
426426
echo " Result: $PASS passed, $FAIL failed, $WARN warnings"
427427
if [ "$FAIL" -gt 0 ]; then
@@ -447,3 +447,34 @@ crg-badge:
447447
D) color="orange" ;; E) color="red" ;; F) color="critical" ;; \
448448
*) color="lightgrey" ;; esac; \
449449
echo "[![CRG $$grade](https://img.shields.io/badge/CRG-$$grade-$$color?style=flat-square)](https://github.com/hyperpolymath/standards/tree/main/component-readiness-grades)"
450+
451+
452+
# ============================================================================
453+
# ESTATE AGGREGATOR (flat clones = source of truth; this is the coordination
454+
# layer; repos-monorepo is generated output). See ESTATE-ORGANIZATION.adoc.
455+
# ============================================================================
456+
457+
# Regenerate repos.toml from the superproject .gitmodules (groups preserved).
458+
repos-manifest:
459+
@sh scripts/gen-repos-manifest.sh
460+
@echo "✓ repos.toml regenerated (epic groups live in repos.groups.toml)"
461+
462+
# Fan a cross-repo thread over the flat clones. Each repo merges via its own PR.
463+
# just thread resolve --kind julia
464+
# just thread start standards-130 --repos echo-types,affinescript
465+
# just thread pr standards-130 --refs standards#130
466+
# just thread status standards-130 --repos echo-types,affinescript
467+
# just thread land standards-130 --repos echo-types,affinescript --yes
468+
thread *ARGS:
469+
@sh scripts/thread.sh {{ARGS}}
470+
471+
# Regenerate repos-monorepo's submodule index from pushed clone HEADs.
472+
# DRY-RUN by default; pass --commit or --push.
473+
# just sync-aggregator # preview
474+
# just sync-aggregator --push # record + trigger Pages/mirror
475+
sync-aggregator *ARGS:
476+
@sh scripts/sync-aggregator.sh {{ARGS}}
477+
478+
# Drift alarm: classify submodule-gitlink drift across the estate (A2ML out).
479+
aggregator-drift:
480+
@julia scripts/gitlink-drift.jl "$${REPOS_DIR:-$$HOME/dev/repos}/repos-monorepo"

README.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,21 @@ just web-serve
2727

2828
Open `http://localhost:801` in a browser, then load `./export.json`.
2929

30+
== Estate Organization
31+
32+
Reposystem is the *coordination layer* for the ~300-repo estate: flat clones
33+
in `$REPOS_DIR` are the single source of truth, and `repos-monorepo` is a
34+
generated artifact. The rule, the cross-repo `thread` runner, the generated
35+
`repos.toml` manifest, and the migration checklist are documented in
36+
link:ESTATE-ORGANIZATION.adoc[ESTATE-ORGANIZATION.adoc].
37+
38+
[source,bash]
39+
----
40+
just repos-manifest # regenerate repos.toml from .gitmodules
41+
just thread status <name> --group X # cross-repo thread state
42+
just sync-aggregator --push # regenerate repos-monorepo (Pages/mirror)
43+
----
44+
3045
== Web UI (Prototype)
3146

3247
The `web/` folder contains a no-build static UI for the graph:

repos.groups.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
#
4+
# repos.groups.toml — HAND-MAINTAINED. Not generated; survives `just repos-manifest`.
5+
#
6+
# A "group" names a set of repos a cross-repo thread spans. The thread runner
7+
# (scripts/thread.sh) resolves --group <name> to this list, then operates on
8+
# the flat clones in $REPOS_DIR. Each repo still merges via its own normal PR;
9+
# this file is just the index of "what a thread touches".
10+
#
11+
# Add groups as epics arise. Example shape below — edit freely.
12+
13+
[groups]
14+
15+
# Epic example: an estate standards rollout touching a few repos.
16+
# standards-130 = ["echo-types", "affinescript", "standards"]
17+
18+
# Convenience buckets (optional; the runner also accepts --kind julia etc.
19+
# resolved straight from repos.toml, so you only list a group here when the
20+
# membership is NOT derivable from `kind`).
21+
# proof-estate = ["echo-types", "affinescript", "my-lang", "gossamer"]

0 commit comments

Comments
 (0)