split: cut the community layer over to its own repository - #2266
Merged
Conversation
AI verification: passedClaude reviewed the data changes and found nothing concerning. This is advisory; a maintainer still reviews before merge. |
AI verification: passedClaude reviewed the data changes and found nothing concerning. This is advisory; a maintainer still reviews before merge. |
AI verification: flaggedClaude flagged the following for a maintainer to check (advisory - not a merge block):
|
The core half of the community-repo split. data/works-community/ - 3,049 works, 154 packs, the whole CC BY-SA layer - now lives in KodeStar/audiosilo-meta-community, and this repository is the CC0 core: works, people, series and redirects.json. The layer is DELETED here, not copied: the community repo's tree was verified byte-identical to this one's (diff -r against a fresh clone at 88ddce2, no differences) before a single file was removed, and the release build composes the two back into one artifact. CORE IS ProfileCore WHEREVER IT NAMES A PROFILE. check.yml, the intake bot's re-render pass and release.yml's core-side validation all pass --profile core explicitly; the CLI DEFAULTS stay `all`, because "a data root" with nothing said must keep meaning the whole database (which is what every fixture is). The teeth are the file accounting: under `core` a works-community/ directory that reappears here - a bad merge, a stray revert, a misdirected contribution - is an unrecognized location and a red check, rather than a family silently re-adopted. release.yml composes the pair: it checks out audiosilo-meta-community at main beside this tree (public, shallow, no secret) and builds with `metabuild -data data --community community/data`. Everything downstream is untouched - same asset contract, same patch chain, same data-release concurrency group, same webhook - and the notes now name both composed SHAs, since one sha no longer identifies an artifact. A `community-data` repository_dispatch joins the triggers, because a merge in the other repository cannot push to this one's main. The two sidecar issue forms go with the layer. Their COMPOSER stays (internal/issueform, TemplateFromLabels, compose_sidecar.go): the community repo runs this very cmd/metaissue under the community profile, so deleting the code would break the tree the forms moved to. ISSUE_TEMPLATE/config.yml carries a contact link pointing a sidecar contributor there. internal/repair and internal/audit gain an Options.Profile (zero value ProfileAll, so every existing caller is unchanged) threaded through the store, the post-write format pass and the post-write validation, with --profile flags on both CLIs. writeFamiliesIn narrows the repair's write list to the families the root holds, which is not tidiness: pack.OpenForProfile REFUSES a named family the profile disclaims, and under `core` works-community is one. Nothing else about a wave moves - no sidecar loads from a core tree, so the sidecar-moving arm of a merge has nothing to move. EQUIVALENCE PROOF, at the same content: metabuild over pre-cutover main as one tree, and metabuild over this branch's core tree plus a clone of the community repo via --community, with --built-at pinned so the wall-clock stamp is not the only difference. Both artifacts are 76ce2cd5e725db2660b7d43263e8005816c2e4088ed7c63279d7d859189a7d23. Without the pin the two differ in exactly one row, meta.built_at, and in nothing else (every other table hashes equal).
addCharactersIssueUrl and addRecapsIssueUrl built new-issue URLs against ISSUE_BASE - this repository - naming templates that moved out with the layer they contribute to. Left alone, every "add characters" and "add recaps" button on meta.audiosilo.app would land a contributor on a template chooser with no such form on it. They now build against COMMUNITY_ISSUE_BASE. Nothing else about them moves: the same template names, the same work_ref carrying a meta.audiosilo.app work URL, read by the same internal/issueform resolveWorkRef - which the community repo's intake bot runs, since it runs this repo's cmd/metaissue. These two are the only issue URLs on the site that do not point at core, so the existing host/path assertion is the guard and now pins the community path explicitly.
Found by smoke-testing `metarepair --profile core` over the real post-cutover tree: every merge died with read works-community entry "1984": family "works-community" is not in the core tree profile (this root holds people, series, works) The merge planner asks every loser whether it carries a works-community member, unconditionally. Under `core` that family is not in the root's profile, so pack.Store.Get refuses it by name - correctly, since a misdirected read or write must be loud - and narrowing the WRITE list was not enough, because the read happens first. The plan's view now marks such a family out-of-profile and reads EMPTY. That is not a suppression: "this tree holds no such family" and "this tree holds no such entry" are the same answer to a merge asking whether the loser carries a sidecar, and it is the answer a whole-database store already gives for an absent family. Only the READ is gated - a put or a del still goes to the store and is still refused by name, so a write that somehow found something to move stays loud. Pinned as PARITY: the same fixture merged under both profiles must produce the same report and the same tree, because over a core tree they are two readings of one catalogue. Confirmed to fail without the fix. The two profiles also agree over the real tree - both plan 5 applied / 0 refused / 6 tombstones on the same wave.
The internal/repair entry described writeFamiliesIn but not the half that turned out to be load-bearing: the plan's view reads an out-of-profile family EMPTY, because the merge planner asks every loser about a sidecar unconditionally and a refused read killed every merge over the real core tree.
The read-empty fix made merges plan again over a core tree, and in doing so
made the sidecar-member-collision refusal STRUCTURALLY BLIND. That refusal is
the human-decision guard over the most expensive data in the project - both
halves of a duplicate carrying the same characters or recaps member - and it
is a question about a family this repository no longer holds. Answering it
"no sidecars" is not a quiet degradation: a wave folds two sidecar-carrying
works together, the loss surfaces much later as a release-blocking
LoadComposed collision, and nothing connects it to the wave that caused it.
Measured over the real pair, the first 30 non-advisory merge proposals are 25
sidecar collisions. A blind wave would have destroyed 25 CC BY-SA sidecars.
The guard now has three modes and never infers safety (sidecarSource):
in-tree the root holds works-community - the pre-split shape, read and
written exactly as before.
read-only --community names the community checkout's data/. The question
is answered from there and the refusal works exactly as it did
pre-split, but nothing is written to another repository: the
members keep their retired keys and ride the slug tombstone to
the survivor (the compose-time re-key) until the community
re-key sweep lands them. The applied record says so.
unknown neither - every merge-works proposal is refused as
community-data-required, naming the flag. A refused merge is
recoverable; a folded sidecar pair is not.
Staging is identical in all three, deliberately: a read-only run still stages
the merged entry, which is the only thing that catches two clusters folding
two characters-carrying works onto one target. view.queue is the single place
the modes part.
openCommunity refuses at the door a --community root the tree already holds
the family for, and one carrying no works-community packs - metabuild's own
rule, because pointing the flag at the community repo's top level rather than
its data/ would answer "no sidecars anywhere" for every cluster, which is the
original blindness wearing the flag that was meant to end it.
metarepair and metaaudit now DEFAULT --profile to core, unlike metacheck and
metafmt, and the axis is the cost of being wrong. Those two are read by CI
with the flag spelled out and a wrong reading is a red check. These are
pointed at this tree by a human typing a command, one of them deletes
records, and under `all` over a core tree the family is present-but-empty -
the blind case again, reached through the default. The pair must also always
agree, since metarepair re-runs metaaudit's detectors.
Verification: the split pair and the whole pre-cutover tree plan the same
wave - 5 applied / 25 refused / 5 tombstones either way - and the fixture
test pins the same decision AND the same database, read through
check.LoadComposed so the tombstone and the compose-time re-key are what make
it pass. Every new refusal was confirmed to fail without its guard.
Three fixes to the two-repository build. TAG COLLISION. The tag was date + the core short sha, which stopped being unique the moment a community merge could trigger a release: a community-data dispatch on a day that already released leaves core's HEAD unmoved, so the second run composes a genuinely different artifact under a tag that already exists and gh release create fails - and two community merges in one day fail the same way. The tag now carries both short SHAs. Consumers are unaffected: nothing reads the tag's shape, since metaserve's latestDataRelease and this workflow's own prev lookup both select by asset presence at the maximum published_at, so older single-sha releases stay valid and selectable. Build identity is computed up front and every step is guarded on it, so a tag that already exists - the same day, the same pair, genuinely nothing new - skips for one API call instead of a full compose. PR-TIME CROSS-TREE SIGNAL. check.yml gains a compose job running the exact build release.yml performs. The cross-tree rules cannot be asked of one root, so until now they ran only after merge: a core PR that retired or deleted a sidecar-keyed work went green and broke the release stream for everyone, with the fix needing a second PR. It should be required, not advisory - being the thing that stops the merge is its whole purpose. TWO CONFIRMED NITS. The composed SHAs are assigned before they are echoed: as echo "x=$(git ...)", the substitution's failure is discarded because echo's own status is what the shell sees, so a broken community checkout would have published a release recording an empty community sha - unreproducible, and silently so. And the push paths filter gains pkg/check/** and pkg/pack/**, which now shape artifact CONTENT rather than only validating it (resolveSidecarKeys re-keys sidecars; the collision keeper decides what ships). An extra release on a rule tweak costs a build; a wrong-keying fix sitting unreleased costs every consumer. actionlint clean. GitHub Actions cannot be run here, so both workflows are reviewed-only and the diffs are kept minimal and commented.
The review found this class dangerous rather than untidy, and it is: core's AUTHORING.md walked a contributor through adding a file under data/works-community/, which is now an unrecognized location here. Following our own guide produced a pull request our own CI refuses. AUTHORING.md, EXTRACTION.md and EXTRACTION-AUDIO.md become short pointer stubs at the community repository, which is where they live. Not copies: keeping the text in two places drifts, and the drift is exactly what walks somebody into the red check. The metaextract TOOL stays here; only the process docs moved, and the two Go doc comments that cited AUTHORING.md now say where it is. The site must not name two repositories in one panel. BuildTool's authoring link and its "pull request instead" hand-off, and contribute.astro's authoring link, now point at the community repo - the issue prefills beside them already did, so the panel was telling a contributor to attach the file there and open a pull request here. ai-verify.sh's prompt described a four-family tree and carried the whole sidecar review rubric. It now describes three families, requires CC0-1.0 of every record, and treats any data/works-community/ path or CC-BY-SA-4.0 record in a diff HERE as a finding in itself. The sidecar length, spoiler and position rules went with the layer, to the reviewer that sees diffs which can actually contain one; the two rules in that block that were never about sidecars are kept. CONTRIBUTING.md and README.md's gate blocks pass --profile core with one sentence on why: CI runs those exact commands, and ProfileAll is green precisely where CI is red. LICENSING.md's "moved with it" is now true.
Completes the cutover's intake half on top of #2265's profile gating. The two sidecar forms left with the layer, but a data:characters or data:recaps LABEL can still arrive - a maintainer applying one by hand, a bookmarked prefill URL, the sibling contributor tool - and routing is by label, not by which forms this repository ships. Under the default profile the composer would then write a works-community entry into a family this root does not hold: a green-looking bot pull request putting CC BY-SA content in the CC0 repo, and permanently unmergeable, since CI validates --profile core. intake.yml now passes --profile core to metaissue, so #2265's templateBelongsElsewhere refuses the submission as needs-human and the comment names the community repository's issue chooser - the mirror of the message that sends a core form the other way. Verified end to end against the real tree: a data:recaps issue returns needs-human naming audiosilo-meta-community, where the same run without the flag walks past the gate entirely. Nothing was missing on the Go side: #2265 derives the rule from each template's writeFamilies (so it cannot drift from what the composer would write), its message already names the community repo for this direction, and TestCoreProfileSendsASidecarFormToTheCommunityRepo already pins it. The rebase sweep runs no metaissue - only metafmt and metacheck, both already core-profile. CLAUDE.md records the intake gate and adds metaissue to the tools whose default stays 'all' with CI naming the profile, beside the approved metaaudit/metarepair exception.
KodeStar
force-pushed
the
split/core-cutover
branch
from
August 21, 2026 18:21
d073618 to
b5f37f6
Compare
AI verification: flaggedClaude flagged the following for a maintainer to check (advisory - not a merge block):
|
…t cannot move, finish the tense sweep Simplify pass over the cutover. view.get's nil-read branch answered EMPTY - the superseded read-empty behavior surviving as a silent fallback one forgotten sidecarUnknown check away from regaining the blindness the three modes ended - so it errors now, and the field doc states the contract. The compose CI job runs on pull requests only (push-to-main composes again minutes later as the release build, the authoritative gate) and self-skips unless the PR touches a path that can move its answer, probed via the PR files API rather than a history-deep clone. Category declaration order matches Categories()'s triage order; the red-tree advice names the run's own profile; openCommunity's door comment owns the packs-vs-loadable-entries gap against metabuild's rule; the staging rationale has one home (view.queue). The tense sweep the cutover itself demanded: pkg/pack/profile.go's canonical header, sources.go, metabuild's and compose.go's headers all describe the POST-split world (and profile.go now carries the CLI default asymmetry by the cost-of-being-wrong axis); README's metabuild snippets say core-only and name --community; CLAUDE.md's metabuild entry credits the compose job beside the release build, the repair entry scopes the modes-part claim to the WRITE paths and drops the phantom title-carries-no-identity category (that veto is internal/audit's, inherited through the fresh-audit gate).
AI verification: flaggedClaude flagged the following for a maintainer to check (advisory - not a merge block):
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The core cutover of the community-repo split (
.claude/community/PLAN.md,migration order steps 4-5).
data/works-community/- 3,049 works, 154 packs, thewhole CC BY-SA layer - leaves this repository for
KodeStar/audiosilo-meta-community,
and the release build composes the two checkouts back into one artifact.
Community-side companion: KodeStar/audiosilo-meta-community#2 (the
notify-core.ymldispatch). Neither PR is merged; they should land together,core first.
Divergence check (before deleting anything)
diff -rof this repo'sdata/works-community/against a fresh shallow clone ofaudiosilo-meta-communityat88ddce29eda9249dd8ef9a9c9c55bed3f4c69922:no differences. The community repo is byte-identical to what is deleted here,
verified rather than assumed.
The equivalence proof (the cutover's acceptance test)
At the same content, two artifacts:
metabuild -data <pre-cutover data/> --built-at 2026-08-21T00:00:00Z -o A.sqlitemetabuild -data data --community <clone>/data --built-at 2026-08-21T00:00:00Z -o B.sqliteByte-identical. (APFS clone of the pre-cutover
data/in a scratch dir;1.67GB each; 277,628 works / 291,553 recordings / 123,118 people / 45,172 series
/ 43,203 characters / 19,065 recaps on both sides, and identical advisory
censuses.)
--built-atis pinned because it is the one thing that legitimately differsbetween two runs. Run unpinned, the first pair came out
b8bd980…vsaeb35b2…- and a table-by-table comparison found the differencein exactly one row,
meta.built_at(a wall-clock stamp), with every othertable (
works,recordings,people,series,characters,character_aliases,recaps,recap_summaries,chapters,work_genres,work_isbns,work_authors,series_works,series_authors,recording_asins,recording_isbns,recording_narrators,redirects, and allfive
search_fts_*shadow tables) hashing equal. Worth recording, because it isthe shape a future re-proof will hit too.
What changed
1. The data
git rm -r data/works-community/- 154 packs, ~634k lines. Nothing else indata/is touched.2. Core runs as
ProfileCorewherever it names a profilecheck.yml:metacheck --profile core,metafmt --check --profile core.intake.yml: all threemetafmt --write/metachecksites (the libex-fillstep and both arms of the rebase sweep) pass
--profile core.release.yml: core-side validation ismetacheck --profile core; thecross-tree rules run in the compose, which is the authoritative gate.
scripts/README.mdandscripts/pack-union-merge.sh's usage recipes.internal/repairandinternal/auditgainOptions.Profile(see 5).
CLI defaults are unchanged - every
--profilestill defaults toall, andthat is deliberate: "a data root" with nothing said must keep meaning the whole
database, which is what the fixtures and most tests are. Changing a default to
fit one repository's shape would silently redefine every other caller's silence.
CI says
coreout loud instead.The teeth are the file accounting, which was already total: under
coreaworks-community/root stops being a family root, so every file under it is anunrecognized location. A reappearing copy here - a bad merge, a stray revert, a
misdirected contribution - is a red check, not a family silently re-adopted.
3.
release.ymlcomposes the pairactions/checkoutofKodeStar/audiosilo-meta-communityatmaininto
community/. Public repo, sogithub.tokensuffices and no secret isinvolved; shallow for the same reason the core checkout is.
metabuild -data data --community community/data -o meta.sqlite.Record the composed SHAsstep, and the release notes now name bothSHAs - one sha no longer identifies an artifact.
repository_dispatchtrigger,types: [community-data], so a community-sidemerge can cut a release.
Unchanged on purpose: the asset contract (
meta.sqlite.gz+meta.sqlite.gz.sha256+meta.sqlite.sha256+ the best-effortmeta.sqlite.patch.from-<PREV_TAG>.zst), the prev-release selection, thedata-releaseconcurrency group, the tag scheme, and the signed metaservewebhook. metaserve, the poller and the patch chain see nothing new.
Not added:
pkg/check/**to thepaths:filter. The filter names whatchanges the artifact; the compose rules there change whether a release
passes, not what it contains. Flagging it in case you disagree.
4. Intake side of core
.github/ISSUE_TEMPLATE/add-characters.ymlandadd-recaps.ymldeleted (theylive in the community repo).
config.ymlgains a contact link pointing a sidecar contributor there, so theforms do not simply vanish for someone who lands here.
intake.ymlhad no template-specific routing branches to remove - routingis entirely
issueform.TemplateFromLabels, by design. Two stale comments thatnamed
add-charactersare corrected.internal/issueform/compose_sidecar.go, thefSidecar*constants and
TemplateFromLabels' characters/recaps rows are all untouched:the community repo runs this
cmd/metaissueunder the community profile, sodeleting the code would break the tree the forms moved to.
TestFieldLabelsExistInTemplatesloses its two sidecar cases (it reads thetemplate files, which are no longer here) with a comment saying where that
drift guard now belongs.
TestTemplateFromLabelsstill pins thecharacters/recaps routing, because the routing is still live.
5. Profile threading in code
internal/repair(Options.Profile, zero valueProfileAllso every existingcaller is byte-identical) is threaded through all three places that read or write
the tree - the store, the post-write
internal/formatpass, and the post-writepkg/checkvalidation - because a pass that healed under one profile and wasthen judged under another would report a file it had just been told not to touch.
writeFamiliesInnarrows the write list to the families the profile holds. Thatis not tidiness:
pack.OpenForProfilerefuses a named family the profiledisclaims, and under
core,works-communityis one. Nothing else about a wavemoves - no sidecar loads from a core tree, so the sidecar-moving arm of a merge
has nothing to move, and
sidecar-member-collisionsimply stops occurring here.internal/auditgets the same field for one reason:metarepairre-runsmetaaudit's detectors in process, so the two must be told the same thingabout the same root or a report is taken under one reading and applied under
another. Both CLIs gain
--profile.Deliberately not threaded:
internal/importer(itswriteFamiliesneverincluded works-community) and
internal/issueform(per-template family gating,and its profile is the parallel community-intake task's business).
This threading found a real bug (third commit). Smoke-testing
metarepair --profile coreover the real post-cutover tree, every merge died:The merge planner asks every loser whether it carries a works-community member,
unconditionally - and
pack.Store.Getrefuses an out-of-profile family byname, correctly, since a misdirected read or write must be loud. Narrowing the
write list was not enough: the read happens first. The plan's
viewnow markssuch a family out-of-profile and reads empty - "this tree holds no such
family" and "this tree holds no such entry" are the same answer to that question,
and it is the answer a whole-database store already gives for a merely absent
family. Only the READ is gated; a
put/delstill reaches the store and isstill refused by name.
Pinned as parity, not as "it does not crash":
TestMergeWorksUnderTheCoreProfileMatchesTheDefaultmerges one fixture underboth profiles and requires the same report and the same tree - confirmed to fail
without the fix. The two profiles also agree over the real tree: both plan
5 applied / 0 refused / 6 tombstoneson the same--op merge-works --limit 5wave.
This is worth a reviewer's attention as evidence for the threading: the same
shape would have bitten the first post-cutover repair wave, and under
allit isinvisible.
6. The site's sidecar hand-offs
site/src/lib/github-prefill.ts'saddCharactersIssueUrl/addRecapsIssueUrlbuilt new-issue URLs againstISSUE_BASE- thisrepository - naming templates that just moved out. Left alone, every "add
characters" / "add recaps" button on meta.audiosilo.app would land a contributor
on a chooser with no such form. They now build against a new
COMMUNITY_ISSUE_BASE; nothing else about them changes (same template names,same
work_ref, read by the sameresolveWorkRef- which the community repo'sbot runs, since it runs this repo's
cmd/metaissue). The existing host/pathassertion is the guard and now pins the community path. Not on the brief's list;
it is a link the cutover breaks.
7. Docs
CLAUDE.md(what-this-is, the gate section, the storage block,data/,.github/, the metaaudit/metarepair/internal-repair entries, Phase 2),PACK-SPEC.md(the layout block and the profiles table, which now says whichprofile lives where),
LICENSING.md(a new "the licence split is now aREPOSITORY split" section - this repo holds no share-alike content, and the
artifact still mixes both, which is why the per-record
licensefield staysauthoritative),
GOVERNANCE.md(the intake form list, plus why the review bardiffers between the repositories),
README.md, andCONTRIBUTING.md(whichdescribed a four-family tree this repo no longer has; not on the brief's list but
it was the most contributor-visible staleness).
Verification
go build ./...,go vet ./...,go test ./...,go test -race ./...,golangci-lint run- all green. No test needed weakening: the two removedcases read deleted
.githubfiles, everything else is fixture-based anduntouched. The real-data tests (
pkg/check'sTestRealDataTree,internal/repair'sTestRealTreeDryRunComposesAPlan) pass over thepost-deletion tree as they are.
go run ./cmd/metacheck --profile core-> green:ok: 277628 works, 123118 people, 45172 series (2532 advisory).go run ./cmd/metafmt --check --profile core-> clean, exit 0.metacheck(ProfileAll) over the post-deletion tree is also green,with byte-identical output to the
corerun:pack.ListProfiletolerates amissing family root by design, so an absent family is an empty one. The census
line differs from pre-cutover in exactly the sidecar class
(
369 mis-scaled sidecars->0), which is the layer being gone.yarn install --frozen-lockfile && yarn build && yarn run check && yarn test): build clean,astro check0 errors / 0 warnings / 0 hints,408 tests passing.
actionlintclean on all workflows. I cannot run GitHub Actions, sorelease.yml's two-checkout compose and therepository_dispatchtrigger arereviewed-only; the workflow diffs are kept minimal and commented for that
reason. The first release after merge is the live test - and it is recoverable
(a failed compose publishes nothing).
One-time maintainer setup (not done here - creating credentials is yours)
For the
community-datadispatch to work:KodeStar/audiosilo-metawith theContents: read and write repository permission (what
POST /repos/{owner}/{repo}/dispatchesrequires).CORE_DISPATCH_TOKENinaudiosilo-meta-community.
Until then the community workflow logs
no CORE_DISPATCH_TOKEN configured - core release will pick this up with the next core-side data/tooling change or a manual dispatchand exits 0; nothing is lost,because
release.ymlalways composes from the twomains. The setup is recordedin
release.yml's own trigger comment (tracked, where an operator looks) and inthe community PR.
Follow-ups (out of scope here)
~/dev/audiosilo/CLAUDE.md's repo tableneeds a seventh row for
audiosilo-meta-community, andCROSS-REPO.md§17describes the community-metadata seam as a three-repo contract that is now
four.
AUTHORING.md/EXTRACTION.md/EXTRACTION-AUDIO.mdare still hereand already in the community repo. The plan moves them; the brief for this PR
enumerated the docs to touch and did not include them, so they are duplicated
until a follow-up deletes core's copies (
metaextractitself stays here)..claude/community/PLAN.md(untracked, and outside this worktree, so Icould not edit it): steps 4-5 are what this PR is; the PAT setup above wants
recording there.
a Go module, and the community repo still pins a SHA and sparse-checks out the
tooling dirs.
Review round (2026-08-21)
The high-effort review's findings, applied. Its top finding - "no working sidecar
path anywhere" - is resolved by the parallel PR #2265 (issueform/metaissue
--profile, artifact-backed key verification, the community repo's intake), nothere: the sequencing is #2265 merges first, this rebases onto it. Items 3 and
4 of the review's list are staged for after that rebase; 4 landed now because
metabuild --communityis already on main.1. Release tag collision (fixed)
The tag was date + core short sha, which stopped being unique the moment a
community merge could trigger a release: a
community-datadispatch on a day thatalready released leaves core's HEAD unmoved, so the second run composes a
genuinely different artifact under an existing tag and
gh release createfails -and two community merges in one day fail the same way.
data-vYYYY.MM.DD-<core7>-<community7>.shape:
latestDataReleaseand this workflow's own prev-release lookup bothselect by asset presence at the maximum
published_at. Olderdata-vYYYY.MM.DD-<sha>releases stay valid and selectable.it, so an existing tag - same day, same pair, genuinely nothing new - skips
with a
::notice::and exits 0 for the cost of one API call, rather thanpaying a full compose to fail at the end.
2. The sidecar-collision refusal was structurally blind (fixed - the big one)
The read-empty fix in the previous round made merges plan again, and in doing so
made the collision guard answer "no sidecars" for every cluster.
Measured over the real pair: the first 30 non-advisory merge proposals contain
25 sidecar collisions. A blind wave would have destroyed 25 CC BY-SA sidecars,
surfacing much later as a release-blocking
LoadComposederror nothing connects tothe wave that caused it.
metarepairgains--community <dir>, and the guard now has three modes(
sidecarSource), never inferring safety:sidecarInTreeworks-communitysidecarReadOnly--communitygivensidecarUnknowncommunity-data-required, naming the flagDetails that matter:
--communitythe members keep their retired keys and ride the slugtombstone to the survivor (the compose-time re-key) until the community re-key
sweep lands them - and the applied record's notes say so, so the operator
knows a follow-up exists.
stages the merged entry, which is the only thing that catches two clusters
folding two
characters-carrying works onto one target.view.queueis thesingle place the modes part.
openCommunityrefuses at the door both a--communityroot the tree alreadyholds the family for, and one carrying no works-community packs - metabuild's
own rule, because pointing the flag at the community repo's top level rather
than its
data/would answer "no sidecars anywhere" for every cluster: theoriginal blindness wearing the flag that was meant to end it.
Deviation - APPROVED by the coordinator.
metarepairandmetaauditdefault
--profiletocore, unlike metacheck/metafmt which stayall. Without thisthe dangerous case is reachable through the default: under
allover a coretree the family is present-but-empty, which is
sidecarInTree- blind again. Theaxis is the cost of being wrong: metacheck/metafmt are read by CI with the flag
spelled out and a wrong reading is a red check; these are pointed at this tree by
a human typing a command and one of them deletes records. The library default
(
Options.Profilezero value) staysProfileAll, so every existing caller andfixture is byte-identical. The two must also always agree with each other, since
metarepair re-runs metaaudit's detectors.
Verification
(
--profile all) and over the split pair (core+--community) both plan5 applied / 25 refused / 5 tombstones, with the same 25
sidecar-member-collisionrefusals. (One summary line differs: "op this passdoes not apply" 21,363 vs 20,543 - the audit's REF-SIDECAR class, which only
fires where sidecars are in the same tree. It is a class count, not a merge
decision.)
TestMergeWorksOverTheSplitPairMatchesTheWholeTreemergesone cluster both ways and requires the same decision and the same database,
read through
check.LoadComposed- so the tombstone and the compose-time re-keyare what make it pass, which is exactly how the failure would otherwise reach a
release. Plus tests for the unknown-mode refusal, the collision through
--community, and bothopenCommunitydoor refusals.3. Core intake refuses-and-redirects sidecar submissions (landed)
Rebased onto main with #2265 merged - clean, no conflicts.
intake.ymlnow passes--profile coreto metaissue. The two sidecar forms leftwith the layer, but a
data:characters/data:recapslabel can still arrivecontributor tool - and routing is by label, not by which forms this repo ships.
Under the default profile the composer would write a
works-communityentry intoa family this root does not hold: a green-looking bot PR putting CC BY-SA content
in the CC0 repo, permanently unmergeable since CI is core-profile too.
Nothing was missing on the Go side. #2265's
templateBelongsElsewherealreadyderives the rule from each template's
writeFamilies(so it cannot drift fromwhat the composer would write), its message already names the community repo for
this direction, and
TestCoreProfileSendsASidecarFormToTheCommunityRepoalreadypins it. Both direction tests re-run and pass. The rebase sweep runs no metaissue
Verified end to end against the real tree:
The same run without the flag walks past the gate entirely (it stops later, on
an unrelated field) - which is the hazard this closes.
4. PR-time cross-tree signal (landed)
check.ymlgains a compose job: shallow-clone the community repo, runmetabuild -data data --community community/data, fail on non-zero. It is thepre-merge half of the release build - the cross-tree rules cannot be asked of one
root, so until now they ran only after merge, and a core PR that retired or
deleted a sidecar-keyed work went green and broke the release stream for everyone.
It should be required, not advisory.
5. Docs/prompt sweep (landed)
stubs at the community repo. Not copies - keeping the text in two places drifts,
and core's own guide currently walks a contributor into adding a file under
data/works-community/, which is the exact red-CI failure. Themetaextracttool stays; the two Go doc comments citing AUTHORING.md now say where it is.
--profile corewith onesentence on why (CI runs those exact commands; ProfileAll is green precisely
where CI is red).
BuildTool.tsx'sAUTHORING_URLand its "pull request instead"hand-off text, plus
contribute.astro's authoring link, point at the communityrepo. The issue prefills beside them already did - the panel was telling a
contributor to attach the file there and open a pull request here.
ai-verify.sh's prompt: three families in the storage-layout paragraph,every record must be CC0-1.0, and any
data/works-community/path orCC-BY-SA-4.0record in a diff here is itself a finding. The sidecar length /spoiler / position rubric went to the community repo's reviewer, which sees
diffs that can contain one; the two rules in that block that were never about
sidecars are kept.
6. release.yml nits (both fixed)
echo "x=$(git ...)"the substitution's failure is discarded - echo's own statusis what the shell sees and
set -enever fires - so a broken community checkoutwould have published a release recording an empty community sha:
unreproducible, and silently so.
pathsfilter gainspkg/check/**andpkg/pack/**, which nowshape artifact content rather than only validating it (
resolveSidecarKeysre-keys sidecars; the collision keeper decides what ships). The header comment
carries the rationale: an extra release on a rule tweak costs a build; a
wrong-keying fix sitting unreleased costs every consumer.
Also done
The community PR (#2) was accidentally branched on top of the intake task's
commit in a shared clone. Rebased onto that repo's
main; it now carries onlynotify-core.yml+ its README paragraph.Gate (this round)
go build/go vet/go test/go test -race/golangci-lint run- allgreen.
actionlintclean.metacheck --profile coregreen (277,628 works),metafmt --check --profile coreclean. Site: build,astro check0/0/0, 413tests. GitHub Actions still cannot be run here, so both workflow diffs remain
reviewed-only.
Rebase onto main (#2265 merged)
Clean, no conflicts - the two branches were disjoint, and even CLAUDE.md's
issueform-adjacent prose did not collide.
The equivalence proof was re-measured, not inherited. #2265 touched no
builder, compose or data path (
cmd/metaissue,internal/issueform,internal/importer; nogo.mod/go.sumchange), so the artifact should beunmoved - and it is, rebuilt from the rebased branch with a freshly compiled
binary:
Same hash as before the rebase. Gate re-run in full on the rebased branch:
go build/vet/test/test -race/golangci-lintgreen,actionlintclean,
metacheck --profile coregreen (277,628 works),metafmt --check --profile coreclean, site build +astro check0/0/0 + tests green.