Skip to content

Commit 3e57141

Browse files
feat(rsr): direct capability declaration primary; preset optional (#392)
## Summary Refines the just-merged template-applicability model (#391) to the **pure capability-gating** shape: a profile declares `capabilities = [...]` directly as the primary form, and `preset` becomes **optional sugar** (a named bundle expanded from the gate data). This matches the chosen "capability-gated profile" approach while keeping presets available for ergonomics. Precedence: `capabilities`, `preset`, and `add` all union; `remove` subtracts; at least one of `capabilities` / `preset` is required. ## Changes - **`scripts/check-rsr-profile.sh`** — accept a direct `capabilities` list; `preset` is now optional (error only if a profile declares neither). - **`TEMPLATE-APPLICABILITY-POLICY.adoc`** — capability declaration is *the model*; presets are *optional shorthand* (updated Model, profile example, and Presets section). - **`.machine_readable/template-capability-gates.toml`** — note presets are optional. ## Verification - Direct-capabilities profile (no preset) → **OK**. - Preset profile (arghda-core `rust-cli`) → still **OK**. - A planted `abi.ipkg` under the direct form → still flagged **VESTIGIAL**. - A profile declaring neither `capabilities` nor `preset` → clean error (exit 2). Note: arghda-core's existing `rsr-profile.a2ml` (which uses `preset = "rust-cli"`) remains valid — preset is now demonstrated as the optional form, so no change is needed there. Follow-up to #391 (per the agreed pure-gating-primary direction). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019GiSiEfgZCte35dyykgBHs --- _Generated by [Claude Code](https://claude.ai/code/session_019GiSiEfgZCte35dyykgBHs)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 78b2900 commit 3e57141

3 files changed

Lines changed: 49 additions & 25 deletions

File tree

.machine_readable/template-capability-gates.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ paths = ["README.adoc", "EXPLAINME.adoc", "LICENSE", "SECURITY.md", "CONTRIBUTIN
4646
"MAINTAINERS.adoc" = "governance-tier"
4747

4848
[presets]
49+
# OPTIONAL shorthands. A profile may declare `capabilities = [...]` directly
50+
# instead of a preset; presets are pure sugar that expands to a capability set.
4951
# preset name = [ base capabilities ]
5052
rust-cli = ["rust", "cli", "library"]
5153
rust-ffi-lib = ["rust", "zig", "ffi", "abi", "library"]

TEMPLATE-APPLICABILITY-POLICY.adoc

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,20 @@ makes "carry it only if you need it" a *rule with a test*, not a judgement call.
2727

2828
== Model
2929

30-
Three layers:
31-
3230
. *Universal baseline* — modules every repo carries unconditionally (identity,
3331
community-health, machine-readable state, the licence invariant).
34-
. *Capability gates* — every other template module is tagged with the
35-
*capability* it serves. A repo carries the module **iff** it declares that
36-
capability.
37-
. *Presets* — named capability bundles (`rust-cli`, `rust-ffi-lib`,
38-
`rust-service`, `formal-proof-lib`, `docs-site`, …) a repo adopts as its base,
39-
then adjusts with explicit per-capability `add` / `remove`. (The "hybrid"
40-
layer: presets for ergonomics, gates for precision.)
41-
42-
A repo's *profile* (`.machine_readable/rsr-profile.a2ml`) declares its preset
43-
and any adjustments. Its effective capability set drives everything else.
32+
. *Capability gates (the model)* — every other template module is tagged with
33+
the *capability* it serves. A repo *declares its capabilities* and carries a
34+
module **iff** it declares that module's gating capability.
35+
. *Presets (optional sugar)* — named capability bundles (`rust-cli`,
36+
`rust-ffi-lib`, `rust-service`, `formal-proof-lib`, `docs-site`, …). A repo
37+
*may* adopt a preset as a shorthand for its base capabilities instead of
38+
listing them, then adjust with `add` / `remove`. Presets are pure ergonomics
39+
over the gate model — never required.
40+
41+
A repo's *profile* (`.machine_readable/rsr-profile.a2ml`) declares its
42+
capabilities (directly, or via a preset + adjustments). Its effective
43+
capability set drives everything else.
4444

4545
== Capability taxonomy
4646

@@ -114,7 +114,10 @@ and the SPDX licence invariant (`LICENCE-POLICY.adoc`).
114114
| `.github/workflows/release.yml`, registry metadata | `published-package`
115115
|===
116116

117-
== Presets
117+
== Presets (optional)
118+
119+
Presets are optional shorthands — a repo can always declare `capabilities`
120+
directly instead. Each expands to a base capability set:
118121

119122
[cols="1,3"]
120123
|===
@@ -153,21 +156,33 @@ that justifies it. That is the thing this policy removes.
153156

154157
== Per-repo profile
155158

156-
`.machine_readable/rsr-profile.a2ml` (A2ML, TOML-like; SPDX `MPL-2.0`):
159+
`.machine_readable/rsr-profile.a2ml` (A2ML, TOML-like; SPDX `MPL-2.0`). Declare
160+
capabilities directly (primary form):
157161

158162
[source,toml]
159163
----
160164
[profile]
161-
preset = "rust-cli"
162-
add = [] # extra capabilities beyond the preset
163-
remove = [] # preset capabilities this repo does NOT have
165+
capabilities = ["rust", "cli", "library"]
164166
165167
[rationale]
166-
# One line per non-obvious add/remove or declined capability.
168+
# One line per non-obvious capability or declined module.
167169
no-abi = "language-agnostic engine; no C-ABI seam, no Idris2 ABI proofs"
168170
no-formal-proofs = "consumes Agda (shells out); contains no in-tree proofs"
169171
----
170172

173+
Or, equivalently, adopt a preset (optional shorthand) and adjust:
174+
175+
[source,toml]
176+
----
177+
[profile]
178+
preset = "rust-cli" # = ["rust", "cli", "library"]
179+
add = ["published-package"] # extra capabilities beyond the preset
180+
remove = [] # preset capabilities this repo does NOT have
181+
----
182+
183+
`preset`, `capabilities`, `add`, and `remove` all union (then `remove`
184+
subtracts); at least one of `capabilities` / `preset` is required.
185+
171186
== Enforcement (how this evolves the existing machinery)
172187

173188
`root-allow.txt` becomes *derived*, not hand-maintained as one shape:

scripts/check-rsr-profile.sh

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,25 @@ section() { awk -v s="[$1]" '$0==s{f=1;next} /^\[/{f=0} f && !/^[[:space:]]*#/ &
3030
quoted_on_key() { grep -E "^[[:space:]]*$1[[:space:]]*=" | grep -oE '"[^"]+"' | tr -d '"' || true; }
3131

3232
# --- target profile ---
33+
# Capability declaration is primary: a profile may list `capabilities = [...]`
34+
# directly. `preset` is optional sugar — a named bundle expanded from the gate
35+
# data. A profile must provide at least one of the two.
3336
PBODY="$(section profile "$PROFILE")"
3437
PRESET="$(printf '%s\n' "$PBODY" | quoted_on_key preset | sed -n 1p)"
38+
DIRECT="$(printf '%s\n' "$PBODY" | quoted_on_key capabilities)"
3539
ADD="$(printf '%s\n' "$PBODY" | quoted_on_key add)"
3640
REMOVE="$(printf '%s\n' "$PBODY" | quoted_on_key remove)"
37-
[ -n "$PRESET" ] || { echo "ERROR: profile declares no preset" >&2; exit 2; }
41+
[ -n "$PRESET$DIRECT" ] || { echo "ERROR: profile declares neither 'capabilities' nor 'preset'" >&2; exit 2; }
3842

39-
# --- preset's base capabilities (from the gate data) ---
40-
PRESETCAPS="$(section presets "$GATES" | quoted_on_key "$PRESET")"
41-
[ -n "$PRESETCAPS" ] || { echo "ERROR: unknown preset '$PRESET' (not in $GATES [presets])" >&2; exit 2; }
43+
# --- preset's base capabilities, if a preset is named ---
44+
PRESETCAPS=""
45+
if [ -n "$PRESET" ]; then
46+
PRESETCAPS="$(section presets "$GATES" | quoted_on_key "$PRESET")"
47+
[ -n "$PRESETCAPS" ] || { echo "ERROR: unknown preset '$PRESET' (not in $GATES [presets])" >&2; exit 2; }
48+
fi
4249

43-
# --- effective capabilities = presetcaps + add - remove ---
44-
EFFECTIVE="$(printf '%s\n%s\n' "$PRESETCAPS" "$ADD" | sort -u | sed '/^$/d')"
50+
# --- effective capabilities = capabilities + presetcaps + add - remove ---
51+
EFFECTIVE="$(printf '%s\n%s\n%s\n' "$DIRECT" "$PRESETCAPS" "$ADD" | sort -u | sed '/^$/d')"
4552
if [ -n "$REMOVE" ]; then
4653
EFFECTIVE="$(comm -23 <(printf '%s\n' "$EFFECTIVE") <(printf '%s\n' "$REMOVE" | sort -u))"
4754
fi
@@ -58,7 +65,7 @@ present() {
5865
}
5966

6067
echo "repo: $REPO"
61-
echo "preset: $PRESET"
68+
echo "profile: ${PRESET:+preset=$PRESET }${DIRECT:+direct-capabilities}"
6269
echo "effective capabilities: $(printf '%s ' $EFFECTIVE)"
6370
echo
6471

0 commit comments

Comments
 (0)