Current state and the next steps, so work continues on either machine. Update State and Next and append to Log at the end of every session (through a PR, like any change in this repo). Also comment on ITPROJ-1421, in first person, when a step lands.
PR #193 (the move) is merged; the connector is not on main until its PR merges. Work
from the connector branch, or from feat/cos-lab-updates, stacked on it, which holds
everything:
cd ~/sysengscripts # or C:\Users\adams\Code\sysengscripts
git fetch origin
git switch feat/cos-lab-connector # the first time, this creates it from origin
git pull
cd integrations/scsp_ai_initiative/cos_lab
python3.13 -m venv .venv # Windows: py -3.13 -m venv .venv (CI uses 3.13)
# Windows: .venv\Scripts\python · macOS: .venv/bin/python
<venv python> -m pip install -r requirements.txt
<venv python> -m pytest -q -p no:cacheprovider # 164 pass
gcloud auth login && gcloud auth application-default login
Once the connector and the updates are merged, use git switch main && git pull instead.
Start Claude Code from this folder (claude): it loads this folder’s CLAUDE.md and
sysengscripts’ root CLAUDE.md. SCSP context is one level up (../HANDOFF.md). Sessions
and Claude’s memory don’t move between machines, so end each one by updating this file
and start the next with “read HANDOFF.md”.
| What | Where | State |
|---|---|---|
| The move into SysEngineering | PR #193 | Merged 2026-10-16 (8da5cad). Its reviewer’s four points are fixed on the connector branch. |
| The connector (Session B) | branch feat/cos-lab-connector, rebased onto main |
In its own PR, so the reviewer sees only the connector. Adam reviews and merges. |
| The update loop (Vesa’s design) | branch feat/cos-lab-updates, stacked on the connector |
Its PR opens once the connector merges. |
The connector branch’s commits, in order: 099fc22 (the first draft, exactly as the
first review saw it), then 9fbbea7, 9d634ee and 81f5a72 (the fixes from review rounds
one, two and three), then docs. The commit messages say what each round changed and why.
The order from here:
main, dropping the connector’s commits, push
it with --force-with-lease, and open its PR (the updates branch’s HANDOFF has the
exact command).COS_ALLOWED_EMAILS defaults to the owner alone.client_secret_post only. client_secret_basic is not
offered: the SDK needs client_id in the form even then.python -m lab.connector.signer (with a checksum).adam-cos-lab (schmidtentity.com org, number 680745570527,
same billing as Adam’s other projects), Jira ITPROJ-1421 In Progress under Planning
(ITPROJ-1403).Hillspire/adam-cos-lab repo, to sit with the rest of the SCSP work under the repo’s
PR rules, Claude reviewer and CI. That repo is deleted once this move is merged.adam-cos-lab: cos-google-oauth-client
(Session A) and cos-mcp-signing-key (2026-10-15). The live run needs no other setup.| Session | What | State |
|---|---|---|
| A | Google sign-in setup (console) | Done 2026-10-15 |
| B | The connector on Adam’s machine (localhost:8080): Gwen’s OAuth + Google sign-in, a whoami tool, record of use; Claude Code connects to it |
Code done; live run next |
| C | Deploy to Cloud Run: runtime service account, secrets, a warm instance | |
| D | GitHub tools on the deployed connector (needs the token below) |
adam-cos-lab: app Adam CoS Lab, Audience External
(hillspire.com is outside the schmidtentity.com org), publishing status Testing.
Don’t click “Make internal”: it would lock out hillspire.com.aschembri@hillspire.com (main identity), aschembri@schmidtentity.com
(the second identity for the permissions demo).openid, userinfo.email, userinfo.profile;
sensitive calendar.readonly; restricted gmail.readonly. Testing mode allows all three
for test users. Production would need verification, plus a security assessment for Gmail.
That is why SCSP’s connector belongs in SCSP’s own org as an Internal app.cos-connector, redirect URIs
https://cos-connector-680745570527.us-central1.run.app/oauth/google/callback and
http://localhost:8080/oauth/google/callback. Stored as Secret Manager
cos-google-oauth-client v1 (Google’s {"web": {...}} JSON). Backup copy in Adam’s
1Password (“Cos Lab Secret”). Google shows a client secret only once, at creation.cos-mcp-signing-key, version 1, in
adam-cos-lab, made with the generator and checked to decode; created as
aschembri@schmidtentity.com, the gcloud account to use for this project. It was made
with (from this folder):
<venv python> -m lab.connector.signer | gcloud secrets create cos-mcp-signing-key --project adam-cos-lab --replication-policy=automatic --data-file=-
Nothing is printed or saved on disk. To rotate it later, pipe it the same way into
gcloud secrets versions add cos-mcp-signing-key --project adam-cos-lab --data-file=-;
that signs everyone out. The connector reads it with your application-default login,
so run gcloud auth application-default login (as that account) on each machine first.
<venv python> -m lab.connector. It reads both
secrets from Secret Manager with your gcloud login and listens on this machine only.
With op run --env-file=secrets.env.tpl -- in front, the Jira and Google tools are
offered too.claude mcp add --transport http adam-cos-local http://localhost:8080/mcp,
then /mcp in a Claude Code session to sign in. Expect our consent page, then Google
(the “unverified app” warning, Testing mode), then back to Claude Code.whoami. The terminal running the connector shows the record of use:
client_registered, sign_in_started, consent_given, sign_in_ok,
session_started, tool_call.Settings (all optional): COS_ALLOWED_EMAILS (default: the owner alone; add
aschembri@schmidtentity.com to try the second identity, which gets whoami only),
LAB_OWNER_EMAIL, COS_PUBLIC_URL. Restarting the connector signs everyone out (by
design), so Claude Code asks to sign in again.
Not yet proven against the real thing, so watch for it on the first run: Google accepting
PKCE alongside the Web client’s secret, and the hd claim for hillspire.com accounts. A
refusal shows up as sign_in_failed in the connector’s log with a reason and the facts to
tell causes apart: google_refused_code with Google’s error code (invalid_grant,
redirect_uri_mismatch, invalid_client…), id_token_invalid with which check failed
(issued_in_the_future means this machine’s clock is behind), not_a_workspace_account
with the hd Google sent and the email’s domain. The consent page is proven in real
Chrome and Edge, over http and https, but not yet in Safari or Firefox.
MemoryStore keeps codes and sessions in memory, so
Cloud Run needs --max-instances 1, and every deploy or instance restart signs everyone
out. Firestore (already enabled) would implement store.py’s interface and remove both
limits; it would also hold step 3’s per-person Google tokens.python -m lab.connector,
.gcloudignore/.dockerignore, the runtime service account cos-connector@, secrets
bound as environment variables, --min-instances 1 (claude.ai gives OAuth 10 s). Build
from a full lock with hashes: requirements.txt pins the direct and security-relevant
packages, not every transitive one.iam.allowedPolicyMemberDomains on this project is allValues: ALLOW, and Gwen already
runs with allUsers as invoker in the same organization.jsonPayload.audit="cos_v1" to its own log bucket
or BigQuery with a set retention, readable by named people only. The default bucket
keeps 30 days and every log viewer can read it.cos-github-token (below), and
the owner’s Google consent (LAB_GOOGLE_TOKEN_JSON) expires every 7 days in Testing
mode, so step 3 is where Calendar and Gmail move to each person’s own sign-in.Claude’s hosted apps share one callback, https://claude.ai/api/mcp/auth_callback, and the
connector refuses it today (redirect_uri_allowed in oauth.py). Someone could start a
sign-in in their own claude.ai account and forward the link; our consent page would look
exactly like the person’s own, and whether claude.ai then finishes the flow in the
forwarder’s account is unproven. Before allowing it:
The calendar’s time zone is cached per process (lab/tools/google.py); step 3, which reads each person’s own calendar, must key it per person.
Hillspire/adam-cos-lab GitHub repo (unblocked: #193 is merged). Everything in
it is in this folder. Use github.com → the repo → Settings → Danger Zone → Delete, or run
gh auth refresh -h github.com -s delete_repo so Claude can run
gh repo delete Hillspire/adam-cos-lab.~/adam-cos-lab, the old clone.C:\Users\adams\Code\adam-cos-lab on the
Windows PC. Its only extra was the uncommitted connector draft, now in this folder.The separate Anthropic key and service account planned for the old repo’s reviewer are not needed: this repo’s own Claude reviewer covers this folder.
Locally the lab borrows gh auth token, but Cloud Run has no gh login. Create a
fine-grained personal access token at github.com → Settings → Developer settings:
Hillspire may need to approve it. Store it:
gcloud secrets create cos-github-token --project adam-cos-lab --replication-policy=automatic --data-file=-
(paste the token, press Enter, then Ctrl-Z Enter on Windows or Ctrl-D on macOS).
whoami answers.cos-connector, us-central1, at
https://cos-connector-680745570527.us-central1.run.app (MCP at /mcp, Google callback
at /oauth/google/callback), after the decisions listed above. Then in Claude Code:
claude mcp add --transport http adam-cos https://cos-connector-680745570527.us-central1.run.app/mcp.
No consumer Claude plans with this data.cos-github-token).How the connector fits together: lab/connector/__init__.py maps its modules, and
oauth.py’s docstring walks the sign-in journey step by step. The rules it must keep are in
this folder’s CLAUDE.md, each pinned by a test.
Hillspire/adam-cos-lab; GCP project
created and APIs enabled; ITPROJ-1421 opened. MacBook set up with Claude Code CLI
(~/.local/bin added to PATH in ~/.zshrc).cos-connector stored as cos-google-oauth-client. ITPROJ-1421 commented.integrations/scsp_ai_initiative/cos_lab
so it sits under the repo’s PR rules, Claude reviewer and CI; the separate repo is
deleted once the move is merged. On the MacBook, ~/adam-cos-lab can go: git pull in
~/sysengscripts is all it needs.hd and PKCE, iss on redirects, rotating refresh tokens in 7-day sessions,
working revocation, owner-only data tools, schema-checked arguments, one JSON record of
use. Dependencies pinned. 120 tests.Referrer-Policy: no-referrer made browsers post Origin: null, so every
sign-in was refused. Also: loopback-only redirects (claude.ai waits for step 7), no query
on them (a duplicate-iss trick), one-time Google state, cookies per sign-in, iss on
the SDK’s own refusals, a checksummed signing key, a real-MCP-client test. 147 tests.feat/cos-lab-connector pushed (HEAD 48d8e97). Checked: the org
allows allUsers on this project. HANDOFFs, READMEs and the root CLAUDE.md brought up to
date for the switch to the work laptop, and every finding of all three rounds written
down in REVIEWS.md. Signing key cos-mcp-signing-key created with the
generator (version 1) and checked to decode, so the live run is ready to go.