Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
9a2091d
refactor(gen-local-env): compare with [[ so a port cannot split the test
danielfdez17 Oct 28, 2026
05ebd17
refactor(host-ports): compare with [[ so a taken port cannot split th…
danielfdez17 Oct 28, 2026
bdf3730
refactor(grobase): compare the link mode with [[
danielfdez17 Oct 28, 2026
9a1ca23
refactor(public-verify): compare container facts with [[
danielfdez17 Oct 28, 2026
20d9fee
refactor(public-verify): compare app origins and funnel ports with [[
danielfdez17 Oct 28, 2026
3d66a04
refactor(public-verify): compare surface checks with [[
danielfdez17 Oct 28, 2026
8c4c616
refactor(public-verify): compare shared predicates with [[
danielfdez17 Oct 28, 2026
9d76780
refactor(publish-socket): compare paths and pins with [[
danielfdez17 Oct 28, 2026
3f32580
refactor(public-b2b): compare image-build checks with [[
danielfdez17 Oct 28, 2026
86beb88
refactor(public-b2b): compare deploy gates with [[
danielfdez17 Oct 28, 2026
0c227c2
refactor(public-b2b): compare env-init checks with [[
danielfdez17 Oct 28, 2026
31b73c0
refactor(public-b2b): compare rollback guards with [[
danielfdez17 Oct 28, 2026
dfcb66a
refactor(public-funnel): compare funnel checks with [[
danielfdez17 Oct 28, 2026
6c84924
refactor(public-node): compare node checks with [[
danielfdez17 Oct 28, 2026
aa4df76
refactor(mounts): compare reencrypt guards with [[
danielfdez17 Oct 28, 2026
569d92d
test(account): compare round-trip checks with [[
danielfdez17 Oct 28, 2026
9f561f8
test(ai): compare the guard-wire check with [[
danielfdez17 Oct 28, 2026
e1d77be
test(ai): compare image-closure checks with [[
danielfdez17 Oct 28, 2026
93f9f56
test(ai): compare image-closure fixtures with [[
danielfdez17 Oct 28, 2026
aeccb74
chore(config): pin the pull-requests rule
danielfdez17 Oct 28, 2026
86e00ed
chore(calendar): pin the Cloudflare Pages deploy
danielfdez17 Oct 28, 2026
fb61b98
chore(grobase): pin edge body limits and the portable pgvector build
danielfdez17 Oct 28, 2026
af32f9a
chore(mail): pin the Cloudflare Pages deploy
danielfdez17 Oct 28, 2026
433773e
chore(opposite-osiris): pin account linking and the newsletter footer
danielfdez17 Oct 28, 2026
ac63325
chore(born2root): pin osionos model seeds and the tailnet health path
danielfdez17 Oct 28, 2026
a92fe59
chore(monkey-bot): pin suite runs and the form-xss baseline
danielfdez17 Oct 28, 2026
29a030a
chore(scripts): pin symlink guards on cache and output paths
danielfdez17 Oct 28, 2026
c892bf0
chore(drawnosaurus): pin the osionos board and the phone layout
danielfdez17 Oct 28, 2026
04d2469
chore(osionos): pin the invite-inbox merge
danielfdez17 Oct 28, 2026
094ff0e
fix(ai): let the share-rule modules into the image context
danielfdez17 Oct 28, 2026
18fefce
fix(ai): copy the share-rule modules the service loads
danielfdez17 Oct 28, 2026
c1e6e74
Merge branch 'main' into fix/ci
danielfdez17 Nov 1, 2026
0164300
fix(grobase): pin the production audit overrides
danielfdez17 Nov 1, 2026
bac204b
chore(drawnosaurus): pin the framed-board save
danielfdez17 Nov 1, 2026
d43acc3
chore(opposite-osiris): pin the sign-up hint and the i18n landing
danielfdez17 Nov 1, 2026
15f689b
chore(osionos): pin the canvas-date-bomb merge and three nested pins
danielfdez17 Nov 1, 2026
b03c29b
chore(born2root): pin the inception profile and the README docs link
danielfdez17 Nov 1, 2026
a04e419
chore(monkey-bot): pin the security MCP server and the scripts rewind
danielfdez17 Nov 1, 2026
e238482
chore(grobase): pin develop with the follow-redirects floor
danielfdez17 Nov 1, 2026
ab26983
chore(graph_render): rewind the pin 559 commits to 77b68d9
danielfdez17 Nov 1, 2026
56f8fd6
config: triggering CI after pushing commits to remote repositories
danielfdez17 Nov 1, 2026
1afe240
fix(graph_render): pin the commit the studio pack was built from
danielfdez17 Nov 1, 2026
a54dfa5
fix(ci): solve 'make all' bats tests
danielfdez17 Nov 1, 2026
89d690f
fix(osionos): pin the nested modules the app build imports
danielfdez17 Nov 1, 2026
2cac029
chore(cursor): forbid a co-author trailer on commits
danielfdez17 Nov 4, 2026
c3ab51f
fix(opposite-osiris): pin the site overhaul that still has the V4 views
danielfdez17 Nov 4, 2026
4bd682a
fix(grobase): pin main instead of the disposable follow-redirects branch
danielfdez17 Nov 4, 2026
d6c0025
fix(ai): drop the share-rule lines main already carries
danielfdez17 Nov 4, 2026
d5abeea
fix(gitmodule): Drop blank line added
danielfdez17 Nov 4, 2026
c1e79a6
refactor(scripts): finish the [ to [[ sweep in the touched files
danielfdez17 Nov 4, 2026
c0fe376
Merge branch 'main' into fix/ci
danielfdez17 Nov 4, 2026
bf6c615
fix: make-all-footprint tests
danielfdez17 Nov 4, 2026
00cf686
fix(ci): each BaaS CI job is a documented make target
SMOSTAFAH1 Nov 5, 2026
b02dc5d
merge(develop): resolve make-all-footprint conflict
SMOSTAFAH1 Nov 5, 2026
6d00b26
Merge remote-tracking branch 'origin/develop' into fix/ci
SMOSTAFAH1 Nov 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude
31 changes: 31 additions & 0 deletions .cursor/rules/no-commit-coauthor.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
description: Commits and pull requests have one human author. No Cursor co-author trailer.
alwaysApply: true
---

# One author on commits

A commit or pull request has one author: the person who asked for it. The message must not contain `Co-authored-by` or `Generated with`.

Passing a clean message is not enough. The commit path can append `Co-authored-by: Cursor <cursoragent@cursor.com>` after the text you supplied. Reviewers read the stored object.

After every `git commit` you create, including a commit inside a submodule, read the stored message:

```sh
git log -1 --format='%B' | grep -E -i 'co-authored-by|generated with'
```

No output means the object is clean. Any output means a trailer was injected. When that commit is one you just created and it is not on the remote, amend the message back to the exact text you intended, trailer removed, same tree, no `--no-verify`. Leave a commit that is already pushed alone and say so.

Author and committer stay the person who asked for the commit. Do not set either to Cursor.

```sh
git commit -m "$(cat <<'EOF'
fix(scope): what changed

Why it had to change.
EOF
)"
```

The same ban applies to a pull request title and body.
57 changes: 17 additions & 40 deletions .github/workflows/baas-ci.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
# BaaS CI — fast pre-merge checks for the grobase backend.
#
# grobase is now a SEPARATE repo (Univers42/grobase), tracked here as the
# `apps/grobase` submodule. Its own CI (that repo's .github/workflows/ci.yml) runs
# the exhaustive image-build + integration + enterprise battery. THIS workflow is
# the lean superproject gate: it checks out the pinned grobase commit and proves
# the Go control-plane and the two Rust planes compile + unit-test, plus validates
# static config. Toolchains run in containers (Docker-first) so no host setup drifts.
# Each job is one documented Makefile target (subject: single-command / make entrypoints).
# grobase is a SEPARATE repo (Univers42/grobase), tracked here as apps/grobase.
name: BaaS CI

on:
pull_request:
paths:
- "apps/grobase"
- "utils/mk/baas.mk"
- ".github/workflows/baas-ci.yml"
push:
branches: ["main", "feat/**", "fix/**", "chore/**"]
branches: ["main", "develop", "feat/**", "fix/**", "chore/**"]
paths:
- "apps/grobase"
- "utils/mk/baas.mk"
- ".github/workflows/baas-ci.yml"
workflow_dispatch:

Expand All @@ -26,7 +24,7 @@ concurrency:

jobs:
go:
name: Go control-plane (vet + test)
name: Go control-plane (make baas-ci-go)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
Expand All @@ -39,13 +37,11 @@ jobs:
run: |
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "git@github.com:"
git submodule update --init apps/grobase
- name: vet + test (containerised toolchain)
run: |
docker run --rm -v "$PWD/apps/grobase/src/control-plane:/src" -w /src \
golang:1.25-bookworm sh -c "go vet ./... && go test ./..."
- name: make baas-ci-go
run: make baas-ci-go

rust-data-plane:
name: Rust data-plane (test)
name: Rust data-plane (make baas-ci-rust-data-plane)
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
Expand All @@ -58,14 +54,11 @@ jobs:
run: |
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "git@github.com:"
git submodule update --init apps/grobase
- name: cargo test --workspace
run: |
docker run --rm -e CARGO_NET_RETRY=10 -e CARGO_HTTP_MULTIPLEXING=false \
-v "$PWD/apps/grobase/src/data-plane-router:/src" -w /src \
rust:1-bookworm cargo test --workspace
- name: make baas-ci-rust-data-plane
run: make baas-ci-rust-data-plane

rust-realtime:
name: Rust realtime (test)
name: Rust realtime (make baas-ci-rust-realtime)
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
Expand All @@ -78,14 +71,11 @@ jobs:
run: |
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "git@github.com:"
git submodule update --init apps/grobase
- name: cargo test --workspace
run: |
docker run --rm -e CARGO_NET_RETRY=10 -e CARGO_HTTP_MULTIPLEXING=false \
-v "$PWD/apps/grobase/infra/docker/services/realtime/realtime-agnostic:/src" -w /src \
rust:1-bookworm cargo test --workspace
- name: make baas-ci-rust-realtime
run: make baas-ci-rust-realtime

static:
name: Static config (manifest parity + Prometheus rules)
name: Static config (make baas-ci-static)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
Expand All @@ -98,18 +88,5 @@ jobs:
run: |
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "git@github.com:"
git submodule update --init apps/grobase
- name: package manifest parity (config == Go-embedded copy)
working-directory: apps/grobase
run: |
diff <(jq -S . infra/config/packages/packages.json) \
<(jq -S . src/control-plane/internal/packages/packages.json) \
&& echo "package manifest is the single source of truth"
- name: Prometheus rules + config (m52)
working-directory: apps/grobase
run: |
docker run --rm --entrypoint promtool \
-v "$PWD/infra/config/prometheus":/etc/prometheus \
prom/prometheus:v2.52.0 check config /etc/prometheus/prometheus.yml
- name: shellcheck verify gates (advisory)
working-directory: apps/grobase
run: shellcheck -S error scripts/verify/m*.sh || true
- name: make baas-ci-static
run: make baas-ci-static
16 changes: 9 additions & 7 deletions infrastructure/docker/osionos/ai.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,21 @@
# IMPORT CLOSURE of services/osionos-ai/src into scripts/ — bridge-session-token
# imports bridge-session-cutoff (which imports bridge-baas-body), and since the
# account platform's P0-4 also bridge-session-revocations and bridge-roles; both
# revocations and cutoff import bridge-session-tracker:
# revocations and cutoff import bridge-session-tracker, which imports
# bridge-access-generation. Share rules (retrieval/shareRules.ts) call
# bridge-page-access, which calls bridge-share-rules:
# services/osionos-ai/src/auth.ts -> scripts/bridge-session-token.mjs
# -> scripts/bridge-session-cutoff.mjs
# -> scripts/bridge-baas-body.mjs
# -> scripts/bridge-session-revocations.mjs
# -> scripts/bridge-session-tracker.mjs
# -> scripts/bridge-roles.mjs
# -> scripts/bridge-session-tracker.mjs
# -> scripts/bridge-access-generation.mjs
# services/osionos-ai/src/runs/prompt.ts -> scripts/bridge-page-read.mjs
# services/osionos-ai/src/retrieval/acl.ts -> scripts/bridge-live-workspaces.mjs
# services/osionos-ai/src/retrieval/related.ts -> scripts/bridge-graph-edges.mjs -> scripts/bridge-page-read.mjs
# services/osionos-ai/src/retrieval/shareRules.ts -> scripts/bridge-page-access.mjs
# -> scripts/bridge-share-rules.mjs
# -> scripts/bridge-access-generation.mjs
# services/osionos-ai/src/retrieval/related.ts -> scripts/bridge-graph-edges.mjs -> scripts/bridge-page-read.mjs
# The thirteenth is the L0 gate of the image prompt (devil C72): the module reads
# its lists next to itself, so they travel with it:
# scripts/bridge-moderation.mjs -> scripts/moderation-lists/{en,fr,es}.txt
Expand Down Expand Up @@ -63,10 +65,10 @@ COPY --from=deps /app/services/osionos-ai/node_modules ./services/osionos-ai/nod
# The service itself: its manifest, its sources. No tests, no dev deps.
COPY services/osionos-ai/package.json services/osionos-ai/tsconfig.json ./services/osionos-ai/
COPY services/osionos-ai/src ./services/osionos-ai/src
# The thirteen bridge modules (see the header): the twelve of the import closure and
# the gate of the image prompt. scripts/tests/bridge-image-closure.bats checks
# The thirteen bridge modules (see the header): the twelve of the import closure
# and the gate of the image prompt. scripts/tests/bridge-image-closure.bats checks
# this list against the closure, so a new import fails CI, not the container.
COPY scripts/bridge-session-token.mjs scripts/bridge-session-cutoff.mjs scripts/bridge-baas-body.mjs scripts/bridge-session-revocations.mjs scripts/bridge-session-tracker.mjs scripts/bridge-roles.mjs scripts/bridge-page-read.mjs scripts/bridge-live-workspaces.mjs scripts/bridge-graph-edges.mjs scripts/bridge-page-access.mjs scripts/bridge-share-rules.mjs scripts/bridge-access-generation.mjs scripts/bridge-moderation.mjs ./scripts/
COPY scripts/bridge-session-token.mjs scripts/bridge-session-cutoff.mjs scripts/bridge-baas-body.mjs scripts/bridge-session-revocations.mjs scripts/bridge-session-tracker.mjs scripts/bridge-access-generation.mjs scripts/bridge-roles.mjs scripts/bridge-page-read.mjs scripts/bridge-page-access.mjs scripts/bridge-share-rules.mjs scripts/bridge-live-workspaces.mjs scripts/bridge-graph-edges.mjs scripts/bridge-moderation.mjs ./scripts/
# The lists of the L0 gate: the module reads them next to itself, at import.
COPY scripts/moderation-lists ./scripts/moderation-lists

Expand Down
10 changes: 5 additions & 5 deletions infrastructure/docker/osionos/ai.Dockerfile.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@ vendor
src
tests
# The scripts/ directory holds a hundred-odd modules; the service's closure is
# twelve of them, and the gate of the image prompt a thirteenth. They are re-included
# one by one, so the closure is visible in both files
# twelve of them, and the gate of the image prompt a thirteenth. They are
# re-included one by one, so the closure is visible in both files
# (scripts/tests/bridge-image-closure.bats checks it).
scripts/*
!scripts/bridge-session-token.mjs
!scripts/bridge-session-cutoff.mjs
!scripts/bridge-baas-body.mjs
!scripts/bridge-session-revocations.mjs
!scripts/bridge-session-tracker.mjs
!scripts/bridge-access-generation.mjs
!scripts/bridge-roles.mjs
!scripts/bridge-page-read.mjs
!scripts/bridge-live-workspaces.mjs
!scripts/bridge-graph-edges.mjs
!scripts/bridge-page-access.mjs
!scripts/bridge-share-rules.mjs
!scripts/bridge-access-generation.mjs
!scripts/bridge-live-workspaces.mjs
!scripts/bridge-graph-edges.mjs
!scripts/bridge-moderation.mjs
# The lists of the L0 gate: the module reads them next to itself, at import.
!scripts/moderation-lists/
Expand Down
44 changes: 22 additions & 22 deletions scripts/gen-local-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ note() { printf '[gen-local-env] %s\n' "$1" >&2; }

# The host port the osionos app is published on. The environment wins (make exports it);
# .env.ports is the record scripts/host-ports.sh wrote. 3001 is the fallback.
if [ -z "${OSIONOS_APP_HOST_PORT:-}" ]; then
if [[ -z "${OSIONOS_APP_HOST_PORT:-}" ]]; then
PORTS_FILE="${HOST_PORTS_FILE:-$REPO/.env.ports}"
if [ -f "$PORTS_FILE" ]; then
if [[ -f "$PORTS_FILE" ]]; then
OSIONOS_APP_HOST_PORT="$(get_env "$PORTS_FILE" OSIONOS_APP_HOST_PORT || true)"
fi
fi
Expand Down Expand Up @@ -103,8 +103,8 @@ gsrc() {
while :; do
_one="${_spec%%|*}"
_v="$(gval "$_one")"
[ -n "$_v" ] && { printf '%s' "$_v"; return 0; }
[ "$_spec" = "$_one" ] && return 0
[[ -n "$_v" ]] && { printf '%s' "$_v"; return 0; }
[[ "$_spec" = "$_one" ]] && return 0
_spec="${_spec#*|}"
done
}
Expand All @@ -118,7 +118,7 @@ gen_password() { gen | head -c 24; }
# minted_missing: names of MINTED keys absent or empty in $OUT, one per line.
minted_missing() {
for key in $MINTED; do
[ -n "$(val "$OUT" "$key")" ] || printf '%s\n' "$key"
[[ -n "$(val "$OUT" "$key")" ]] || printf '%s\n' "$key"
done
}

Expand Down Expand Up @@ -150,12 +150,12 @@ mint_missing() {
media_fill() {
for key in $MEDIA; do
want="$(get_env "$MEDIA_ENV" "$key" || true)"
[ -n "$want" ] || continue
[[ -n "$want" ]] || continue
have="$(get_env "$OUT" "$key" || true)"
if [ -z "$have" ]; then
if [[ -z "$have" ]]; then
put_env "$OUT" "$key" "$want"
note "copied $key from $MSRC"
elif [ "$have" != "$want" ]; then
elif [[ "$have" != "$want" ]]; then
note "kept $key (differs from $MSRC)"
fi
done
Expand All @@ -166,7 +166,7 @@ media_differing() {
for key in $MEDIA; do
want="$(get_env "$MEDIA_ENV" "$key" || true)"
have="$(get_env "$OUT" "$key" || true)"
if [ -n "$want" ] && [ -n "$have" ] && [ "$want" != "$have" ]; then printf '%s\n' "$key"; fi
if [[ -n "$want" ]] && [[ -n "$have" ]] && [[ "$want" != "$have" ]]; then printf '%s\n' "$key"; fi
done
}

Expand All @@ -179,36 +179,36 @@ drift_report() {
dst="${pair%%=*}"
src="${pair#*=}"
want="$(gsrc "$src")"
if [ -z "$want" ]; then printf 'unknown %s\n' "$dst"; bad=1
if [[ -z "$want" ]]; then printf 'unknown %s\n' "$dst"; bad=1
elif ! grep -q "^$dst=" "$OUT"; then printf 'missing %s\n' "$dst"; bad=1
elif [ "$(val "$OUT" "$dst")" = "$want" ]; then printf 'sync %s\n' "$dst"
elif [[ "$(val "$OUT" "$dst")" = "$want" ]]; then printf 'sync %s\n' "$dst"
else printf 'drift %s\n' "$dst"; bad=1
fi
done
want="$(port_url)"
for dst in $PORT_KEYS; do
if ! grep -q "^$dst=" "$OUT"; then printf 'missing %s\n' "$dst"; bad=1
elif [ "$(val "$OUT" "$dst")" = "$want" ]; then printf 'sync %s\n' "$dst"
elif [[ "$(val "$OUT" "$dst")" = "$want" ]]; then printf 'sync %s\n' "$dst"
else printf 'drift %s\n' "$dst"; bad=1
fi
done
return "$bad"
}

require_inputs() {
[ -f "$GRO_ENV" ] || { note "$SRC not found — bring the backend up first (make backend-up)."; exit 1; }
[[ -f "$GRO_ENV" ]] || { note "$SRC not found — bring the backend up first (make backend-up)."; exit 1; }
}

check_mode() {
require_inputs
[ -f "$OUT" ] || { note "$(basename "$OUT") absent — run without --check to generate it."; exit 1; }
[[ -f "$OUT" ]] || { note "$(basename "$OUT") absent — run without --check to generate it."; exit 1; }
rc=0
report="$(drift_report)" || rc=$?
for key in $(minted_missing); do report="$report
missing $key"; rc=1; done
printf '%s\n' "$report" | sed 's/^/[gen-local-env] /' >&2
for key in $(media_differing); do note " media $key differs from $MSRC (informational, exit status unchanged)"; done
if [ "$rc" -eq 0 ]; then note "in sync with $SRC (8 derived keys + 4 port-derived keys)."; else
if [[ "$rc" -eq 0 ]]; then note "in sync with $SRC (8 derived keys + 4 port-derived keys)."; else
note "DRIFT — $(basename "$OUT") disagrees with $SRC. Fix: bash scripts/gen-local-env.sh --sync"
fi
exit "$rc"
Expand All @@ -218,12 +218,12 @@ missing $key"; rc=1; done
# atomic, mode kept, every other line untouched). An EMPTY source is never written.
sync_mode() {
require_inputs
[ -f "$OUT" ] || { note "$(basename "$OUT") absent — generating it."; generate; return; }
[[ -f "$OUT" ]] || { note "$(basename "$OUT") absent — generating it."; generate; return; }
mint_missing
media_fill
rc=0
report="$(drift_report)" || rc=$?
[ "$rc" -ne 0 ] || { note "in sync with $SRC — nothing to write."; exit 0; }
[[ "$rc" -ne 0 ]] || { note "in sync with $SRC — nothing to write."; exit 0; }
changed=""
unknown=""
for pair in $DERIVED; do
Expand All @@ -240,12 +240,12 @@ sync_mode() {
changed="$changed $dst"
fi
done
[ -z "$changed" ] || note "synced from $SRC:$changed"
[ -z "$unknown" ] || { note "left as is (EMPTY source in $SRC — is the backend healthy?):$unknown"; exit 1; }
[[ -z "$changed" ]] || note "synced from $SRC:$changed"
[[ -z "$unknown" ]] || { note "left as is (EMPTY source in $SRC — is the backend healthy?):$unknown"; exit 1; }
}

generate() {
if [ -f "$OUT" ] && [ "$FORCE" != 1 ]; then
if [[ -f "$OUT" ]] && [[ "$FORCE" != 1 ]]; then
note "$(basename "$OUT") already present — leaving it (FORCE=1 to overwrite; --sync to refresh the derived keys)."
exit 0
fi
Expand All @@ -261,7 +261,7 @@ PUBLIC="$(gval ANON_KEY)"
SERVICE="$(gval SERVICE_ROLE_KEY)"
JWT="$(gval JWT_SECRET)"
ADAPTER="$(gval ADAPTER_REGISTRY_SERVICE_TOKEN)"
if [ -z "$PUBLIC" ] || [ -z "$SERVICE" ] || [ -z "$JWT" ]; then
if [[ -z "$PUBLIC" ]] || [[ -z "$SERVICE" ]] || [[ -z "$JWT" ]]; then
note "grobase .env is missing ANON_KEY/SERVICE_ROLE_KEY/JWT_SECRET — is the backend healthy?"
exit 1
fi
Expand Down Expand Up @@ -363,5 +363,5 @@ case "$MODE" in
--check) check_mode ;;
--sync) sync_mode ;;
generate) generate ;;
*) printf 'usage: gen-local-env.sh [--check|--sync] (no flag: generate ./.env.local when absent)\n' >&2; exit 2 ;;
*) printf 'usage: gen-local-env.sh [[--check|--sync]] (no flag: generate ./.env.local when absent)\n' >&2; exit 2 ;;
esac
Loading
Loading