Skip to content

[Enhancement] - Automate GCS bucket, datastore, and GCP app creation - #349

Merged
leekahung merged 24 commits into
mainfrom
issue-347/automate-gcs-bucket-creation-uploads
Jun 2, 2026
Merged

leekahung merged 24 commits into
mainfrom
issue-347/automate-gcs-bucket-creation-uploads

Conversation

@leekahung

Copy link
Copy Markdown
Contributor

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update
  • Infrastructure
  • Maintenance

Description

This PR is a follow-up to #345, where bucket, datastore, and app creation can be made via automation scripts from the backend. The current flow is for file-based storage and deployment. Only users with GCP access can run this automated flow in CLI.

The creation process has been tested, see bucket for tenantfirstaid-05122026-test in GCS and the datastore for tenantfirstaid-test-fresh in AI Applications on GCP, and the app for tenantfirstaid-test-fresh in AI Applications on GCP.

Related Tickets & Documents

QA Instructions, Screenshots, Recordings

Please replace this line with instructions on how to test your changes, a note on the devices and browsers this has been tested on, as well as any relevant images for UI changes.

To test, you can run the following from the backend directory:

  1. make generate-metadata GCS_BUCKET_NAME=<name-of-your-new-bucket>
  2. make upload-to-gcs GCS_BUCKET_NAME=<name-of-your-new-bucket>
  3. make create-datastore-gcs GCS_BUCKET_NAME=<name-of-your-new-bucket> DATASTORE_NAME=<name-of-your-new-datastore>
  4. make create-app-gcs DATASTORE_ID=<name-of-your-new-datastore> APP_NAME=<name-of-your-new-app>

What each step does:

  1. Creates the new metadata.jsonl reference (PR Include annotations for existing statutes and ASCII enforcement #345)
  2. Creates a new bucket in GCS and uploads metadata.jsonl and the documents to the new bucket (this PR)
  3. Creates a new datastore to and connects your bucket to the datastore (this PR)
  4. Creates a new app on GCP for the datastore to connect to (this PR)

Added/updated tests?

  • Yes
  • No, and this is why: please replace this line with details on why tests have not been included
  • I need help with writing tests

Documentation

  • If this PR changes the system architecture, Architecture.md has been updated

[optional] Are there any post deployment tasks we need to perform?

leekahung added 3 commits May 11, 2026 20:21
…o its own module; Include new unit tests for functions
…tion; Include new unit tests to cover creation scripts; Update doucmentation new automated flow for bucket to connected app on GCP
@leekahung
leekahung requested a review from yangm2 May 13, 2026 02:18
@leekahung leekahung added backend Bot implementation and other backend concerns infrastructure Pull requests related to infrastructure and underlying workflows labels May 13, 2026
@leekahung

Copy link
Copy Markdown
Contributor Author

@claude pr-review

@leekahung
leekahung marked this pull request as ready for review May 13, 2026 02:19
@claude

This comment was marked as outdated.

@leekahung

Copy link
Copy Markdown
Contributor Author

@claude pr-reivew

@claude

This comment was marked as outdated.

@leekahung
leekahung marked this pull request as draft May 13, 2026 02:49
@leekahung
leekahung force-pushed the issue-347/automate-gcs-bucket-creation-uploads branch from e2ced7b to e64e9a8 Compare May 17, 2026 23:18
@leekahung
leekahung marked this pull request as ready for review May 17, 2026 23:19
@leekahung

Copy link
Copy Markdown
Contributor Author

@claude pr-review

@claude

This comment was marked as outdated.

@leekahung

Copy link
Copy Markdown
Contributor Author

@claude pr-review

@claude

This comment was marked as outdated.

@yangm2 yangm2 left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks this is so much more reproducible and convenient than clicking through the cloud console interface!

Claude says these are experiments that we want to enable in order to improve RAG retrieval for the ORS/Portland/Eugene docs.

Impact Experiment Script Arg change
Highest Section-boundary chunking generate_metadata_jsonl.py --granularity [file|section] — switches from one entry per .txt file to one entry per 90.XXX section; no Vertex AI API change needed
High Cross-reference expansion generate_metadata_jsonl.py --expand-refs — resolves inline ORS X.XXX citations and appends target section text to each entry's struct_data at generation time
Medium Chunk size / overlap create_datastore_gcs.py --chunk-size INT and --chunk-overlap INT — passed into DocumentProcessingConfig; pairs with --granularity file since section-level entries are already small
Medium Annotations datastore separation generate_metadata_jsonl.py + create_datastore_gcs.py --exclude-annotations flag on metadata generation, run twice to produce two metadata.jsonl files; --datastore-id used twice to create two datastores; --datastore-id on create_app_gcs.py made repeatable (action="append") to attach both to one app

(@lee-coates this is what you've been advocating for, right?)

Additionally, my guess is that we'll end up with multiple RAG tools with unique settings for each, so reproducible deployment of the RAG should be driven by a revision-controlled configuration (e.g. TOML) file.

Speaking of, please update Deployment.md



class TestUploadFiles:
def test_uploads_each_file_and_metadata(self, tmp_path: Path):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This test is noisy ...

tests/test_upload_to_gcs.py::TestUploadFiles::test_uploads_each_file_and_metadata   uploaded a.txt (/private/var/folders/57/9t9kbd551dgd9dp_mz_kjxsc0000gn/T/-redacted-/pytest-0/test_uploads_each_file_and_met0/a.txt)
  uploaded b.txt (/private/var/folders/57/9t9kbd551dgd9dp_mz_kjxsc0000gn/T/-redacted-/pytest-0/test_uploads_each_file_and_met0/b.txt)
  uploaded metadata.jsonl (/private/var/folders/57/9t9kbd551dgd9dp_mz_kjxsc0000gn/T/-redacted-/pytest-0/test_uploads_each_file_and_met0/metadata.jsonl)
PASSED



class TestMain:
def test_dry_run_does_not_call_storage(self, tmp_path: Path):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this test is noisy


client_cls.assert_not_called()

def test_happy_path_creates_bucket_and_uploads(self, tmp_path: Path):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this test is noisy

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There's a M:1 relationship between buckets and datastore that is not exposed this current flow. Also a M:1 relationship between datastore(s) and crawled websites to the app.

This flow should probably also utilize the scripts/vertex_ai_*.py scripts as part of the debug/diagnosis/experimentation.

Comment thread backend/Makefile
Comment on lines +67 to +70
ifeq ($(strip $(GCS_BUCKET_NAME)),)
$(error GCS_BUCKET_NAME is required: make upload-to-gcs GCS_BUCKET_NAME=my-bucket)
endif
$(PYTHON) run python -m scripts.upload_to_gcs --bucket $(GCS_BUCKET_NAME) $(if $(LOCATION),--location $(LOCATION)) $(UPLOAD_OPTIONS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

would need a foreach to support multiple buckets

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Think we can do multi-bucket support in a follow-up

Comment on lines +180 to +184
parser.add_argument(
"--bucket",
required=True,
help="GCS bucket holding metadata.jsonl and the document objects.",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could have 1 or more buckets in the datastore

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

we can certainly think about multi-bucket support in a follow up PR, we could get the pipeline for single bucket set up here first

Comment thread backend/scripts/create_datastore_gcs.py Outdated
load_gcp_credentials,
)

DEFAULT_LOCATION = "us"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

DRY ... should this just go in constants.py or get read from a corpus-cfg.TOML

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

moving it to constants.py

Comment thread backend/scripts/create_datastore_gcs.py Outdated
Comment thread backend/scripts/create_app_gcs.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For the case of multiple jurisdictions in a single datastore, I think those should probably be in separate buckets and have separate JSONL files. I think that would simplify the filtering in this script but also streamline the ingestion of different doc structures. This would be more flexible for adding additional jurisdictions in the future.

@leekahung

Copy link
Copy Markdown
Contributor Author

Thanks @yangm2 for the feedback! I'll have a go at the feedback and revise it

leekahung added 7 commits May 27, 2026 22:19
Replace stale create_vector_store.py references with the
upload-to-gcs/create-datastore-gcs flow and document the
vertex_ai_search debugging scripts.
Drop the generate-metadata entry point and call
scripts.generate_metadata_jsonl directly from the Makefile.
@yangm2

yangm2 commented May 31, 2026

Copy link
Copy Markdown
Contributor

@claude PR review and consider whether this addresses yangm2's review comments

@claude

claude Bot commented May 31, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @yangm2's task in 3m 19s —— View job


PR review

TL;DR: This PR is in good shape and addresses most of @yangm2's review comments that were explicitly committed to. The bigger architectural asks (corpus-cfg.TOML, RAG experiment flags, multi-bucket support, web-scraper tool) were intentionally deferred to follow-up PRs per leekahung's replies. A few small code-quality nits noted below.


How yangm2's review comments map to this branch

Comment Status Where
Noisy upload-to-gcs tests (lines 176/211/241) ✅ Addressed _silence_stdout() helper added in backend/tests/test_upload_to_gcs.py:21-24 and applied at the three call sites (upload_files, test_dry_run_…, test_happy_path_…)
Remove generate-metadata from [project.scripts] ✅ Addressed Removed in backend/pyproject.toml; Makefile invokes via python -m scripts.generate_metadata_jsonl
DRY: move location default to constants.py or TOML ✅ Partially addressed DEFAULT_VERTEX_AI_SEARCH_LOCATION = "us" added to backend/tenantfirstaid/constants.py:168; both scripts import it. TOML config deferred
"…BUT DO NOT DELETE/REPLACE A PRODUCTION DATASTORE!!!" ✅ Addressed backend/scripts/create_datastore_gcs.py:105
"…BUT DO NOT DELETE/REPLACE A PRODUCTION APP!!!" ✅ Addressed backend/scripts/create_app_gcs.py:59
Update Deployment.md ✅ Addressed Lifecycle diagram + "Empty retrieval results" runbook now reference make upload-to-gcs / make create-datastore-gcs instead of the removed create_vector_store.py
Skill should reference vertex_ai_*.py for debug/diagnosis ✅ Addressed New "Debugging and diagnosis" section in .claude/skills/corpus-dataset.md covers both helpers
Multi-bucket support (Makefile, create_datastore_gcs, generate_metadata_jsonl per-jurisdiction) ⏭️ Deferred leekahung committed to a follow-up PR — reasonable given this PR's already-large scope
Web-scraper argument on create_app_gcs ⏭️ Deferred leekahung committed to a follow-up PR
RAG experiment flags (--granularity, --expand-refs, --chunk-size, --chunk-overlap, --exclude-annotations) ⏭️ Not yet No flags wired up; would fit best alongside the corpus-cfg.TOML work since several are co-dependent
TOML-driven RAG deployment config ⏭️ Not yet Not addressed in this PR

Net assessment of feedback: every concrete fix yangm2 asked for in this PR's scope is in. The deferred items are real and should land in follow-ups, but bundling them here would have ballooned the PR further.


Code-level review

The scripts are clean, well-documented, and well-tested. A few minor observations:

  1. No rollback for upload_to_gcs on partial failure — backend/scripts/upload_to_gcs.py:124-139. create_datastore_gcs has a rollback path on import failure, but upload_to_gcs does not. If an upload fails midway, the bucket is left half-populated and the script will refuse to re-run (because the bucket now exists). The user has to manually delete. Probably fine for a one-time provisioning script, but worth noting in the docstring or surfacing in the error message. Fix this →

  2. validate_resource_name permits consecutive hyphens — backend/scripts/shared.py:6. The regex ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ allows "a--b". GCP's actual rules for Discovery Engine IDs reject -- in some contexts. Low-impact since GCP will reject server-side, but a stricter regex would fail faster.

  3. create_app_gcs doesn't verify the referenced datastore exists — backend/scripts/create_app_gcs.py:54-60. A typo in --datastore-id will surface only when the engine tries to use the missing datastore, which may produce a confusing error. A pre-flight get_data_store call would catch this with a clearer message; not blocking.

  4. Document.from_json in _iter_documents — backend/scripts/upload_to_gcs.py:43. Good choice — using the protobuf parser keeps schema validation consistent with how Discovery Engine itself will read the file. The "Unknown fields fail loud" test at test_upload_to_gcs.py:127 confirms this. Nice.

  5. Bucket region/datastore location compatibility check — backend/scripts/create_datastore_gcs.py:47-77. Solid pre-flight check with good test coverage in TestCheckBucketLocationCompat. The parametrized cases cover the realistic matrix.

  6. ROLLBACK_TIMEOUT_SECONDS = 120 — backend/scripts/create_datastore_gcs.py:28. Good that this is a named constant with a comment explaining the contract; the test test_delete_calls_delete_and_waits locks in the assertion that the timeout is actually passed.

  7. Doc URL templates — both scripts construct console.cloud.google.com/gen-app-builder/... URLs by hand. Fine since the URL shape is stable, but if Google ever moves the UI you'll need to chase it. Acceptable trade-off.

  8. Working tree has uncommitted reverts of .claude/skills/corpus-dataset.md — when I started, git status showed local edits that revert this PR's skill updates. Probably stale state from another session — worth a git checkout .claude/skills/corpus-dataset.md before pushing anything else so the PR's documentation stays intact.


Suggested follow-up issues to file

To keep the deferred items from being forgotten, consider filing tracker issues for:

  • Multi-bucket / multi-jurisdiction datastore support (covers the Makefile foreach, create_datastore_gcs multi-bucket, and per-jurisdiction generate_metadata_jsonl)
  • corpus-cfg.TOML revision-controlled RAG configuration (this would also be a natural home for chunk-size/overlap/granularity flags)
  • Web-scraper tool for create_app_gcs to ingest the Oregon Law Library
  • Pre-flight datastore existence check in create_app_gcs

  • Read PR diff and changed files
  • Check each of yangm2's review comments against current state
  • Review code for correctness/bugs
  • Post review summary
    • Branch: issue-347/automate-gcs-bucket-creation-uploads

@yangm2 yangm2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@leekahung

Copy link
Copy Markdown
Contributor Author

@claude can you start new issues related to the deferred topics?

@claude

claude Bot commented Jun 1, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @leekahung's task in 1m 42s —— View job


Filing follow-up issues for deferred topics

I tried to file these directly with gh issue create, but issue-creation commands aren't in my allowed-tools list for this workflow, so they're getting blocked at the permission layer. You can find more information and potential workarounds in the FAQ.

Below are three ready-to-file drafts covering the deferred items from this PR. The fourth item from my earlier review (pre-flight datastore existence check in create_app_gcs) was already addressed in 5bc442a, so no issue is needed for it.

  • Confirm which deferred items still need tracker issues
  • Draft issue: Multi-bucket / multi-jurisdiction datastore support
  • Draft issue: corpus-cfg.TOML revision-controlled RAG configuration
  • Draft issue: Web-scraper tool for create_app_gcs (Oregon Law Library)
  • (Blocked) File the issues — needs gh issue create in allowed tools, or you can copy-paste the drafts below

Draft 1 — Multi-bucket / multi-jurisdiction datastore support

Title: Multi-bucket / multi-jurisdiction datastore support across GCS ingestion pipeline

Body:

Follow-up from #349 (deferred per yangm2's review comments and leekahung's reply).

The current GCS provisioning flow assumes a single bucket → single datastore → single app. To support adding jurisdictions (ORS / Portland / Eugene / future) without coupling them into one bucket, the pipeline should support an M:1 bucket-to-datastore relationship.

Scope

  • backend/Makefile: wrap upload-to-gcs / create-datastore-gcs targets in a foreach over a jurisdiction list (referenced at backend/Makefile:70 in [Enhancement] - Automate GCS bucket, datastore, and GCP app creation #349).
  • backend/scripts/create_datastore_gcs.py: accept multiple --bucket arguments (or repeated --bucket) and attach all of them to the new datastore. The check_bucket_location_compat pre-flight check should run per bucket.
  • backend/scripts/generate_metadata_jsonl.py: emit per-jurisdiction metadata.jsonl files into per-jurisdiction directories instead of a single combined file. This also simplifies filtering and lets each jurisdiction's doc structure evolve independently.
  • Document the jurisdiction list source of truth (likely the corpus-cfg.TOML proposed in the sibling issue).

Acceptance

  • make upload-to-gcs / make create-datastore-gcs can provision N buckets attached to a single datastore from one invocation.
  • Per-jurisdiction metadata.jsonl files are kept separate end-to-end.
  • Existing single-bucket flow still works (sensible default when only one jurisdiction is configured).

Refs: #347, #349


Draft 2 — corpus-cfg.TOML revision-controlled RAG configuration

Title: Revision-controlled corpus-cfg.TOML for RAG deployment + chunking/granularity flags

Body:

Follow-up from #349. yangm2 raised that as we accumulate multiple RAG tools with unique settings, reproducible deployment should be driven by a checked-in config file rather than ad-hoc CLI args.

Proposed config (backend/corpus-cfg.TOML or similar): revision-controlled settings for each RAG tool — bucket list, datastore ID, app ID, chunking strategy, granularity, annotation handling, location.

RAG experimentation flags to wire up (from yangm2's review table in #349):

Impact Experiment Script Arg
Highest Section-boundary chunking generate_metadata_jsonl.py --granularity [file|section] — switches from one entry per .txt file to one entry per 90.XXX section
High Cross-reference expansion generate_metadata_jsonl.py --expand-refs — resolves inline ORS X.XXX citations, appends target section text to each entry's struct_data
Medium Chunk size / overlap create_datastore_gcs.py --chunk-size INT and --chunk-overlap INT passed into DocumentProcessingConfig (pairs with --granularity file)
Medium Annotations datastore separation generate_metadata_jsonl.py + create_datastore_gcs.py --exclude-annotations on metadata generation; run twice to produce two metadata.jsonl files; --datastore-id on create_app_gcs.py made repeatable (action="append") so both datastores attach to one app

Most of these naturally live in the TOML once it exists, so they should land together (or the TOML should land first and these flags become overrides).

Also covers yangm2's comment on DEFAULT_VERTEX_AI_SEARCH_LOCATION in constants.py — long term that constant should be sourced from the TOML rather than a Python constant.

Acceptance

  • corpus-cfg.TOML exists in the repo with documented schema.
  • generate_metadata_jsonl.py, create_datastore_gcs.py, create_app_gcs.py consume the TOML by default; CLI flags above act as overrides.
  • --datastore-id on create_app_gcs.py is repeatable.

Refs: #349


Draft 3 — Web-scraper tool for create_app_gcs (Oregon Law Library)

Title: Add web-scraper RAG tool to create_app_gcs for Oregon Law Library ingestion

Body:

Follow-up from #349. yangm2 noted that create_app_gcs currently attaches 1 datastore to an app, but apps can have 0+ datastores when web-site scrapers are configured. The Oregon Law Library is the motivating source.

Scope

  • Add a --scraper-url (repeatable) argument to backend/scripts/create_app_gcs.py that registers a web-scraper data source on the app in addition to (or instead of) the GCS-backed datastore.
  • Wire the scraper config through DocumentProcessingConfig / engine creation so the app indexes the crawled site alongside the corpus.
  • Document the URL patterns used for the Oregon Law Library and any rate-limit / robots.txt considerations.
  • Coordinate with Draft 2 — scraper targets are a natural fit for the TOML config.

Acceptance

  • make create-app-gcs SCRAPER_URL=... (or equivalent) creates an app with both the GCS datastore and a web-scraper data source.
  • At least one scraper target (Oregon Law Library) is configured and verified to return results in the RAG tool.

Refs: #349


• Branch: issue-347/automate-gcs-bucket-creation-uploads

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Bot implementation and other backend concerns infrastructure Pull requests related to infrastructure and underlying workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Automate GCS bucket creation and split metadata generator from ASCII encoder

2 participants