docs(design): starter-credits bridge and write-only secrets - #6183
docs(design): starter-credits bridge and write-only secrets#6183mmabrouk wants to merge 1 commit into
Conversation
|
@coderabbitai review |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
❌ Action failedReview failed.
|
|
Oops, something went wrong! Please try again later. 🐰 💔 |
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdded four design documents for the starter-credits bridge. The documents define signup provisioning, policy and budget controls, vault contracts, proxy deployment, failure handling, and teardown sequencing. ChangesStarter-credits bridge
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: 🟡 Moderate · up to The documentation currently omits several concrete behavior and deployment constraints, including when new organizations qualify, which proxy URLs are valid, how policy changes affect active keys, how teardown and failed writes behave, how proxy errors are classified, and how development defaults are prevented from arming production. These omissions could lead to incorrect setup or unsafe runtime behavior, so follow-up is needed before merge. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
❌ Action failedReview failed.
|
|
Oops, something went wrong! Please try again later. 🐰 💔 |
❌ Action failedReview failed.
|
|
Oops, something went wrong! Please try again later. 🐰 💔 |
|
There was a problem hiding this comment.
Actionable comments posted: 7
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 330c2687-cc20-46f6-8b6f-d2673cef65c4
📒 Files selected for processing (4)
docs/design/starter-credits-bridge/README.mddocs/design/starter-credits-bridge/design.mddocs/design/starter-credits-bridge/proxy-and-deployment.mddocs/design/starter-credits-bridge/write-only-secrets.md
Included review availability: Your plan provides up to 8 included reviews per hour; 2 remain after this review.
| Resolution is deliberately awkward, and every branch fails toward not seeding: | ||
|
|
||
| | Situation | Result | | ||
| | --- | --- | | ||
| | Live payload present and valid | Seed. The payload is cached for transport outages | | ||
| | Live payload present and malformed | Do not seed. Alert the operator. The cache is deliberately not consulted, because a bad rollout must never silently keep the old caps | | ||
| | PostHog reachable, no payload | Do not seed. A reachable source with nothing to say is a real "no policy" signal | | ||
| | PostHog unreachable | Use the cached payload if there is one, otherwise do not seed | | ||
| | The deployment configured no PostHog key of its own | Use the built-in development policy | |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(design\.md|.*starter.*credits.*|.*credits.*bridge.*)$|posthog|teardown|cache' | head -200
printf '%s\n' '--- design references ---'
rg -n -i -C 4 'cache|teardown|clear(ing)?|disable|seed|PostHog|payload|resolver|outage' docs/design/starter-credits-bridge/design.md
printf '%s\n' '--- related repository references ---'
rg -n -i -C 3 'starter.?credits|credits.?bridge|cached payload|cache invalid|clear.*payload|PostHog unreachable|no policy' . --glob '!node_modules' --glob '!dist' --glob '!build' | head -400Repository: Agenta-AI/agenta
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- design size and focused sections ---'
wc -l docs/design/starter-credits-bridge/design.md
sed -n '100,180p' docs/design/starter-credits-bridge/design.md
sed -n '180,280p' docs/design/starter-credits-bridge/design.md
printf '%s\n' '--- tracked files with focused names ---'
git ls-files | rg -i 'starter|credit|posthog|bridge' | head -200
printf '%s\n' '--- focused references outside generated/schema files ---'
git grep -n -i -E 'starter.?credits|credits.?bridge|cached payload|cache invalid|clear.*payload|PostHog unreachable|no policy' -- ':!**/openapi*' ':!**/schema*' ':!**/generated/**' ':!**/node_modules/**' | head -300Repository: Agenta-AI/agenta
Length of output: 25936
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- design tail ---'
cat -n docs/design/starter-credits-bridge/design.md | sed -n '280,339p'
printf '%s\n' '--- README focused sections ---'
cat -n docs/design/starter-credits-bridge/README.md | sed -n '1,110p'
printf '%s\n' '--- proxy and deployment focused sections ---'
wc -l docs/design/starter-credits-bridge/proxy-and-deployment.md
rg -n -i -C 8 'teardown|stop|clear|payload|cache|disable|PostHog|seed' docs/design/starter-credits-bridge/proxy-and-deployment.mdRepository: Agenta-AI/agenta
Length of output: 16014
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
p = Path("docs/design/starter-credits-bridge/design.md")
lines = p.read_text().splitlines()
resolution = "\n".join(lines[132:146])
teardown = "\n".join(lines[314:321])
checks = {
"valid live payload is cached": "payload is cached" in resolution,
"PostHog outage uses cache": "PostHog unreachable" in resolution and "cached payload" in resolution,
"clearing live payload is teardown step": "clearing the policy payload" in teardown,
"teardown explicitly invalidates cache": any(
term in teardown.lower()
for term in ("invalidate", "cache", "disable check", "clear the cache")
),
}
for name, result in checks.items():
print(f"{name}: {result}")
print("\n--- resolver branch ---")
print("live payload cleared + PostHog unreachable => cached payload selected")
print("--- teardown text ---")
print(teardown)
PYRepository: Agenta-AI/agenta
Length of output: 695
Make teardown independent of the cached policy. If teardown clears the live payload while PostHog is unreachable, the resolver still uses the cached payload and seeds new signups. Add a local disable check before cache use or explicitly invalidate the cache.
🧰 Tools
🪛 LanguageTool
[grammar] ~138-~138: Ensure spelling is correct
Context: ...ward, and every branch fails toward not seeding: | Situation | Result | | --- | --- | | L...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
| ## Per-key concurrency and throughput caps | ||
|
|
||
| Every minted key carries a maximum number of parallel requests, a requests-per-minute | ||
| limit, and a tokens-per-minute limit, all from the policy payload. | ||
|
|
||
| The grant alone bounds what one organization can spend in total. It does not bound how | ||
| fast. Two things break under speed rather than volume. The upstream provider's throughput | ||
| quota is shared by everything the operator's credential serves, so one organization | ||
| looping calls can starve every other funded organization. And a burst that drains a grant | ||
| in minutes turns a trial into an error message before the person has typed a second | ||
| message. | ||
|
|
||
| The proxy also enforces its own upper bounds on what a key may be issued with. Raising a | ||
| policy value above the proxy's ceiling makes every mint fail with HTTP 400 until the proxy | ||
| configuration is raised and redeployed. Lowering a value is a live payload edit. That | ||
| asymmetry is intentional: loosening requires two people and a deploy, tightening does not. |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(design\.md|.*(bridge|policy|virtual.*key|key).*)$' | head -200
printf '%s\n' '--- relevant symbols and phrases ---'
rg -n -S -i 'starter.?credits|virtual key|per-key|policy payload|PostHog|mint|grant|tokens-per-minute|requests-per-minute|parallel requests|active bridge|block|update' docs src .github 2>/dev/null | head -300Repository: Agenta-AI/agenta
Length of output: 47781
🏁 Script executed:
#!/bin/bash
set -eu
for f in docs/design/starter-credits-bridge/README.md \
docs/design/starter-credits-bridge/design.md \
docs/design/starter-credits-bridge/proxy-and-deployment.md \
docs/design/starter-credits-bridge/write-only-secrets.md; do
echo "--- $f ($(wc -l < "$f") lines) ---"
cat -n "$f"
doneRepository: Agenta-AI/agenta
Length of output: 45177
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
root = Path("docs/design/starter-credits-bridge")
terms = re.compile(
r"\b(existing|active|future|mint|minted|issued|policy|payload|"
r"update|change|lower|tighten|block|block all|revoke|unblock|"
r"bulk|every virtual key|never touches)\b",
re.I,
)
for path in sorted(root.glob("*.md")):
print(f"--- {path} ---")
for number, line in enumerate(path.read_text().splitlines(), 1):
if terms.search(line):
print(f"{number}: {line}")
print("--- capability check ---")
text = "\n".join(path.read_text() for path in root.glob("*.md")).lower()
for phrase in (
"update every",
"update all",
"bulk update",
"revoke every",
"block every virtual key",
"future mints",
"existing keys",
):
print(f"{phrase!r}: {phrase in text}")
PYRepository: Agenta-AI/agenta
Length of output: 7962
Define policy-change semantics for active virtual keys.
The policy payload can change live, but each virtual key receives its grant and limits at mint time. State whether changes apply only to future mints, or define an operation that updates or blocks active bridge keys.
| An invited teammate is not a special case. They still sign up, and the signup path creates | ||
| their own organization unless they already belong to one, so they are seeded exactly like | ||
| any other new signup. Nobody has to seed an organization twice, and nobody has to decide | ||
| what an invitation into a funded organization means. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
State invitation eligibility explicitly.
An invited teammate who joins an existing organization does not create a new organization. The sentence “they are seeded exactly like any other new signup” conflicts with the new-signups-only rule. State that only signups creating a new organization are eligible.
| ## No repair path | ||
|
|
||
| Seeding does two writes that can fail independently: it mints a key at the proxy, then it | ||
| writes the vault row. A crash between them leaves an organization with a live key that | ||
| nothing references. | ||
|
|
||
| The bridge handles that inline and then stops. If the row write fails, the service blocks | ||
| the just-minted key, so an orphaned grant can never be spent. Nothing retries later. | ||
|
|
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- design document context ---'
sed -n '180,260p' docs/design/starter-credits-bridge/design.md
printf '%s\n' '--- related bridge and block references ---'
rg -n -i 'block|orphan|repair|retry|expire|mint|vault' docs/design/starter-credits-bridge
printf '%s\n' '--- repository files near the design ---'
git ls-files | rg 'starter-credits|bridge|proxy|vault' | head -200Repository: Agenta-AI/agenta
Length of output: 18012
🏁 Script executed:
#!/bin/bash
set -e
for f in $(git ls-files 'api/ee/src/core/starter_credits_bridge/*' | sort); do
printf '\n--- %s ---\n' "$f"
wc -l "$f"
ast-grep outline "$f" 2>/dev/null || true
done
printf '\n--- bridge symbols and call sites ---\n'
rg -n -i 'block|generate|mint|seed|vault|starter_credits_bridge|provision_signup_subscription' \
api/ee/src api/ee/tests 2>/dev/null | head -300Repository: Agenta-AI/agenta
Length of output: 11363
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- signup provisioning implementation ---'
sed -n '900,990p' api/ee/src/core/organizations/service.py
printf '%s\n' '--- bridge-related files discovered by content search ---'
rg -l -i 'starter.?credits|proxy admin|virtual key|seeded connection|PROXY_ADMIN_URL' api docs \
-g '*.py' -g '*.md' -g '*.yaml' -g '*.yml' | sort
printf '%s\n' '--- repository state and recent file summary ---'
git status --short
git diff --statRepository: Agenta-AI/agenta
Length of output: 3206
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- proxy contract ---'
cat -n docs/design/starter-credits-bridge/proxy-and-deployment.md | sed -n '1,90p'
printf '%s\n' '--- design failure, timeout, alert, and teardown sections ---'
cat -n docs/design/starter-credits-bridge/design.md | sed -n '1,75p;120,180p;260,350p'
printf '%s\n' '--- all documented proxy operation contracts ---'
rg -n -C 3 -i 'block|unblock|timeout|alert|failure|retry|expiry|idempot|generate|mint|live|spend' \
docs/design/starter-credits-bridge/*.mdRepository: Agenta-AI/agenta
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
design = Path("docs/design/starter-credits-bridge/design.md").read_text()
proxy = Path("docs/design/starter-credits-bridge/proxy-and-deployment.md").read_text()
readme = Path("docs/design/starter-credits-bridge/README.md").read_text()
required = [
"mints a key at the proxy",
"writes the vault row",
"blocks the just-minted key",
"Nothing retries later",
"Mint succeeds, vault write fails",
]
for text in required:
assert text in design, f"missing design contract: {text!r}"
section = design.split("## No repair path", 1)[1].split("## ", 1)[0].lower()
for term in ("retry", "repair", "expiry", "expire", "transaction"):
print(f"{term}: {term in section}")
print("block capability:", "Block and unblock a key" in proxy)
print("failure alert documented:", "operator alert" in readme.lower())
print("failure-table behavior:")
for line in design.splitlines():
if "Mint succeeds, vault write fails" in line:
print(line)
PYRepository: Agenta-AI/agenta
Length of output: 298
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
import re
def normalized(path):
return re.sub(r"\s+", " ", Path(path).read_text()).strip()
design = normalized("docs/design/starter-credits-bridge/design.md")
proxy = normalized("docs/design/starter-credits-bridge/proxy-and-deployment.md")
readme = normalized("docs/design/starter-credits-bridge/README.md")
required = [
"mints a key at the proxy",
"writes the vault row",
"blocks the just-minted key",
"Nothing retries later",
"Mint succeeds, vault write fails",
]
for text in required:
assert text in design, f"missing design contract: {text!r}"
start = design.index("## No repair path")
end = design.find("## ", start + len("## No repair path"))
section = design[start:end if end != -1 else None].lower()
for term in ("retry", "repair", "expiry", "expire", "transaction"):
print(f"{term}: {term in section}")
print("block capability:", "Block and unblock a key" in proxy)
print("failure alert documented:", "operator alert" in readme.lower())
marker = "Mint succeeds, vault write fails"
pos = design.index(marker)
print("failure-table behavior:", design[pos:design.find("|", pos + len(marker))])
PYRepository: Agenta-AI/agenta
Length of output: 339
Define failure handling for the compensating block call.
If the vault write fails while the proxy is unavailable, the key can remain spendable after recovery without a vault row. Define bounded retries with an operator alert, or require proxy-side expiry or transactional issuance.
| ## Exhaustion is a product moment, not an error | ||
|
|
||
| When the organization's spend reaches the grant, the proxy refuses the call at admission | ||
| with HTTP 429. So does a per-key rate limit, and so does an upstream provider quota error. | ||
| Only the response body separates them. | ||
|
|
||
| The runner therefore classifies on the body, never on the status alone, and returns a | ||
| stable code alongside the human line (`RunErrorCode` in | ||
| `services/runner/src/engines/sandbox_agent/errors.ts`): | ||
| `starter_credits_exhausted`, `starter_credits_program_paused`, | ||
| `starter_credits_unavailable`, `rate_limited`, and `runner_error` for everything else. A | ||
| client can render a purposeful state from the code instead of parsing prose. Telling | ||
| someone their credits are gone when they were merely throttled is worse than saying | ||
| nothing, which is why the classification is narrow. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(design\.md|errors\.ts|sandbox_agent|starter.?credits|proxy|runner)' | head -200
printf '%s\n' '--- relevant symbols and text ---'
rg -n -S 'RunErrorCode|starter_credits_exhausted|starter_credits_program_paused|starter_credits_unavailable|rate_limited|response body|429|credits exhausted|program paused' . --glob '!node_modules' --glob '!dist' --glob '!build' | head -300Repository: Agenta-AI/agenta
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- exact tracked paths ---'
git ls-files 'docs/design/starter-credits-bridge/design.md' 'services/runner/src/engines/sandbox_agent/errors.ts'
printf '%s\n' '--- design section ---'
sed -n '260,310p' docs/design/starter-credits-bridge/design.md
printf '%s\n' '--- runner error module ---'
wc -l services/runner/src/engines/sandbox_agent/errors.ts
cat -n services/runner/src/engines/sandbox_agent/errors.ts
printf '%s\n' '--- bounded tracked text search ---'
git grep -n -I -E 'RunErrorCode|starter_credits_exhausted|starter_credits_program_paused|starter_credits_unavailable|rate_limited' -- \
'*.ts' '*.tsx' '*.js' '*.jsx' '*.py' '*.go' '*.rs' '*.md' '*.yaml' '*.yml' '*.json' \
':!*.lock' | head -300Repository: Agenta-AI/agenta
Length of output: 9022
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
import subprocess
from pathlib import Path
paths = subprocess.check_output(["git", "ls-files", "-z"], text=False).split(b"\0")
text_paths = []
for raw in paths:
if not raw:
continue
p = Path(raw.decode())
if p.suffix.lower() in {".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs",
".md", ".yaml", ".yml", ".json"}:
text_paths.append(p)
terms = [
"RunErrorCode",
"starter_credits_exhausted",
"starter_credits_program_paused",
"starter_credits_unavailable",
"rate_limited",
]
for term in terms:
hits = []
for p in text_paths:
try:
data = p.read_text()
except (UnicodeDecodeError, OSError):
continue
for lineno, line in enumerate(data.splitlines(), 1):
if term in line:
hits.append(f"{p}:{lineno}:{line.strip()}")
print(f"--- {term}: {len(hits)} occurrence(s) ---")
print("\n".join(hits[:100]))
print("--- likely runner tests ---")
for p in text_paths:
s = str(p).lower()
if ("runner" in s or "sandbox_agent" in s) and ("test" in s or "spec" in s):
print(p)
PYRepository: Agenta-AI/agenta
Length of output: 11069
Define and implement the proxy-to-runner error contract.
The cited errors.ts file only defines conciseError; no implementation or test defines the listed RunErrorCode values. Specify the proxy-owned JSON discriminator, exact values, ownership, fallback behavior, and end-to-end tests for HTTP 429 classification.
| ## The seeded URL must be HTTPS | ||
|
|
||
| `effective_endpoint` rejects any connection base URL that is not absolute HTTPS | ||
| (`sdks/python/agenta/sdk/agents/connections/endpoints.py`, "model connection endpoint must | ||
| be an absolute HTTPS URL"). There is no override and no local exception. | ||
|
|
||
| This is a real constraint on local development. A proxy on `http://localhost` cannot be | ||
| seeded, because the resolver refuses the connection before any call is made. Put a | ||
| tunnel with a real certificate in front of it, and seed the tunnel's URL. |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Document all endpoint-validation constraints.
This section says only “absolute HTTPS.” The downstream validator in services/runner/src/engines/sandbox_agent/daytona-secret-plan.ts:161-211 also rejects IP literals, local or internal hosts, wildcard hosts, non-default ports, credentials, and fragments, and requires a normalized public DNS hostname. List these constraints or link to the validator.
| Every variable is read once into `StarterCreditsBridgeConfig` | ||
| (`api/oss/src/utils/env.py`). The bridge is `armed` only when `ENABLED` is true and both | ||
| proxy addresses, the master key, and the team id are all present. An unarmed deployment | ||
| returns from the seeding call immediately, which is what every OSS and self-hosted | ||
| deployment does. | ||
|
|
||
| | Variable (`AGENTA_STARTER_CREDITS_BRIDGE_` prefix) | Required to arm | What it holds | | ||
| | --- | --- | --- | | ||
| | `ENABLED` | yes | The opt-in switch. Defaults to false. Changing it takes a redeploy | | ||
| | `PROXY_PUBLIC_URL` | yes | The base URL stored on the seeded connection. A sandboxed run dials it from outside the deployment's network, so only the proxy's inference paths are published there | | ||
| | `PROXY_ADMIN_URL` | yes | The proxy's address on the private network. The minting client dials it, so the master key never crosses the public edge | | ||
| | `MASTER_KEY` | yes | The proxy's admin credential. It mints and blocks keys | | ||
| | `TEAM_ID` | yes | The proxy team every minted key joins. The team's own budget ceiling bounds total exposure, so seeding refuses to run without one | | ||
| | `MODEL_ID` | no | The single model id the minted key allowlists and the seeded connection publishes. Defaults to `vertex_ai/gemini-3.6-flash` | | ||
| | `POLICY_FLAG` | no | The name of the PostHog feature flag whose payload carries the mint policy. Defaults to `starter-credits-bridge-policy` | | ||
| | `ALERT_WEBHOOK` | no | An operator webhook. The service posts `{"text": ...}` to it on refusals and failures | |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(README\.md|design\.md|env\.py)$|starter-credits-bridge'
printf '%s\n' '--- relevant references ---'
rg -n -C 5 'StarterCreditsBridgeConfig|AGENTA_STARTER_CREDITS_BRIDGE|development policy|PostHog|POLICY_FLAG|seed' docs api/oss/src 2>/dev/null | head -n 500Repository: Agenta-AI/agenta
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- bridge files ---'
git ls-files | rg 'starter-credits-bridge|api/oss/src/utils/env\.py$'
printf '%s\n' '--- README excerpt ---'
sed -n '1,130p' docs/design/starter-credits-bridge/README.md
printf '%s\n' '--- design policy excerpt ---'
sed -n '120,180p' docs/design/starter-credits-bridge/design.md
printf '%s\n' '--- configuration and environment references ---'
rg -n -C 8 --glob '*.py' --glob '*.md' --glob '*.yaml' --glob '*.yml' --glob '*.toml' \
'StarterCreditsBridgeConfig|AGENTA_STARTER_CREDITS_BRIDGE|development policy|POSTHOG|ENVIRONMENT|environment' \
docs/design/starter-credits-bridge api/oss/srcRepository: Agenta-AI/agenta
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- exact bridge paths ---'
find docs/design/starter-credits-bridge api/oss/src -maxdepth 8 -type f \( -name 'README.md' -o -name 'design.md' -o -name 'env.py' \) -print
printf '%s\n' '--- README lines 1-100 ---'
sed -n '1,100p' docs/design/starter-credits-bridge/README.md
printf '%s\n' '--- design lines 130-170 ---'
sed -n '130,170p' docs/design/starter-credits-bridge/design.md
printf '%s\n' '--- exact bridge symbol matches ---'
rg -n -C 10 --glob '*.py' 'StarterCreditsBridgeConfig|AGENTA_STARTER_CREDITS_BRIDGE' api/oss/src
printf '%s\n' '--- policy fallback matches in bridge docs ---'
rg -n -C 8 'development policy|PostHog|POLICY_FLAG|policy key|feature flag' docs/design/starter-credits-bridgeRepository: Agenta-AI/agenta
Length of output: 9059
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- bridge implementation files ---'
find api/ee/src/core/starter_credits_bridge api/oss/src -maxdepth 5 -type f 2>/dev/null | sort | head -n 100
printf '%s\n' '--- bridge symbol search across tracked source ---'
rg -n -C 6 --hidden --glob '!*.json' --glob '!*.lock' \
'StarterCreditsBridgeConfig|AGENTA_STARTER_CREDITS_BRIDGE|seed_starter_credits_bridge|development_policy|built-in development policy' .
printf '%s\n' '--- policy section ---'
rg -n -C 12 'The mint policy|PostHog key|no PostHog|development policy|configured no' docs/design/starter-credits-bridge/design.md
printf '%s\n' '--- env PostHog fields ---'
rg -n -C 8 --glob 'env.py' 'POSTHOG|posthog|environment|deployment' api/oss/src/utils/env.pyRepository: Agenta-AI/agenta
Length of output: 26044
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
import re
readme = Path("docs/design/starter-credits-bridge/README.md").read_text()
design = Path("docs/design/starter-credits-bridge/design.md").read_text()
env = Path("api/oss/src/utils/env.py").read_text()
required = set(re.findall(r"\| `([^`]+)` \| yes \|", readme))
assert required == {"ENABLED", "PROXY_PUBLIC_URL", "PROXY_ADMIN_URL", "MASTER_KEY", "TEAM_ID"}
fallback = re.search(
r"\| The deployment configured no PostHog key of its own \| ([^\n]+)",
design,
)
assert fallback and "built-in development policy" in fallback.group(1)
posthog_block = env[env.index("class PostHogConfig"):env.index("class EnvironSettings")]
assert 'or "https://alef.agenta.ai"' in posthog_block
assert 'or "phc_hmVSxIjTW1REBHXgj2aw4HW9X6CXb6FzerBgP9XenC7"' in posthog_block
print("arming variables:", ", ".join(sorted(required)))
print("missing from arming variables: POSTHOG_API_KEY")
print("no-own-key policy result:", fallback.group(1))
print("PostHog fallback: built-in api_url and built-in api_key")
PYRepository: Agenta-AI/agenta
Length of output: 403
Require an explicit non-production guard for the development policy.
ENABLED arms the bridge without POSTHOG_API_KEY. A missing deployment-owned key selects the built-in development policy, and PostHogConfig supplies a built-in key when the environment variable is absent. Add an explicit non-production condition or require a deployment-owned policy source before arming.
Context
The starter-credits bridge lands across six PRs (#6138, #6164, #6165, #6168, #6174,
#6179), and the reasoning behind it lives nowhere a reader of this repository can find it.
The code carries the invariants in comments, but not the options each decision was chosen
against.
This PR is docs only. It adds
docs/design/starter-credits-bridge/, written against thecode on those branches rather than against an earlier draft, because several decisions
moved during implementation (the repair path was removed, the mint policy became
payload-only, the proxy grew a second address).
Changes
Four files:
README.md. What the bridge does, the path from signup to exhaustion, a table of everypiece and where it lives, and the eight
AGENTA_STARTER_CREDITS_BRIDGE_*variables withwhich ones are needed to arm it.
design.md. Each decision with its options and the reason: one key per organizationrather than a shared key, a vault connection rather than a new funded route, write-only
and managed on the seeded row, the mint policy as an operator-controlled payload that
fails closed, per-key rate limits, no repair path, new signups only, and why the
connection's display name is part of the contract. Also the failure-mode table and the
teardown order.
write-only-secrets.md. The vault contract the seeded row depends on: the one-waywrite_onlyflag,has_keyandkey_preview, keep-stored-on-omit, thesecret-resolvegrant that keeps the runtime reading plaintext, and
managed_bywith its 409. It citesthe files and points at the two companion design folders for the full mechanism.
proxy-and-deployment.md. What a proxy has to provide, why the bridge holds a public anda private address for it, the two routing shapes and the one property that separates them,
the HTTPS-only constraint on a seeded connection URL, and what a sandbox can and cannot
reach.
Notes
policy payload, so the docs describe the mechanism and name no value.
../write-only-secrets/and../managed-secrets/resolve once [feat] Write-only vault secrets (values never readable back by users) #6164 and[feat] Map proxy budget refusals to a clear starter-credits message #6168 merge. Both folders are added by those PRs.