|
10 | 10 | # The user's only real effort is choosing settings; everything else is derived |
11 | 11 | # from the proven pre-mint framework. See SPEC.adoc. |
12 | 12 | # |
| 13 | +# FAIL-CLOSED (--strict). The wizard's value is the assurance it carries, so a |
| 14 | +# missing toolchain must not silently degrade it. In strict mode an absent |
| 15 | +# deno/idris2/jq is a FAILURE, not a skip. Without --strict the wizard is |
| 16 | +# permissive for local exploration, but it will still never claim success for a |
| 17 | +# cartridge it did not actually produce. |
| 18 | +# |
13 | 19 | # Usage: |
14 | 20 | # foundry.sh # interactive wizard |
15 | 21 | # foundry.sh --from minter.toml # non-interactive (CI / scripted) |
| 22 | +# foundry.sh --strict --from f.toml# fail on any missing toolchain (use in CI) |
16 | 23 | # foundry.sh --harness <dir> # just re-run the assurance gate on a dir |
| 24 | +# |
| 25 | +# Env: FOUNDRY_STRICT=1 is equivalent to passing --strict. |
17 | 26 | set -euo pipefail |
18 | 27 |
|
19 | 28 | HERE="$(cd "$(dirname "$0")" && pwd)" |
20 | | -REPO="$(cd "$HERE/../.." && pwd)" |
21 | | -MINTER="$REPO/tools/cartridge-minter/mint.ts" |
22 | 29 | KINDS="domain coordination agentic nesy" |
23 | 30 | CAPS="Net Fs Cred Clock Rand" |
24 | 31 |
|
25 | 32 | say() { printf '\033[1m%s\033[0m\n' "$*"; } |
26 | 33 | note(){ printf ' %s\n' "$*"; } |
27 | 34 |
|
| 35 | +STRICT="${FOUNDRY_STRICT:-0}" |
| 36 | + |
| 37 | +# need <tool> <why> — in strict mode a missing tool aborts; otherwise it reports |
| 38 | +# the degradation and returns 1 so the caller can branch. |
| 39 | +need() { |
| 40 | + if command -v "$1" >/dev/null 2>&1; then return 0; fi |
| 41 | + if [ "$STRICT" = 1 ]; then |
| 42 | + echo "ABORT (--strict): '$1' is required — $2" >&2 |
| 43 | + exit 1 |
| 44 | + fi |
| 45 | + note "$1 absent — $2 (re-run with --strict to make this an error)" |
| 46 | + return 1 |
| 47 | +} |
| 48 | + |
| 49 | +# ── argument pre-pass: --strict may appear anywhere ──────────────────────── |
| 50 | +ARGS=() |
| 51 | +for a in "$@"; do |
| 52 | + case "$a" in |
| 53 | + --strict) STRICT=1 ;; |
| 54 | + *) ARGS+=("$a") ;; |
| 55 | + esac |
| 56 | +done |
| 57 | +set -- ${ARGS[@]+"${ARGS[@]}"} |
| 58 | + |
28 | 59 | # ── re-run the harness only ──────────────────────────────────────────────── |
29 | 60 | if [ "${1:-}" = "--harness" ]; then |
30 | 61 | exec "$HERE/stages/harness.sh" "${2:?usage: --harness <cartridge-dir>}" "${@:3}" |
31 | 62 | fi |
32 | 63 |
|
33 | 64 | # ── 0 · confirm the design proof still holds before minting anything ──────── |
34 | 65 | say "Foundry · verifying the design proof (no dropped proofs, least authority)" |
35 | | -if command -v idris2 >/dev/null 2>&1; then |
| 66 | +if need idris2 "the Foundry design proof cannot be checked locally"; then |
36 | 67 | "$HERE/proof/check.sh" >/dev/null && note "proof/Foundry.idr ✓ typechecks" \ |
37 | 68 | || { echo "ABORT: the Foundry design proof does not hold — refusing to mint." >&2; exit 1; } |
38 | | -else |
39 | | - note "idris2 absent — skipping local proof check (CI enforces it)" |
40 | 69 | fi |
41 | 70 |
|
42 | 71 | # ── gather settings ──────────────────────────────────────────────────────── |
43 | 72 | TOML="" |
| 73 | +API_BASE=""; AUTH_METHOD=""; AUTH_ENV="" |
44 | 74 | if [ "${1:-}" = "--from" ]; then |
45 | 75 | TOML="${2:?--from needs a path}" |
46 | 76 | [ -f "$TOML" ] || { echo "no such file: $TOML" >&2; exit 2; } |
|
50 | 80 | read -rp " one-line description: " DESC |
51 | 81 | read -rp " domain (e.g. Archive, Comms, Cloud): " DOMAIN |
52 | 82 | read -rp " tier [Teranga|Shield|Ayo] (Ayo): " TIER; TIER="${TIER:-Ayo}" |
| 83 | + # `protocols` is REQUIRED by the minter's validator. The wizard used to omit |
| 84 | + # it, so every interactively-produced descriptor was rejected at Mint. |
| 85 | + read -rp " protocols, comma-sep [mcp|rest|grpc|graphql|lsp|dap|bsp] (mcp): " PROTOCOLS |
| 86 | + PROTOCOLS="${PROTOCOLS:-mcp}" |
53 | 87 | read -rp " kind [$KINDS] (domain): " KIND; KIND="${KIND:-domain}" |
54 | 88 | read -rp " granted capabilities, comma-sep from [$CAPS] (blank = none, maximally inert): " GRANT |
55 | 89 | note "fork-per-request (ADR-0005) ⇒ every grant is ephemeral; ungranted caps are locked-down/denied" |
| 90 | + read -rp " backend base_url (blank = local://<name> loopback): " API_BASE |
| 91 | + read -rp " auth method [none|api-key|oauth2|vault] (none): " AUTH_METHOD |
| 92 | + AUTH_METHOD="${AUTH_METHOD:-none}" |
| 93 | + [ "$AUTH_METHOD" = none ] || read -rp " auth env var (e.g. EXAMPLE_TOKEN): " AUTH_ENV |
56 | 94 | TOML="$(mktemp --suffix=.minter.toml)" |
57 | 95 | { |
58 | 96 | echo "name = \"$NAME\"" |
59 | 97 | echo "description = \"$DESC\"" |
60 | 98 | echo "version = \"0.1.0\"" |
61 | 99 | echo "domain = \"$DOMAIN\"" |
62 | 100 | echo "tier = \"$TIER\"" |
| 101 | + # CSV -> TOML array of strings, e.g. `mcp,rest` -> ["mcp", "rest"]. |
| 102 | + echo "protocols = [$(printf '%s' "$PROTOCOLS" | tr ',' '\n' \ |
| 103 | + | sed -E 's/^[[:space:]]+|[[:space:]]+$//g; /^$/d; s/^/"/; s/$/"/' \ |
| 104 | + | paste -sd, -)]" |
63 | 105 | [ "$KIND" = domain ] || { echo "category = \"cross-cutting\""; echo "cross_cutting_category = \"$KIND\""; } |
64 | 106 | # Capability plan consumed by the Provision stage (provision.sh --granted). |
65 | 107 | echo "granted = \"$GRANT\"" |
| 108 | + # Settings consumed by the Configure stage (configure.sh). |
| 109 | + [ -n "$API_BASE" ] && echo "api_base = \"$API_BASE\"" |
| 110 | + [ "$AUTH_METHOD" != none ] && echo "auth_method = \"$AUTH_METHOD\"" |
| 111 | + [ -n "$AUTH_ENV" ] && echo "auth_env = \"$AUTH_ENV\"" |
66 | 112 | } > "$TOML" |
67 | 113 | note "wrote $TOML" |
68 | 114 | fi |
69 | 115 |
|
70 | | -# In --from mode the grant lives in the descriptor; read it (default: none). |
| 116 | +# Read any settings that live in the descriptor (both --from and interactive). |
| 117 | +toml_get() { sed -nE "s/^[[:space:]]*$1[[:space:]]*=[[:space:]]*\"([^\"]*)\".*/\1/p" "$TOML" | head -1; } |
71 | 118 | : "${GRANT:=}" |
72 | | -if [ -z "$GRANT" ] && grep -qE '^[[:space:]]*granted[[:space:]]*=' "$TOML"; then |
73 | | - GRANT="$(sed -nE 's/^[[:space:]]*granted[[:space:]]*=[[:space:]]*"([^"]*)".*/\1/p' "$TOML" | head -1)" |
74 | | -fi |
| 119 | +[ -n "$GRANT" ] || GRANT="$(toml_get granted)" |
| 120 | +[ -n "$API_BASE" ] || API_BASE="$(toml_get api_base)" |
| 121 | +[ -n "$AUTH_METHOD" ] || AUTH_METHOD="$(toml_get auth_method)" |
| 122 | +[ -n "$AUTH_ENV" ] || AUTH_ENV="$(toml_get auth_env)" |
75 | 123 |
|
76 | | -DEST_NAME="$(grep -E '^name' "$TOML" | head -1 | sed -E 's/.*"([^"]+)".*/\1/')" |
77 | | - |
78 | | -# ── 1 · MINT (delegate to the existing minter) ───────────────────────────── |
| 124 | +# ── 1 · MINT (delegate to the Mint stage — single source of path logic) ──── |
79 | 125 | say "1/4 · mint — scaffold from the proven template" |
80 | | -if command -v deno >/dev/null 2>&1; then |
81 | | - deno run --allow-read --allow-write "$MINTER" "$TOML" |
82 | | - note "minted via cartridge-minter" |
83 | | -else |
84 | | - note "deno not installed here — the Mint stage shells to $MINTER" |
85 | | - note "(install deno to run end-to-end; the rest of the flow is toolchain-local)" |
| 126 | +CART_DIR="" |
| 127 | +if need deno "the Mint stage cannot scaffold a cartridge"; then |
| 128 | + # mint.sh's stdout is the scaffolded path; its chatter goes to stderr. |
| 129 | + CART_DIR="$("$HERE/stages/mint.sh" "$TOML")" |
| 130 | + [ -n "$CART_DIR" ] && [ -d "$CART_DIR" ] \ |
| 131 | + || { echo "ABORT: Mint reported '$CART_DIR' but no such directory exists." >&2; exit 1; } |
| 132 | + note "minted -> $CART_DIR" |
86 | 133 | fi |
87 | 134 |
|
88 | | -CART_DIR="$(find "$REPO/cartridges" -type d -name "$DEST_NAME" 2>/dev/null | head -1)" |
| 135 | +# Without a minted directory the remaining stages have nothing to act on. Say so |
| 136 | +# plainly and exit non-zero: the wizard must never report success for a |
| 137 | +# cartridge it did not produce. |
| 138 | +if [ -z "$CART_DIR" ]; then |
| 139 | + echo "Foundry: INCOMPLETE — no cartridge was minted, so provision/configure/harness did not run." >&2 |
| 140 | + echo " Install deno and re-run, or gate an existing cartridge with: foundry.sh --harness <dir>" >&2 |
| 141 | + exit 1 |
| 142 | +fi |
89 | 143 |
|
90 | 144 | # ── 2 · PROVISION (grant exactly the chosen caps; record the partition) ──── |
91 | 145 | say "2/4 · provision — grant exactly the capabilities chosen, nothing more" |
92 | | -if [ -n "$CART_DIR" ]; then |
93 | | - "$HERE/stages/provision.sh" "$CART_DIR" --granted "$GRANT" |
94 | | - note "least authority: ungranted capabilities are locked-down (provably absent — see proof)" |
95 | | -else |
96 | | - note "cartridge dir not found (Mint did not run — deno absent). Provision recorded plan: granted=[$GRANT]" |
97 | | -fi |
| 146 | +"$HERE/stages/provision.sh" "$CART_DIR" --granted "$GRANT" |
| 147 | +note "least authority: ungranted capabilities are locked-down (provably absent — see proof)" |
98 | 148 |
|
99 | | -# ── 3 · CONFIGURE (settings already captured in the descriptor) ──────────── |
| 149 | +# ── 3 · CONFIGURE (apply settings; cannot drop a proof or widen the grant) ── |
100 | 150 | say "3/4 · configure — apply settings (preserves every proof + the grant)" |
| 151 | +CFG_ARGS=() |
| 152 | +[ -n "$API_BASE" ] && CFG_ARGS+=(--api-base "$API_BASE") |
| 153 | +[ -n "$AUTH_METHOD" ] && [ "$AUTH_METHOD" != none ] && CFG_ARGS+=(--auth "$AUTH_METHOD") |
| 154 | +[ -n "$AUTH_ENV" ] && CFG_ARGS+=(--auth-env "$AUTH_ENV") |
| 155 | +"$HERE/stages/configure.sh" "$CART_DIR" ${CFG_ARGS[@]+"${CFG_ARGS[@]}"} |
101 | 156 |
|
102 | 157 | # ── 4 · HARNESS (the one standard assurance gate) ────────────────────────── |
103 | 158 | say "4/4 · harness — run the standard assurance gate" |
104 | | -if [ -n "$CART_DIR" ]; then |
105 | | - "$HERE/stages/harness.sh" "$CART_DIR" --granted "$GRANT" |
106 | | -else |
107 | | - note "harness skipped (no minted dir). To gate an existing cartridge: foundry.sh --harness <dir>" |
108 | | -fi |
| 159 | +"$HERE/stages/harness.sh" "$CART_DIR" --granted "$GRANT" |
109 | 160 |
|
110 | | -say "Foundry · done." |
| 161 | +say "Foundry · done — $CART_DIR" |
0 commit comments