Skip to content

Commit 64bba94

Browse files
authored
Merge pull request #17 from galaxyproject/kind-companions
The kind catalog renders the layout the instances declare
2 parents 87eb80b + 607d0ff commit 64bba94

10 files changed

Lines changed: 575 additions & 72 deletions

File tree

content/pattern/astro-stack-glossary.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,14 @@ summarized paper). Three consequences worth stating outright:
7777
Each declaration carries a **requirement level** (`required` / `recommended` / `optional`) and a
7878
**disposition** — whether casting may carry the file into a skill artifact (`foundry-only` never
7979
leaves; `cast-input` is read by the caster but does not appear in the output; `bundled` is copied
80-
in).
80+
in). The disposition is enforced, not descriptive: a cast's forbidden-file list is derived from
81+
it rather than restated beside it.
82+
83+
> A second collision worth naming, like the one above. An instance may also carry a per-*note*
84+
> `companions:` field, and it answers a different question — not *what may sit beside this note*
85+
> (layout, the kind's to declare) but *what does this note carry into a cast* (membership, which
86+
> stays declared per note precisely so that no file ships because of where it was saved). Both
87+
> senses are legitimate; a page using either should say which.
8188
8289
**Collection** — a *location*: a base directory plus the pattern selecting which files under it are
8390
notes, and the kind those notes are. Collections and kinds are deliberately not one-to-one — one

content/pattern/kind-catalog.md

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,30 @@ artifact. Both terms are pinned in the [[astro-stack-glossary]] — they belong
6767
than to the pattern — and specified in Part 3 of the
6868
[[standing-up-a-foundry|Astro-stack worked example]].
6969

70-
Two things that declaration makes visible for the first time:
70+
Three things that declaration makes visible for the first time:
7171

7272
- **`pattern` is a directory in one instance and a flat file in the other.** Same kind, same
7373
layer, different physical form. That difference used to live inside each instance's collection
7474
glob and reached no manifest, so this page could not have reported it however carefully you
7575
read the tables.
7676
- **An open companion set is not an empty one.** A kind may declare that files it has not
77-
enumerated are legitimately present — vendored sources beside a research note, acquisition
78-
files beside a book chapter. That says *unbounded*, not *none*. They are opposite claims, and
79-
only one of them is checkable.
80-
81-
**Not in the tables below yet.** The declarations landed in the instances first. The snapshots
82-
this page renders predate them, and this site's manifest reader is still the version that drops
83-
the new fields on the way in. So the section above describes what the instances now declare, not
84-
what is rendered below — deliberately stated rather than left for a reader to discover as a gap.
77+
enumerated are legitimately present — vendored sources beside a research note. That says
78+
*unbounded*, not *none*. They are opposite claims, only one of them is checkable, and the table
79+
below marks the open one rather than leaving a blank to be read either way. Exactly one kind
80+
across both instances declares it. A book chapter looked like the second case and is not: its
81+
acquisition files sit at the *book* level, one directory above the chapter that is the note, so
82+
declaring the chapter open would have claimed unboundedness where the truth is those files
83+
belong to a container that is not a note at all.
84+
- **The declaration is load-bearing, not documentation.** One instance's casting pipeline kept a
85+
hand-written list of files a skill artifact may never carry; it named two of the eight its kinds
86+
declare. Deriving that list from the dispositions instead is what turned the metadata this page
87+
reports into something a check answers to — and the size of the gap is the argument for it.
88+
89+
Each substrate kind below carries a companion table with one row per file and one column per
90+
instance, reading *requirement · disposition*. A file both instances declare at the same
91+
requirement and the same disposition is highlighted: that is the layout transferring, and it is
92+
the strongest claim on this page. A file one instance declares and the other does not shows as a
93+
dash — an extension inside a shared kind, which is a real difference and not a gap in the data.
8594

8695
For where these kinds live inside a repository, and what a new instance should copy first, see
8796
[[anatomy-of-an-instance]] and the [[standing-up-a-foundry|Astro-stack worked example]]. For the

content/pattern/standing-up-a-foundry.instructions.txt

Lines changed: 137 additions & 39 deletions
Large diffs are not rendered by default.

content/pattern/standing-up-a-foundry.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ deduced:
6565
- **Commit the probe.** One instance carried three comments claiming its typecheck caught a
6666
hazard; measured, it caught neither of the two. Zero errors is what success and total erasure
6767
both look like.
68+
- **An upgrade can move a rule's enforcement without moving the rule.** Both instances took the
69+
same major-version wave, and it changed *who* was holding two of the checklist's rules. A schema
70+
library stopped making one of them compulsory, so the contract now holds it alone; a framework
71+
stopped running a plugin pipeline unless asked, so a build that renders every link as literal
72+
text still passes. Neither failure is loud, and a checklist that keeps the rule while its
73+
original justification quietly expires is how prescription rots.
6874

6975
That last one is the shape of the whole category: the checklist absorbed the measurement, not
7076
the assumption.

site/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
},
1717
"dependencies": {
1818
"@fontsource/atkinson-hyperlegible": "^5.2.8",
19-
"@galaxy-foundry/kind-manifest": "^0.2.1",
19+
"@galaxy-foundry/kind-manifest": "^0.3.0",
2020
"@galaxy-foundry/tag-registry": "^0.1.0",
2121
"@galaxy-foundry/wiki-links": "^0.1.0",
2222
"@tailwindcss/typography": "^0.5.19",

site/src/components/KindCatalog.astro

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,36 @@
11
---
22
import {
3+
companionRows,
34
loadInstances,
45
kindRows,
56
layerDisagreements,
67
sharedRequiredFields,
8+
shapeDifferences,
79
} from "../lib/instances";
10+
import type { ManifestKind } from "../lib/instances";
811
912
const instances = loadInstances();
1013
const rows = kindRows(instances);
1114
const shared = rows.filter((r) => r.shared);
1215
const local = rows.filter((r) => !r.shared);
1316
const disagreements = layerDisagreements(rows);
17+
const differingShapes = shapeDifferences(rows);
1418
const base = import.meta.env.BASE_URL.replace(/\/$/, "");
19+
20+
// A directory kind that enumerates nothing is not the same as one that declares undeclared
21+
// files legitimate. Both would render as an absent companion table, so `allow` is said out loud.
22+
const openSet = (kind: ManifestKind) => kind.additionalCompanions === "allow";
1523
---
1624
<div class="not-prose mt-8">
1725
<div class="rounded-lg border p-4 text-sm" style="border-color: var(--rule); background: var(--graphite)">
1826
<strong>Generated</strong> from each instance's own schemas. Every required-metadata table
1927
below is derived from that instance's zod definitions, not transcribed — so a kind that
20-
changes upstream changes here, and cannot quietly disagree.
28+
changes upstream changes here, and cannot quietly disagree. A kind also declares its
29+
<strong>shape</strong> and its <strong>companions</strong>: each companion carries a
30+
requirement level (<code>required</code> / <code>recommended</code> / <code>optional</code>)
31+
and a disposition saying where it may travel — <code>foundry-only</code> never leaves the
32+
Foundry, <code>cast-input</code> is read while casting but not shipped,
33+
<code>bundled</code> is carried into the artifact.
2134
<ul class="mt-2 space-y-0.5" style="color: var(--muted)">
2235
{instances.map((i) => (
2336
<li>
@@ -37,6 +50,17 @@ const base = import.meta.env.BASE_URL.replace(/\/$/, "");
3750
</p>
3851
)}
3952

53+
{differingShapes.length > 0 && (
54+
<p class="mt-4 rounded-lg border p-4 text-sm" style="border-color: var(--rule); color: var(--muted)">
55+
<strong style="color: var(--ink)">Same kind, different shape:</strong>{" "}
56+
{differingShapes.join(", ")} — a flat file in one instance, a directory in the other. Not
57+
drift, and not smoothed over: the two are the same kind carrying the same required
58+
metadata, laid out differently because one domain needed files beside the note and the
59+
other did not. Before kinds declared a shape this difference lived inside each instance's
60+
collection glob and reached no manifest, so this page could not have reported it.
61+
</p>
62+
)}
63+
4064
<h2 class="mt-10 text-2xl font-bold" style="font-family: var(--font-display)">
4165
Substrate kinds
4266
</h2>
@@ -54,7 +78,25 @@ const base = import.meta.env.BASE_URL.replace(/\/$/, "");
5478
<dl class="mt-3 space-y-3 text-sm">
5579
{row.present.map((i) => (
5680
<div>
57-
<dt class="font-semibold">{i.title}</dt>
81+
<dt class="font-semibold">
82+
{i.title}
83+
{row.by[i.slug]!.shape && (
84+
<span
85+
class="ml-2 rounded px-1.5 py-0.5 text-xs font-normal"
86+
style="background: var(--graphite); color: var(--muted)"
87+
>
88+
{row.by[i.slug]!.shape}
89+
</span>
90+
)}
91+
{openSet(row.by[i.slug]!) && (
92+
<span
93+
class="ml-1 rounded px-1.5 py-0.5 text-xs font-normal"
94+
style="background: var(--graphite); color: var(--glow)"
95+
>
96+
open companion set
97+
</span>
98+
)}
99+
</dt>
58100
<dd class="mt-0.5" style="color: var(--muted)">{row.by[i.slug]!.summary}</dd>
59101
<dd class="mt-1">
60102
<span style="color: var(--muted)">requires</span>{" "}
@@ -71,6 +113,54 @@ const base = import.meta.env.BASE_URL.replace(/\/$/, "");
71113
{sharedRequiredFields(row).map((f) => <code class="mr-1">{f}</code>)}
72114
</p>
73115
)}
116+
{companionRows(row).length > 0 && (
117+
<div class="mt-3 border-t pt-3" style="border-color: var(--rule)">
118+
<p class="text-sm"><strong>Companions</strong></p>
119+
<div class="mt-2 overflow-x-auto">
120+
<table class="w-full text-left text-sm">
121+
<thead>
122+
<tr style="color: var(--muted)">
123+
<th class="pb-1 pr-4 font-normal">file</th>
124+
{row.present.map((i) => (
125+
<th class="pb-1 pr-4 font-normal">{i.title}</th>
126+
))}
127+
</tr>
128+
</thead>
129+
<tbody>
130+
{companionRows(row).map((companion) => (
131+
<tr class="border-t align-top" style="border-color: var(--rule)">
132+
<td class="py-1.5 pr-4">
133+
<code style={companion.identical ? "color: var(--glow)" : undefined}>
134+
{companion.file}
135+
</code>
136+
</td>
137+
{row.present.map((i) => (
138+
<td class="py-1.5 pr-4">
139+
{companion.by[i.slug] ? (
140+
<span>
141+
{companion.by[i.slug]!.requirement}
142+
<span style="color: var(--muted)">
143+
{" · "}{companion.by[i.slug]!.disposition}
144+
</span>
145+
</span>
146+
) : (
147+
<span style="color: var(--muted)">—</span>
148+
)}
149+
</td>
150+
))}
151+
</tr>
152+
))}
153+
</tbody>
154+
</table>
155+
</div>
156+
{companionRows(row).some((c) => c.identical) && (
157+
<p class="mt-2 text-sm" style="color: var(--muted)">
158+
Highlighted files are declared by both instances at the same requirement and the
159+
same disposition — the layout itself transferring, not just the kind's name.
160+
</p>
161+
)}
162+
</div>
163+
)}
74164
</section>
75165
))}
76166

@@ -93,6 +183,22 @@ const base = import.meta.env.BASE_URL.replace(/\/$/, "");
93183
.map((row) => (
94184
<div class="mt-3 rounded-lg border p-4" style="border-color: var(--rule)">
95185
<code style="color: var(--ember)">{row.kind}</code>
186+
{row.by[instance.slug]!.shape && (
187+
<span
188+
class="ml-2 rounded px-1.5 py-0.5 text-xs"
189+
style="background: var(--graphite); color: var(--muted)"
190+
>
191+
{row.by[instance.slug]!.shape}
192+
</span>
193+
)}
194+
{openSet(row.by[instance.slug]!) && (
195+
<span
196+
class="ml-1 rounded px-1.5 py-0.5 text-xs"
197+
style="background: var(--graphite); color: var(--glow)"
198+
>
199+
open companion set
200+
</span>
201+
)}
96202
<p class="mt-1 text-sm" style="color: var(--muted)">
97203
{row.by[instance.slug]!.summary}
98204
</p>
@@ -102,6 +208,18 @@ const base = import.meta.env.BASE_URL.replace(/\/$/, "");
102208
.filter((f) => f.required)
103209
.map((f) => <code class="mr-1">{f.name}</code>)}
104210
</p>
211+
{(row.by[instance.slug]!.companions ?? []).length > 0 && (
212+
<ul class="mt-2 space-y-0.5 text-sm">
213+
{row.by[instance.slug]!.companions!.map((companion) => (
214+
<li>
215+
<code>{companion.file}</code>{" "}
216+
<span style="color: var(--muted)">
217+
{companion.requirement} · {companion.disposition}
218+
</span>
219+
</li>
220+
))}
221+
</ul>
222+
)}
105223
</div>
106224
))}
107225
</div>

0 commit comments

Comments
 (0)