Skip to content
Merged
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: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,10 @@ INTUIT_REDIRECT_URI=http://localhost:3001/connections/qb-callback

## CORS — extra origins for dev (e.g., ngrok tunnels for OAuth callbacks).
## Comma-separated. Dev only — staging/prod derive origins from the app URLs
## (ROBOLEDGER_URL / ROBOINVESTOR_URL / ROBOSYSTEMS_URL / HOLON_URL).
## (ROBOLEDGER_URL / ROBOINVESTOR_URL / ROBOSYSTEMS_URL / VIEWER_URL, plus
## HOLON_URL for the viewer's original host, served as an alias).
# EXTRA_CORS_ORIGINS=https://your-tunnel.ngrok-free.dev
# VIEWER_URL=
# HOLON_URL=

## Stripe
Expand Down
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,9 @@ Live compliance posture and audit artifacts are published in the [RoboSystems Tr

- GitHub OIDC federation (no long-term AWS credentials)
- Federated role restricted to `main` branch, `release/*` branches, and `v*` tags
- Scoped to specific repositories (robosystems, robosystems-app, roboledger-app, roboinvestor-app, robosystems-holon-viewer)
- Scoped to specific repositories (robosystems, robosystems-app, roboledger-app, roboinvestor-app, xbrlkit-viewer)
- 1-hour maximum session duration
- Permission scoping: ECR limited to `robosystems*`, S3 limited to `robosystems-*`
- Permission scoping: ECR limited to `robosystems*`; S3 limited to `robosystems-*` for the backend role, and to the app and viewer static-asset bucket prefixes for the frontend role

### Infrastructure as Code

Expand Down
2 changes: 1 addition & 1 deletion bin/setup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The complete bootstrap process for a fresh deployment:
│ Deploys cloudformation/bootstrap-oidc.yaml: │
│ - Creates IAM OIDC Provider for GitHub │
│ - Backend role: trusts {GitHubOrg}/{backend repo} only │
│ - Frontend role: trusts the three *-app repos + holon-viewer
│ - Frontend role: trusts the three *-app repos + xbrlkit-viewer │
│ - Allowed refs (both roles): main, release/*, v* tags │
└─────────────────────────────────────────────────────────────────────────────┘
Expand Down
4 changes: 2 additions & 2 deletions bin/setup/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ deploy_github_oidc() {
print_info " - ${GITHUB_ORG}/robosystems-app"
print_info " - ${GITHUB_ORG}/roboledger-app"
print_info " - ${GITHUB_ORG}/roboinvestor-app"
print_info " - ${GITHUB_ORG}/robosystems-holon-viewer"
print_info " - ${GITHUB_ORG}/xbrlkit-viewer"

# Branch patterns are hardcoded in the template: main, release/*, v* tags
echo ""
Expand Down Expand Up @@ -699,7 +699,7 @@ configure_frontend_repos() {

local repo_names="" key name
for key in GitHubAppRepoName GitHubLedgerAppRepoName \
GitHubInvestorAppRepoName GitHubHolonViewerRepoName; do
GitHubInvestorAppRepoName GitHubViewerRepoName; do
name=$(echo "$params" | jq -r --arg k "$key" \
'.[] | select(.ParameterKey == $k) | .ParameterValue')
if [ -n "$name" ] && [ "$name" != "null" ]; then
Expand Down
2 changes: 1 addition & 1 deletion cloudformation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Parameters are documented in each template — read the `Parameters:` block for
### Bootstrap

#### `bootstrap-oidc.yaml`
**Purpose**: Enables passwordless GitHub Actions → AWS authentication using OIDC federation, for this repo and the three frontend app repos plus the holon viewer.
**Purpose**: Enables passwordless GitHub Actions → AWS authentication using OIDC federation, for this repo and the three frontend app repos plus the xbrlkit viewer.

**Deploy**: Locally with `just bootstrap` before any CI/CD workflows can run.

Expand Down
22 changes: 11 additions & 11 deletions cloudformation/bootstrap-oidc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ Parameters:
AllowedPattern: ^[a-zA-Z0-9._-]+$
ConstraintDescription: Must be a valid GitHub repository name

GitHubHolonViewerRepoName:
GitHubViewerRepoName:
Type: String
Default: robosystems-holon-viewer
Description: Holon viewer static SPA repository name
Default: xbrlkit-viewer
Description: xbrlkit viewer static SPA repository name
AllowedPattern: ^[a-zA-Z0-9._-]+$
ConstraintDescription: Must be a valid GitHub repository name

Expand Down Expand Up @@ -608,18 +608,18 @@ Resources:
- !Sub repo:${GitHubOrg}/${GitHubInvestorAppRepoName}:ref:refs/heads/main
- !Sub repo:${GitHubOrg}/${GitHubInvestorAppRepoName}:ref:refs/heads/release/*
- !Sub repo:${GitHubOrg}/${GitHubInvestorAppRepoName}:ref:refs/tags/v*
# Holon viewer (static SPA → S3 + CloudFront)
- !Sub repo:${GitHubOrg}/${GitHubHolonViewerRepoName}:ref:refs/heads/main
- !Sub repo:${GitHubOrg}/${GitHubHolonViewerRepoName}:ref:refs/heads/release/*
- !Sub repo:${GitHubOrg}/${GitHubHolonViewerRepoName}:ref:refs/tags/v*
# xbrlkit viewer (static SPA → S3 + CloudFront)
- !Sub repo:${GitHubOrg}/${GitHubViewerRepoName}:ref:refs/heads/main
- !Sub repo:${GitHubOrg}/${GitHubViewerRepoName}:ref:refs/heads/release/*
- !Sub repo:${GitHubOrg}/${GitHubViewerRepoName}:ref:refs/tags/v*
Policies:
- PolicyName: !Sub ${ServiceTag}FrontendDeploymentPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
# CloudFormation - mutations are pinned to the frontend stacks
# (RoboSystemsApp*, RoboLedgerApp*, RoboInvestorApp*,
# RoboSystemsHolonViewer*, each with its S3 sibling). Reads stay
# XbrlkitViewer*, each with its S3 sibling). Reads stay
# account-wide: the deploy workflows describe their own stacks
# by name and the calls carry no resource ARN.
- Sid: CloudFormationWrite
Expand All @@ -633,7 +633,7 @@ Resources:
- !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/RoboSystemsApp*/*
- !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/RoboLedgerApp*/*
- !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/RoboInvestorApp*/*
- !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/RoboSystemsHolonViewer*/*
- !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/XbrlkitViewer*/*
- Sid: CloudFormationRead
Effect: Allow
Action:
Expand Down Expand Up @@ -681,8 +681,8 @@ Resources:
- arn:aws:s3:::roboledger-app-*/*
- arn:aws:s3:::roboinvestor-app-*
- arn:aws:s3:::roboinvestor-app-*/*
- arn:aws:s3:::robosystems-holon-viewer-*
- arn:aws:s3:::robosystems-holon-viewer-*/*
- arn:aws:s3:::xbrlkit-viewer-*
- arn:aws:s3:::xbrlkit-viewer-*/*
- Sid: S3Global
Effect: Allow
Action:
Expand Down
4 changes: 2 additions & 2 deletions robosystems/adapters/sec/pipeline/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,8 @@ class SECFilingCatalogConfig(Config):
default=16, description="Concurrent manifest reads from the public bucket"
)
viewer_url: str = Field(
default="https://holon.robosystems.ai",
description="The holon viewer the catalog's viewer links open",
default="https://xbrlkit.com",
description="The xbrlkit viewer the catalog's viewer links open",
)


Expand Down
20 changes: 16 additions & 4 deletions robosystems/config/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,10 +404,21 @@ class EnvConfig:
ROBOLEDGER_URL = get_str_env("ROBOLEDGER_URL", "https://roboledger.ai")
ROBOINVESTOR_URL = get_str_env("ROBOINVESTOR_URL", "https://roboinvestor.ai")
ROBOSYSTEMS_URL = get_str_env("ROBOSYSTEMS_URL", "https://robosystems.ai")
# Holon Viewer origin — a static SPA with no app backend of its own; it
# participates in the CORS allowlist only. No CloudFormation plumbing: the
# env-aware default matches the managed deployments, and forks can override
# or leave it (an RFS-owned origin in a fork's allowlist is inert).
# xbrlkit viewer origin — a static SPA with no app backend of its own; it
# participates in the CORS allowlist only (Graph mode calls this API from
# the browser). No CloudFormation plumbing: the env-aware default matches
# the managed deployment, and forks can override or leave it (an RFS-owned
# origin in a fork's allowlist is inert).
VIEWER_URL = get_str_env(
"VIEWER_URL",
"https://staging.xbrlkit.com"
if ENVIRONMENT == "staging"
else "https://xbrlkit.com",
)
# The viewer's original host, served as an alias of the same deployment.
# Every `xbrlkit view` published before 0.10 opens it and names it as the
# only origin allowed to read the report it serves, so the alias — and this
# allowlist entry — stay for as long as those installs might.
HOLON_URL = get_str_env(
"HOLON_URL",
"https://staging.holon.robosystems.ai"
Expand Down Expand Up @@ -1460,6 +1471,7 @@ def get_main_cors_origins(cls) -> list[str]:
cls.ROBOLEDGER_URL,
cls.ROBOINVESTOR_URL,
cls.ROBOSYSTEMS_URL,
cls.VIEWER_URL,
cls.HOLON_URL,
):
origin = _url_origin(url)
Expand Down
15 changes: 5 additions & 10 deletions tests/adapters/sec/pipeline/test_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,7 @@ def test_build_company_lists_representations_and_viewer_links(self, corpus):
"0000066740-25-000020": None,
}

doc = build_company(
filer, filings, manifests, viewer_url="https://holon.robosystems.ai/"
)
doc = build_company(filer, filings, manifests, viewer_url="https://xbrlkit.com/")

assert doc["ticker"] == "MMM"
assert doc["cik"] == MMM
Expand All @@ -267,18 +265,15 @@ def test_build_company_lists_representations_and_viewer_links(self, corpus):
assert tenq["folder"] is None
assert len(tenk["representations"]) == 3
assert tenk["viewer"] == {
"holon": viewer_link("https://holon.robosystems.ai", holon_url),
"tavi": viewer_link("https://holon.robosystems.ai", tavi_url),
"holon": viewer_link("https://xbrlkit.com", holon_url),
"tavi": viewer_link("https://xbrlkit.com", tavi_url),
}
# The latest openable filing per form: the 10-Q has nothing to open.
assert doc["latest"] == {"10-K": "0000066740-25-000006"}

def test_viewer_link_encodes_the_url_parameter(self):
link = viewer_link("https://holon.robosystems.ai", "https://cdn/a/b/holon.jsonld")
assert (
link
== "https://holon.robosystems.ai/?url=https%3A%2F%2Fcdn%2Fa%2Fb%2Fholon.jsonld"
)
link = viewer_link("https://xbrlkit.com", "https://cdn/a/b/holon.jsonld")
assert link == "https://xbrlkit.com/?url=https%3A%2F%2Fcdn%2Fa%2Fb%2Fholon.jsonld"

def test_build_index_sorts_by_ticker_with_the_newest_filing(self, corpus):
entities, reports, links = corpus
Expand Down
7 changes: 7 additions & 0 deletions tests/config/test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ def test_get_main_cors_origins_respects_environment(monkeypatch):
"https://roboledger.ai",
"https://roboinvestor.ai",
"https://robosystems.ai",
"https://xbrlkit.com",
"https://holon.robosystems.ai",
]

Expand All @@ -265,13 +266,17 @@ def test_get_main_cors_origins_respects_environment(monkeypatch):
monkeypatch.setattr(
EnvConfig, "ROBOSYSTEMS_URL", "https://staging.robosystems.ai", raising=False
)
monkeypatch.setattr(
EnvConfig, "VIEWER_URL", "https://staging.xbrlkit.com", raising=False
)
monkeypatch.setattr(
EnvConfig, "HOLON_URL", "https://staging.holon.robosystems.ai", raising=False
)
assert EnvConfig.get_main_cors_origins() == [
"https://staging.roboledger.ai",
"https://staging.roboinvestor.ai",
"https://staging.robosystems.ai",
"https://staging.xbrlkit.com",
"https://staging.holon.robosystems.ai",
]

Expand All @@ -293,11 +298,13 @@ def test_get_main_cors_origins_derives_fork_domain(monkeypatch):
monkeypatch.setattr(
EnvConfig, "ROBOSYSTEMS_URL", "https://tenant.robosystems.ai/", raising=False
)
monkeypatch.setattr(EnvConfig, "VIEWER_URL", "https://xbrlkit.com", raising=False)
monkeypatch.setattr(
EnvConfig, "HOLON_URL", "https://holon.robosystems.ai", raising=False
)
assert EnvConfig.get_main_cors_origins() == [
"https://tenant.robosystems.ai",
"https://xbrlkit.com",
"https://holon.robosystems.ai",
]

Expand Down