Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ Thumbs.db
# Worktrees
.worktrees/

# Local copies of the onboarding wrapper scripts (may contain team details)
onboard.sh
edge_cases.sh

# Compressed files
*.7z
*.dmg
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export LLM_BASELINE_STAGING_BUCKET
export LLM_BASELINE_NEWS_BUCKET
export BUILD_ENV
export WORKSPACE_BUCKET
export SUBMISSIONS_BUCKET
export SUBMISSIONS_INTERSTITIAL_BUCKET
export SUBMISSIONS_HISTORY_BUCKET
export SUBMISSIONS_SERVICE_ACCOUNT
export SMTP_USER

export CLOUD_DEPLOY_REGION := us-central1

Expand Down
11 changes: 11 additions & 0 deletions src/external-submissions/onboard/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# The onboard CLI is run locally by an admin; it is not deployed to GCP.
# This Makefile exists so that `make setup-python-env` installs requirements.txt
# and the root `make clean` can recurse into this directory.

.PHONY: all clean

all:
@:

clean:
rm -rf __pycache__

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a file called onboard.example.sh that looks likething like:

EMAILS=[]
ORGANIZATION=""
...
python main.py --emails=$EMAILS --organization=$ORGANIZATION --run-mode=TEST ...

add onboard.sh to .gitignore

Then we'll run from the script such that fields are easier to modify and commands don't stay in local history (so aren't unintentionally run).

77 changes: 77 additions & 0 deletions src/external-submissions/onboard/edge_cases.example.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/bin/bash
# Edge-case driver: copy to edge_cases.sh (gitignored) and run against a dev project only.
# Loops through labeled argument sets in TEST mode (no email is ever attempted), so a
# reviewer can see exactly which cases were exercised. Every case here is also covered in
# the unit test suite (src/tests/test_onboard.py).
#
# Labels starting with "ok" are expected to succeed; "fail" cases must exit non-zero.
#
# NB: GCS IAM only accepts EXISTING Google identities, so the ok-cases use $EMAIL (a real
# account — defaults to SMTP_USER) and $SERVICE_ACCOUNT (the real submissions SA).
# Fictional addresses appear only in cases expected to fail. The non-Google-email warning
# path can't be demoed live for the same reason; it is covered by the unit suite.
#
# NB: the loop uses `eval` so case strings can contain quoted arguments. Keep the CASES
# table admin-authored; never build it from external input.

set -a
. <(grep -v '^#' ../../../variables.mk | tr -d '\r')
set +a

EMAIL="${SMTP_USER}"
SERVICE_ACCOUNT="${SUBMISSIONS_SERVICE_ACCOUNT:-${SUBMISSIONS_SA_EMAIL:-}}"

if [ -z "$EMAIL" ]; then
echo "FAIL: SMTP_USER not set in variables.mk (needed as the real test identity)."
exit 1
fi
if [ -z "$SERVICE_ACCOUNT" ]; then
echo "FAIL: no submissions service account in variables.mk (needed for the SA cases)."
exit 1
fi

# The anonymous case needs the counter; harmless if it already exists.
python init_counters.py --anon-count 0 || true

CASES=(
"ok-minimal |--organization MinimalOrg --emails $EMAIL"
"ok-anonymous |--organization 'Secret Labs' --anonymous --emails $EMAIL"
"ok-service-account-only|--organization 'Bot Org' --service-accounts $SERVICE_ACCOUNT"
"ok-emails-plus-sa |--organization 'Mixed Org' --emails $EMAIL --service-accounts $SERVICE_ACCOUNT"
"ok-team-name |--organization 'Acme' --team-name acme-alpha --emails $EMAIL"
"ok-same-org-twice |--organization 'Acme' --emails $EMAIL"
"ok-unicode-org |--organization 'Gréta Łabs' --emails $EMAIL"
"ok-punctuation-org |--organization 'cmcc.vc' --emails $EMAIL"
"ok-long-org |--organization 'An Extremely Long Organization Name That Exceeds The Slug Limit For Sure' --emails $EMAIL"
"ok-send-test-email |--organization 'Mail Org' --emails $EMAIL --send-email-in-test"
"fail-duplicate-teamname|--organization 'Acme' --team-name acme-alpha --emails $EMAIL"
"fail-empty-org |--organization '' --emails $EMAIL"
"fail-no-emails-no-sa |--organization 'Nobody Org'"
"fail-invalid-email |--organization 'Typo Org' --emails not-an-email"
"fail-nonexistent-acct |--organization 'Ghost Org' --emails a@dummy-domain-x92ah8.com"
)

PASS=0
FAIL=0
for case in "${CASES[@]}"; do
label="$(echo "${case%%|*}" | xargs)"
args="${case#*|}"
echo "=== ${label} ==="
if eval python main.py register $args --mode TEST; then
outcome="succeeded"
else
outcome="failed"
fi
if { [[ $label == ok-* ]] && [ "$outcome" = "succeeded" ]; } ||
{ [[ $label == fail-* ]] && [ "$outcome" = "failed" ]; }; then
echo "--- ${label}: ${outcome} (as expected)"
PASS=$((PASS + 1))
else
echo "--- ${label}: ${outcome} (UNEXPECTED)"
FAIL=$((FAIL + 1))
fi
echo
done

echo "${PASS} as expected, ${FAIL} unexpected."
[ "$FAIL" -eq 0 ]
53 changes: 53 additions & 0 deletions src/external-submissions/onboard/init_counters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""One-time per-environment setup: create the anonymous-number counter.

Run exactly once when setting up a new environment (dev or prod), before the first
registration. Refuses to overwrite an existing counter, so re-running later is harmless
but will not change anything.

Usage (from this directory, with `variables.mk` loaded):

python init_counters.py --anon-count 8

`--anon-count` is the highest anonymous team number already issued (0 for a fresh
environment). Previously issued numbers must never be reissued, including any gaps.
"""

import argparse
import sys

from main import COUNTERS_COLLECTION, COUNTERS_DOCUMENT, get_clients


def init_counters(anon_count: int, db=None) -> dict:
"""Create the counter document; refuses to overwrite an existing one.

Args:
anon_count (int): Highest anonymous number already issued.
db: Firestore client (injected in tests).
"""
if db is None:
db, _ = get_clients()
counter_ref = db.collection(COUNTERS_COLLECTION).document(COUNTERS_DOCUMENT)
if counter_ref.get().exists:
raise ValueError(
f"{COUNTERS_COLLECTION}/{COUNTERS_DOCUMENT} already exists; not overwriting."
)
counters = {"anon_count": anon_count}
counter_ref.set(counters)
return counters


def main() -> None:
"""Parse arguments and create the counter document."""
parser = argparse.ArgumentParser(description="One-time anonymous-counter setup.")
parser.add_argument("--anon-count", type=int, required=True)
args = parser.parse_args()
try:
counters = init_counters(anon_count=args.anon_count)
except ValueError as exception:
sys.exit(str(exception))
print(f"Created {COUNTERS_COLLECTION}/{COUNTERS_DOCUMENT}: {counters}")


if __name__ == "__main__":
main()
Loading
Loading