fix: scope GCP Leo database runtime

This commit is contained in:
fwazb 2026-07-14 12:57:31 -07:00
parent 794635d17f
commit ce90a79aac
12 changed files with 1867 additions and 138 deletions

View file

@ -0,0 +1,361 @@
#!/usr/bin/env bash
# Deploy the source-controlled Cloud SQL bridge and non-secret service settings
# to the GCP parallel leoclean runtime. This does not create database users,
# read secret values, send Telegram messages, or promote Cloud SQL.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
PROJECT="${PROJECT:-teleo-501523}"
ZONE="${ZONE:-europe-west6-a}"
INSTANCE="${INSTANCE:-teleo-prod-1}"
SERVICE="${SERVICE:-leoclean-gcp-prod-parallel.service}"
MERGED_REF="${MERGED_REF:-origin/main}"
REMOTE_PROFILE_BIN="${REMOTE_PROFILE_BIN:-/home/teleo/.hermes/profiles/leoclean/bin}"
REMOTE_DROPIN_DIR="${REMOTE_DROPIN_DIR:-/etc/systemd/system/$SERVICE.d}"
REMOTE_CLOUDSDK_CONFIG="${REMOTE_CLOUDSDK_CONFIG:-/run/leoclean-gcp-prod-parallel}"
EXPECTED_VM_SERVICE_ACCOUNT="${EXPECTED_VM_SERVICE_ACCOUNT:-sa-teleo-prod-vm@teleo-501523.iam.gserviceaccount.com}"
WRAPPER_REL="hermes-agent/leoclean-bin/teleo-kb"
TOOL_REL="hermes-agent/leoclean-bin/cloudsql_memory_tool.py"
DROPIN_REL="systemd/leoclean-gcp-prod-parallel-cloudsql.conf"
DROPIN_NAME="10-cloudsql-memory.conf"
DRY_RUN=false
RESTART=false
VERIFY_ONLY=false
usage() {
cat <<'USAGE'
Usage: deploy/sync-gcp-leoclean-runtime.sh [--dry-run] [--restart] [--verify-only]
The database role and scoped Secret Manager secret must exist before --restart.
This script deploys only the bridge files and non-secret systemd drop-in.
USAGE
}
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
--restart) RESTART=true ;;
--verify-only) VERIFY_ONLY=true ;;
--help|-h)
usage
exit 0
;;
*)
echo "Unknown arg: $arg" >&2
usage >&2
exit 64
;;
esac
done
for path in "$WRAPPER_REL" "$TOOL_REL" "$DROPIN_REL"; do
if [ ! -f "$REPO_ROOT/$path" ]; then
echo "Required source file is missing: $path" >&2
exit 66
fi
done
if ! grep -Fxq 'UnsetEnvironment=PGPASSWORD' "$REPO_ROOT/$DROPIN_REL"; then
echo "Runtime drop-in must explicitly unset inherited PGPASSWORD." >&2
exit 65
fi
if ! grep -Fxq "Environment=CLOUDSDK_CONFIG=$REMOTE_CLOUDSDK_CONFIG" "$REPO_ROOT/$DROPIN_REL"; then
echo "Runtime drop-in CLOUDSDK_CONFIG does not match $REMOTE_CLOUDSDK_CONFIG." >&2
exit 65
fi
SOURCE_REVISION="$(git -C "$REPO_ROOT" rev-parse HEAD)"
if ! git -C "$REPO_ROOT" diff --quiet HEAD -- "$WRAPPER_REL" "$TOOL_REL" "$DROPIN_REL"; then
echo "Refusing to deploy runtime files that do not match HEAD." >&2
exit 65
fi
if [ -n "$(git -C "$REPO_ROOT" ls-files --others --exclude-standard -- "$WRAPPER_REL" "$TOOL_REL" "$DROPIN_REL")" ]; then
echo "Refusing to deploy untracked runtime files." >&2
exit 65
fi
if ! git -C "$REPO_ROOT" rev-parse --verify "$MERGED_REF^{commit}" >/dev/null 2>&1; then
echo "Merged reference is unavailable: $MERGED_REF" >&2
exit 69
fi
if ! git -C "$REPO_ROOT" merge-base --is-ancestor "$SOURCE_REVISION" "$MERGED_REF"; then
echo "Refusing to deploy unmerged revision $SOURCE_REVISION (not contained in $MERGED_REF)." >&2
exit 65
fi
WRAPPER_SHA="$(shasum -a 256 "$REPO_ROOT/$WRAPPER_REL" | awk '{print $1}')"
TOOL_SHA="$(shasum -a 256 "$REPO_ROOT/$TOOL_REL" | awk '{print $1}')"
DROPIN_SHA="$(shasum -a 256 "$REPO_ROOT/$DROPIN_REL" | awk '{print $1}')"
GCP_SSH=(
gcloud compute ssh "$INSTANCE"
--project="$PROJECT"
--zone="$ZONE"
--tunnel-through-iap
)
remote_helpers=$(cat <<'REMOTE'
assert_service_running() {
local active_state sub_state main_pid unit_user process_user
active_state="$(systemctl show "$SERVICE" --property=ActiveState --value)"
sub_state="$(systemctl show "$SERVICE" --property=SubState --value)"
if [ "$active_state" != "active" ] || [ "$sub_state" != "running" ]; then
echo "Service is not active/running: ActiveState=$active_state SubState=$sub_state" >&2
return 1
fi
main_pid="$(systemctl show "$SERVICE" --property=MainPID --value)"
unit_user="$(systemctl show "$SERVICE" --property=User --value)"
case "$main_pid" in
''|0|*[!0-9]*)
echo "Service has no live MainPID: $main_pid" >&2
return 1
;;
esac
if [ "$unit_user" != "teleo" ]; then
echo "Service unit user is not teleo: $unit_user" >&2
return 1
fi
process_user="$(ps -o user= -p "$main_pid" | awk '{print $1}')"
if [ "$process_user" != "teleo" ]; then
echo "Service process user is not teleo: $process_user" >&2
return 1
fi
}
assert_runtime_environment() {
local environment runtime_dir_state
environment="$(systemctl show "$SERVICE" --property=Environment --value)"
case " $environment " in
*" PGPASSWORD="*)
echo "Service environment still contains PGPASSWORD." >&2
return 1
;;
esac
case " $environment " in
*" CLOUDSDK_CONFIG=$REMOTE_CLOUDSDK_CONFIG "*) ;;
*)
echo "Service does not use the isolated CLOUDSDK_CONFIG." >&2
return 1
;;
esac
runtime_dir_state="$(stat -c '%U:%G:%a' "$REMOTE_CLOUDSDK_CONFIG")"
if [ "$runtime_dir_state" != "teleo:teleo:700" ]; then
echo "Isolated CLOUDSDK_CONFIG has unexpected ownership or mode: $runtime_dir_state" >&2
return 1
fi
}
assert_attached_service_account() {
local sdk_config="$1" metadata_email
sudo install -d -o teleo -g teleo -m 0700 "$sdk_config"
metadata_email="$(
sudo -n -u teleo env -u PGPASSWORD \
HOME=/home/teleo \
CLOUDSDK_CONFIG="$sdk_config" \
curl --fail --silent --show-error \
--header 'Metadata-Flavor: Google' \
'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email'
)"
if [ "$metadata_email" != "$EXPECTED_VM_SERVICE_ACCOUNT" ]; then
echo "Attached VM service account mismatch: $metadata_email" >&2
return 1
fi
# An empty, phase-local Cloud SDK config forces this token request to use
# the VM's attached identity instead of an operator's cached credentials.
sudo -n -u teleo env -u PGPASSWORD \
HOME=/home/teleo \
CLOUDSDK_CONFIG="$sdk_config" \
CLOUDSDK_CORE_PROJECT="$PROJECT" \
gcloud --quiet auth print-access-token >/dev/null
echo "ATTACHED_VM_SERVICE_ACCOUNT=$metadata_email"
}
run_scoped_status() {
local phase="$1" tool_path="$2" sdk_config="$3"
assert_attached_service_account "$sdk_config"
echo "SCOPED_STATUS phase=$phase user=teleo database=teleo_canonical"
sudo -n -u teleo env -u PGPASSWORD \
HOME=/home/teleo \
CLOUDSDK_CONFIG="$sdk_config" \
CLOUDSDK_CORE_PROJECT="$PROJECT" \
TELEO_GCP_PROJECT=teleo-501523 \
TELEO_CLOUDSQL_HOST=10.61.0.3 \
TELEO_CLOUDSQL_PORT=5432 \
TELEO_CLOUDSQL_DB=teleo_canonical \
TELEO_CANONICAL_CLOUDSQL_DB=teleo_canonical \
TELEO_CLOUDSQL_USER=leoclean_kb_runtime \
TELEO_CLOUDSQL_PASSWORD_SECRET=gcp-teleo-pgvector-standby-leoclean-kb-runtime-password \
TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK=0 \
python3 "$tool_path" status --format json --redacted
}
assert_deployed_files() {
local wrapper tool dropin
wrapper="$REMOTE_PROFILE_BIN/teleo-kb"
tool="$REMOTE_PROFILE_BIN/cloudsql_memory_tool.py"
dropin="$REMOTE_DROPIN_DIR/$DROPIN_NAME"
test "$(sha256sum "$wrapper" | awk '{print $1}')" = "$WRAPPER_SHA"
test "$(sha256sum "$tool" | awk '{print $1}')" = "$TOOL_SHA"
test "$(sudo sha256sum "$dropin" | awk '{print $1}')" = "$DROPIN_SHA"
sudo grep -Fx 'UnsetEnvironment=PGPASSWORD' "$dropin"
sudo grep -E '^Environment=(CLOUDSDK_CONFIG|TELEO_(KB_MODE|GCP_PROJECT|CLOUDSQL_|CANONICAL_CLOUDSQL_DB))' "$dropin"
}
REMOTE
)
printf -v verify_context \
'PROJECT=%q\nSERVICE=%q\nEXPECTED_VM_SERVICE_ACCOUNT=%q\nREMOTE_PROFILE_BIN=%q\nREMOTE_DROPIN_DIR=%q\nREMOTE_CLOUDSDK_CONFIG=%q\nDROPIN_NAME=%q\nWRAPPER_SHA=%q\nTOOL_SHA=%q\nDROPIN_SHA=%q\n' \
"$PROJECT" "$SERVICE" "$EXPECTED_VM_SERVICE_ACCOUNT" "$REMOTE_PROFILE_BIN" \
"$REMOTE_DROPIN_DIR" "$REMOTE_CLOUDSDK_CONFIG" "$DROPIN_NAME" \
"$WRAPPER_SHA" "$TOOL_SHA" "$DROPIN_SHA"
verify_body=$(cat <<'REMOTE'
set -euo pipefail
verification_tmp="$(mktemp -d /tmp/teleo-gcp-runtime-verify.XXXXXX)"
cleanup_verification() { sudo rm -rf -- "$verification_tmp"; }
trap cleanup_verification EXIT
assert_deployed_files
assert_service_running
assert_runtime_environment
run_scoped_status verify "$REMOTE_PROFILE_BIN/cloudsql_memory_tool.py" "$verification_tmp/gcloud"
systemctl show "$SERVICE" -p ActiveState -p SubState -p NRestarts -p MainPID -p User --no-pager
REMOTE
)
verify_command="${verify_context}${remote_helpers}"$'\n'"${verify_body}"
if $DRY_RUN; then
echo "DRY RUN: project=$PROJECT zone=$ZONE instance=$INSTANCE service=$SERVICE restart=$RESTART"
echo "DRY RUN: revision=$SOURCE_REVISION wrapper_sha256=$WRAPPER_SHA tool_sha256=$TOOL_SHA dropin_sha256=$DROPIN_SHA"
exit 0
fi
if $VERIFY_ONLY; then
"${GCP_SSH[@]}" --command="$verify_command"
exit 0
fi
if ! $RESTART; then
echo "Refusing to install runtime files without --restart; use --dry-run or --verify-only for read-only work." >&2
exit 64
fi
REMOTE_TMP="/tmp/teleo-gcp-leoclean-runtime-$(date -u +%Y%m%dT%H%M%SZ)-$$"
printf -v sync_context \
'PROJECT=%q\nSERVICE=%q\nEXPECTED_VM_SERVICE_ACCOUNT=%q\nREMOTE_PROFILE_BIN=%q\nREMOTE_DROPIN_DIR=%q\nREMOTE_CLOUDSDK_CONFIG=%q\nDROPIN_NAME=%q\nWRAPPER_SHA=%q\nTOOL_SHA=%q\nDROPIN_SHA=%q\nREMOTE_TMP=%q\nWRAPPER_REL=%q\nTOOL_REL=%q\nDROPIN_REL=%q\nSOURCE_REVISION=%q\n' \
"$PROJECT" "$SERVICE" "$EXPECTED_VM_SERVICE_ACCOUNT" "$REMOTE_PROFILE_BIN" \
"$REMOTE_DROPIN_DIR" "$REMOTE_CLOUDSDK_CONFIG" "$DROPIN_NAME" \
"$WRAPPER_SHA" "$TOOL_SHA" "$DROPIN_SHA" "$REMOTE_TMP" \
"$WRAPPER_REL" "$TOOL_REL" "$DROPIN_REL" "$SOURCE_REVISION"
sync_body=$(cat <<'REMOTE'
set -euo pipefail
remote_tmp="$REMOTE_TMP"
profile_bin="$REMOTE_PROFILE_BIN"
dropin_dir="$REMOTE_DROPIN_DIR"
backup_dir="$remote_tmp/backup"
dropin_stage="$dropin_dir/.${DROPIN_NAME}.livingip-new.$$"
mutation_started=false
backup_path() {
local target="$1" name="$2"
if sudo test -e "$target"; then
sudo cp -a -- "$target" "$backup_dir/$name"
touch "$backup_dir/$name.present"
fi
}
restore_path() {
local target="$1" name="$2" restore_rc=0 restore_tmp
if [ -f "$backup_dir/$name.present" ]; then
restore_tmp="${target}.livingip-rollback.$$"
sudo rm -f -- "$restore_tmp" || restore_rc=1
if sudo cp -a -- "$backup_dir/$name" "$restore_tmp"; then
sudo mv -f -- "$restore_tmp" "$target" || restore_rc=1
else
restore_rc=1
fi
sudo rm -f -- "$restore_tmp" || restore_rc=1
else
sudo rm -f -- "$target" || restore_rc=1
fi
return "$restore_rc"
}
rollback_runtime() {
local rollback_rc=0
echo "Deployment failed; restoring the complete previous runtime set." >&2
sudo systemctl stop "$SERVICE" || rollback_rc=1
restore_path "$profile_bin/teleo-kb" wrapper || rollback_rc=1
restore_path "$profile_bin/cloudsql_memory_tool.py" tool || rollback_rc=1
restore_path "$dropin_dir/$DROPIN_NAME" dropin || rollback_rc=1
restore_path "$profile_bin/.livingip-runtime-revision" revision || rollback_rc=1
sudo systemctl daemon-reload || rollback_rc=1
if [ "$rollback_rc" -eq 0 ]; then
sudo systemctl start "$SERVICE" || rollback_rc=1
assert_service_running || rollback_rc=1
fi
return "$rollback_rc"
}
on_exit() {
local rc="$1" rollback_rc=0
trap - EXIT INT TERM
if [ "$rc" -ne 0 ] && [ "$mutation_started" = true ]; then
set +e
rollback_runtime
rollback_rc=$?
set -e
if [ "$rollback_rc" -ne 0 ]; then
echo "CRITICAL: previous runtime restoration did not return the service to active/running." >&2
rc=1
fi
fi
sudo rm -f -- "$dropin_stage" || true
sudo rm -rf -- "$remote_tmp" || true
exit "$rc"
}
trap 'on_exit $?' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
sudo rm -rf -- "$remote_tmp"
mkdir -p "$backup_dir"
tar -C "$remote_tmp" -xf -
assert_service_running
run_scoped_status pre "$remote_tmp/$TOOL_REL" "$remote_tmp/gcloud-pre"
backup_path "$profile_bin/teleo-kb" wrapper
backup_path "$profile_bin/cloudsql_memory_tool.py" tool
backup_path "$dropin_dir/$DROPIN_NAME" dropin
backup_path "$profile_bin/.livingip-runtime-revision" revision
mutation_started=true
sudo systemctl stop "$SERVICE"
sudo install -d -o teleo -g teleo -m 0755 "$profile_bin"
sudo install -o teleo -g teleo -m 0755 "$remote_tmp/$WRAPPER_REL" "$profile_bin/teleo-kb"
sudo install -o teleo -g teleo -m 0755 "$remote_tmp/$TOOL_REL" "$profile_bin/cloudsql_memory_tool.py"
sudo install -d -o root -g root -m 0755 "$dropin_dir"
sudo install -o root -g root -m 0644 "$remote_tmp/$DROPIN_REL" "$dropin_stage"
sudo mv -f -- "$dropin_stage" "$dropin_dir/$DROPIN_NAME"
printf '%s\n' "$SOURCE_REVISION" | sudo tee "$profile_bin/.livingip-runtime-revision" >/dev/null
sudo chown teleo:teleo "$profile_bin/.livingip-runtime-revision"
sudo chmod 0644 "$profile_bin/.livingip-runtime-revision"
sudo systemctl daemon-reload
sudo systemctl start "$SERVICE"
assert_deployed_files
assert_service_running
assert_runtime_environment
run_scoped_status post "$profile_bin/cloudsql_memory_tool.py" "$remote_tmp/gcloud-post"
systemctl show "$SERVICE" -p ActiveState -p SubState -p NRestarts -p MainPID -p User --no-pager
mutation_started=false
REMOTE
)
sync_command="${sync_context}${remote_helpers}"$'\n'"${sync_body}"
COPYFILE_DISABLE=1 tar --format=ustar -C "$REPO_ROOT" -cf - \
"$WRAPPER_REL" "$TOOL_REL" "$DROPIN_REL" | "${GCP_SSH[@]}" --command="$sync_command"

View file

@ -0,0 +1,157 @@
# GCP Leo Runtime Reconciliation
## Scope
This runbook reconciles PR `#145` with the non-production GCP parallel Leo
service and removes its dependency on the PostgreSQL administrator credential.
It does not promote GCP, change the Telegram destination, copy newer VPS data,
or make `teleo_canonical` production-authoritative.
Target surfaces:
- project: `teleo-501523`;
- VM: `teleo-prod-1` in `europe-west6-a`;
- service: `leoclean-gcp-prod-parallel.service`;
- Cloud SQL database: `teleo_canonical` on private address `10.61.0.3`;
- runtime database role: `leoclean_kb_runtime`;
- staging-function owner: `leoclean_kb_stage_owner` (`NOLOGIN`);
- runtime secret: `gcp-teleo-pgvector-standby-leoclean-kb-runtime-password`.
Observed before reconciliation on 2026-07-14: the service was
`active/running`, but `/usr/local/bin/teleo-kb` matched unmerged PR `#145`, the
effective systemd environment selected database user `postgres` and the
administrator password secret, and a live `teleo-kb status` call exceeded a
20-second bound. Secret Manager access and private PostgreSQL reachability both
passed independently, isolating the timeout to the old helper path rather than
IAM or networking.
## Required End State
1. The live wrapper and Cloud SQL helper hashes match a merged repository
commit.
2. `TELEO_KB_MODE=cloudsql`; missing tools or credentials fail closed.
3. Canonical zero-hit searches do not consult `teleo_restore` unless an
operator explicitly opts in.
4. Leo can read the named canonical tables and stage only a
`pending_review` proposal through `kb_stage.stage_leoclean_proposal(...)`.
5. Leo cannot directly insert, update, or delete `public.*` or
`kb_stage.kb_proposals`, forge the proposer identity, `SET ROLE` into a
broader principal, or execute reviewer/apply functions.
6. The VM runtime identity can access only the scoped password secret needed by
this path, not the PostgreSQL administrator password secret.
7. Deployment proves the attached VM service account from the metadata server,
uses an empty phase-local Cloud SDK configuration, and rolls the complete
prior runtime set back if installation or post-restart verification fails.
## Safe Order Of Operations
### 1. Read-only access and IAM audit
Confirm the operator can read the project, use IAP/OS Login for the VM, inspect
Cloud SQL metadata, and inspect Secret Manager IAM. Identify the VM service
account and determine whether its secret access is project-wide or
secret-specific. Do not remove a project-wide binding until every legitimately
required runtime secret has an equivalent secret-level binding.
### 2. Merge reviewed repository code
Run CI on the completed PR `#145` repair before deployment. The deployment
source must be the resulting merged commit, not a working tree or unmerged
branch.
### 3. Create the scoped secret and grant only the VM runtime identity
Create the scoped secret without printing its value. Add one randomly generated
version, then grant `roles/secretmanager.secretAccessor` on that secret to the
VM runtime service account. Do not put `PGPASSWORD` in systemd, a repository,
an artifact, or a command transcript.
### 4. Provision the scoped PostgreSQL role once
Run `ops/gcp_leoclean_runtime_role.sql` as the Cloud SQL administrator from the
private VM path. Supply the new runtime password through
`TELEO_LEOCLEAN_DB_PASSWORD`; the SQL file does not contain it. The migration:
- creates or rotates `leoclean_kb_runtime`;
- creates a dedicated `NOLOGIN` function owner with no role memberships;
- removes stale table, column, sequence, function, and role-membership grants;
- grants exact canonical reads;
- creates a locked security-definer staging function that hard-codes both
`pending_review` and canonical proposer `leo`;
- grants no direct table writes;
- removes all access to the legacy `teleo_restore` schema;
- aborts if either scoped role owns or can reach anything outside the explicit
allowlist.
PostgreSQL grants `TEMP` to `PUBLIC` by default. The migration removes any
direct scoped `TEMP` grant and reports the remaining effective privilege, but
does not revoke `TEMP` from `PUBLIC`: PostgreSQL has no per-role deny, so that
would be a database-wide behavior change requiring a separate inventory of
`kb_apply`, reviewer, and operator use. The staging function remains protected
by a `pg_catalog, pg_temp` search path, schema-qualified relations, a fixed
`session_user`, and a tested temporary-object shadowing denial.
The administrator password is used only for this bounded bootstrap. Retain no
password output.
### 5. Preflight and deploy the merged runtime
From the exact merged checkout, run:
```bash
deploy/sync-gcp-leoclean-runtime.sh --dry-run
deploy/sync-gcp-leoclean-runtime.sh --restart
```
The deploy script runs a scoped, redacted database status preflight before it
installs files or restarts the service. It refuses dirty or unmerged runtime
files and refuses a live install without `--restart`. After preflight it stops
the parallel service, atomically replaces the existing Cloud SQL systemd
drop-in, installs the reviewed wrapper and helper, records the Git revision,
starts the service, and verifies all three file hashes. A failure after mutation
restores the prior wrapper, helper, drop-in, and revision as one set before the
old service is restarted.
### 6. Verify positive and negative behavior
Retain a redacted receipt containing:
- merged Git commit and deployed file hashes;
- service `ActiveState`, `SubState`, `MainPID`, and `NRestarts`;
- metadata-server identity and an access token obtained with an empty Cloud SDK
configuration;
- `current_database()` and `current_user` showing
`teleo_canonical|leoclean_kb_runtime`;
- a real canonical status/search receipt;
- a transaction-rolled-back call to `stage_leoclean_proposal(...)` that returns
`pending_review`;
- denied direct insert into `kb_stage.kb_proposals`;
- denied canonical `public.*` write;
- denied reviewer/apply security-definer function;
- denied forged proposer identity and denied `SET ROLE` escalation;
- zero Telegram messages and zero committed canary rows.
### 7. Remove administrator-secret access
Only after the scoped service passes post-restart verification, remove the VM
runtime identity's access path to
`gcp-teleo-pgvector-standby-postgres-password`. Verify from the VM runtime
identity that the scoped secret is readable and the administrator secret is
denied. Never delete the administrator secret merely to enforce runtime least
privilege; backup/restore operators may still require it under a separate
identity.
## Stop Conditions
Stop without cutover if any of these are true:
- the deployed revision is not merged;
- the scoped status preflight fails;
- the role has direct proposal-table insert or canonical write permission;
- any approval/apply function is executable by the runtime role;
- removing broad Secret Manager access would break another required secret;
- GCP canonical rows are still stale relative to the chosen authority.
The last condition does not invalidate this runtime security repair. It means
GCP remains staging and data reconciliation stays a separate, explicitly
authorized slice.

View file

@ -6,6 +6,7 @@
"database_first_merge_commit": "08371bf2a0461536c112e1235a7229b52ed270cc", "database_first_merge_commit": "08371bf2a0461536c112e1235a7229b52ed270cc",
"database_first_pull_request": 144, "database_first_pull_request": 144,
"fail_closed_wrapper_commit": "8451c51", "fail_closed_wrapper_commit": "8451c51",
"fail_closed_wrapper_merge_state_at_capture": "unmerged PR #145; manually installed on the GCP VM",
"repository": "living-ip/teleo-infrastructure" "repository": "living-ip/teleo-infrastructure"
}, },
"deployment": { "deployment": {
@ -55,7 +56,7 @@
"integration_regression": { "integration_regression": {
"detected_live": true, "detected_live": true,
"symptom": "The old auto-mode wrapper used a full receipted status call as a 30-second health probe; the stronger read exceeded that bound and the wrapper fell through to a different local tool.", "symptom": "The old auto-mode wrapper used a full receipted status call as a 30-second health probe; the stronger read exceeded that bound and the wrapper fell through to a different local tool.",
"repair": "Supported GCP knowledge commands now route directly to Cloud SQL and fail closed on errors instead of changing databases.", "repair": "The manually installed PR #145 wrapper routes supported GCP knowledge commands directly to Cloud SQL and propagates failures after selection. Missing prerequisites could still fall through before selection at capture time.",
"regression_tests": 2, "regression_tests": 2,
"full_repository_tests": { "full_repository_tests": {
"passed": 1240, "passed": 1240,
@ -113,8 +114,10 @@
"post_restart_model_turn_run": false "post_restart_model_turn_run": false
}, },
"claim_ceiling": { "claim_ceiling": {
"proven": "Merged database-first helper and skill deployment, fail-closed Cloud SQL routing, controlled GCP service restart, and post-restart live status/search receipts.", "proven": "Merged database-first helper and skill deployment, manually installed unmerged wrapper routing repair, controlled GCP service restart, and post-restart live status/search receipts.",
"not_proven": [ "not_proven": [
"repository/runtime parity for the wrapper",
"least-privilege Cloud SQL runtime credential",
"post-restart model reasoning turn", "post-restart model reasoning turn",
"Telegram-visible delivery", "Telegram-visible delivery",
"canonical proposal apply", "canonical proposal apply",

View file

@ -94,10 +94,11 @@ remaining target databases. The disabled rollback database still has zero
connections. The live GCP Leo service remained PID `148735`, active/running, connections. The live GCP Leo service remained PID `148735`, active/running,
with `NRestarts=0` throughout restore, model replay, and cleanup. with `NRestarts=0` throughout restore, model replay, and cleanup.
### 5. The merged GCP path survived deployment and restart ### 5. The merged database-first path and an unmerged wrapper repair survived restart
PR `#144` merged the database-first helper and skill. Their exact reviewed PR `#144` merged the database-first helper and skill. Their exact reviewed
hashes were installed on `teleo-prod-1`, then hashes were installed on `teleo-prod-1`. A separate wrapper repair from the
then-unmerged PR `#145` was also installed manually, then
`leoclean-gcp-prod-parallel.service` was intentionally restarted. It returned `leoclean-gcp-prod-parallel.service` was intentionally restarted. It returned
active/running with a new PID (`304036`) and the deployed hashes remained in active/running with a new PID (`304036`) and the deployed hashes remained in
place. place.
@ -105,9 +106,12 @@ place.
The first post-restart search caught one real integration regression: the old The first post-restart search caught one real integration regression: the old
wrapper used a full `status` read as a 30-second health probe. Stronger receipts wrapper used a full `status` read as a 30-second health probe. Stronger receipts
made that probe exceed its bound, after which `auto` mode could silently select made that probe exceed its bound, after which `auto` mode could silently select
a different local tool. The wrapper now routes every supported GCP knowledge a different local tool. The manually deployed wrapper routes every supported
command directly to Cloud SQL and fails closed on errors. Two regression tests GCP knowledge command directly to Cloud SQL and propagates errors after backend
cover direct routing and no fallback. selection. At the time of this receipt, missing host prerequisites could still
cause an earlier fallback, the runtime still used the administrator database
credential, and the live wrapper did not match merged `main`. PR `#145`
therefore remained a reconciliation task rather than a production-ready result.
A corrected live search then found the expected sandbagging claim plus both A corrected live search then found the expected sandbagging claim plus both
expected source rows and hashes from persistent `teleo_canonical`. A subsequent expected source rows and hashes from persistent `teleo_canonical`. A subsequent
@ -140,7 +144,7 @@ rows as if they were source-code branches.
| `gcp-db-first-parity-current.json` | `0173ee6707016e8412e6dd4326d61f71b6ef862bbc5b819079d62680676729f2` | Schema, row, role, extension, and performance parity passed | | `gcp-db-first-parity-current.json` | `0173ee6707016e8412e6dd4326d61f71b6ef862bbc5b819079d62680676729f2` | Schema, row, role, extension, and performance parity passed |
| `gcp-db-first-blind-claim-current.json` | `8a7cc3c1814eb385e858f12ef7579cd4524f37886c03fc6d9c61624b6aff2a52` | Real GCP Hermes reasoning passed with source-bound read receipts | | `gcp-db-first-blind-claim-current.json` | `8a7cc3c1814eb385e858f12ef7579cd4524f37886c03fc6d9c61624b6aff2a52` | Real GCP Hermes reasoning passed with source-bound read receipts |
| `gcp-db-first-cleanup-current.json` | `cac7e34f45653fabb696d911b6ccc921d3f291bf8cbe05a429d757e717dcafb4` | Clone absent, rollback disabled, service unchanged | | `gcp-db-first-cleanup-current.json` | `cac7e34f45653fabb696d911b6ccc921d3f291bf8cbe05a429d757e717dcafb4` | Clone absent, rollback disabled, service unchanged |
| `gcp-db-first-live-deploy-restart-current.json` | `d70fdbac859b8c98d557f4df900139a93e6009e12f7b80c9adbd69a5655b9df7` | Merged deploy, fail-closed routing, restart, and live status/search readback passed | | `gcp-db-first-live-deploy-restart-current.json` | `78e9dde38d641bfa3d58b2b7d8605b37f2861605707eddc0c94ad5c9a70d080d` | Merged PR `#144` files plus an unmerged PR `#145` wrapper were installed; restart and live status/search readback passed |
## What This Unlocks ## What This Unlocks
@ -166,6 +170,9 @@ rows as if they were source-code branches.
6. A model reasoning turn after the controlled GCP restart. The post-restart 6. A model reasoning turn after the controlled GCP restart. The post-restart
proof in this report is the real service plus live `teleo-kb` status/search proof in this report is the real service plus live `teleo-kb` status/search
path, not a new paid model response. path, not a new paid model response.
7. Repository/runtime parity for the wrapper or a least-privilege Cloud SQL
credential. The captured service still used an unmerged wrapper and the
administrator database secret.
The next product-level proof is one natural Telegram challenge that reaches The next product-level proof is one natural Telegram challenge that reaches
the same database-grounded reasoning, followed by one explicitly approved the same database-grounded reasoning, followed by one explicitly approved

View file

@ -74,6 +74,9 @@ STOPWORDS = {
DEFAULT_CLAIM_BASE_URL = "https://leo.livingip.xyz" DEFAULT_CLAIM_BASE_URL = "https://leo.livingip.xyz"
RETRIEVAL_RECEIPT_SCHEMA = "livingip.teleoKbRetrievalReceipt.v1" RETRIEVAL_RECEIPT_SCHEMA = "livingip.teleoKbRetrievalReceipt.v1"
RUNTIME_DB_USER = "leoclean_kb_runtime"
RUNTIME_PASSWORD_SECRET = "gcp-teleo-pgvector-standby-leoclean-kb-runtime-password"
CLONE_READ_ONLY_PGOPTIONS = "-c default_transaction_read_only=on"
RECEIPTED_READ_COMMANDS = frozenset( RECEIPTED_READ_COMMANDS = frozenset(
{ {
"search", "search",
@ -94,14 +97,20 @@ def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--host", default=os.environ.get("TELEO_CLOUDSQL_HOST", "10.61.0.3")) parser.add_argument("--host", default=os.environ.get("TELEO_CLOUDSQL_HOST", "10.61.0.3"))
parser.add_argument("--port", default=os.environ.get("TELEO_CLOUDSQL_PORT", "5432")) parser.add_argument("--port", default=os.environ.get("TELEO_CLOUDSQL_PORT", "5432"))
parser.add_argument("--db", default=os.environ.get("TELEO_CLOUDSQL_DB", "teleo_kb")) parser.add_argument("--db", default=os.environ.get("TELEO_CLOUDSQL_DB", "teleo_canonical"))
parser.add_argument("--canonical-db", default=os.environ.get("TELEO_CANONICAL_CLOUDSQL_DB", "teleo_canonical")) parser.add_argument("--canonical-db", default=os.environ.get("TELEO_CANONICAL_CLOUDSQL_DB", "teleo_canonical"))
parser.add_argument("--user", default=os.environ.get("TELEO_CLOUDSQL_USER", "postgres")) parser.add_argument("--user", default=os.environ.get("TELEO_CLOUDSQL_USER"))
parser.add_argument( parser.add_argument(
"--password-secret", "--password-secret",
default=os.environ.get("TELEO_CLOUDSQL_PASSWORD_SECRET", "gcp-teleo-pgvector-standby-postgres-password"), default=os.environ.get("TELEO_CLOUDSQL_PASSWORD_SECRET"),
) )
parser.add_argument("--project", default=os.environ.get("TELEO_GCP_PROJECT", "teleo-501523")) parser.add_argument("--project", default=os.environ.get("TELEO_GCP_PROJECT", "teleo-501523"))
parser.add_argument(
"--credential-mode",
choices=["runtime", "clone-readonly"],
default=os.environ.get("TELEO_CLOUDSQL_CREDENTIAL_MODE", "runtime"),
help="Runtime mode requires the exact scoped Leo identity. Clone mode is limited to read-only teleo_clone_* tests.",
)
parser.add_argument("--format", choices=["markdown", "json"], default="markdown") parser.add_argument("--format", choices=["markdown", "json"], default="markdown")
parser.add_argument( parser.add_argument(
"--include-excerpts", "--include-excerpts",
@ -185,7 +194,7 @@ def parse_args() -> argparse.Namespace:
propose.add_argument("--proposed", required=True, help="Proposed replacement or correction.") propose.add_argument("--proposed", required=True, help="Proposed replacement or correction.")
propose.add_argument("--evidence", action="append", default=[], help="Evidence/source pointer. Can be repeated.") propose.add_argument("--evidence", action="append", default=[], help="Evidence/source pointer. Can be repeated.")
propose.add_argument("--implication", action="append", default=[], help="Downstream implication. Can be repeated.") propose.add_argument("--implication", action="append", default=[], help="Downstream implication. Can be repeated.")
propose.add_argument("--proposed-by", default="leo") propose.add_argument("--proposed-by", choices=["leo"], default="leo")
propose.add_argument("--originator", default="", help="Human or agent who originated the correction.") propose.add_argument("--originator", default="", help="Human or agent who originated the correction.")
propose.add_argument("--channel", default="telegram") propose.add_argument("--channel", default="telegram")
propose.add_argument("--source-ref", required=True) propose.add_argument("--source-ref", required=True)
@ -196,7 +205,7 @@ def parse_args() -> argparse.Namespace:
propose_edge.add_argument("from_claim") propose_edge.add_argument("from_claim")
propose_edge.add_argument("edge_type") propose_edge.add_argument("edge_type")
propose_edge.add_argument("to_claim") propose_edge.add_argument("to_claim")
propose_edge.add_argument("--proposed-by", default="leo") propose_edge.add_argument("--proposed-by", choices=["leo"], default="leo")
propose_edge.add_argument("--channel", default="telegram") propose_edge.add_argument("--channel", default="telegram")
propose_edge.add_argument("--source-ref", required=True) propose_edge.add_argument("--source-ref", required=True)
propose_edge.add_argument("--rationale", required=True) propose_edge.add_argument("--rationale", required=True)
@ -228,6 +237,34 @@ def parse_args() -> argparse.Namespace:
return parser.parse_args() return parser.parse_args()
def validate_runtime_connection(args: argparse.Namespace) -> None:
user = str(args.user or "")
password_secret = str(args.password_secret or "")
mode = str(getattr(args, "credential_mode", "runtime"))
if mode == "runtime":
if user != RUNTIME_DB_USER:
raise SystemExit(f"Leo runtime requires database user {RUNTIME_DB_USER}.")
if password_secret != RUNTIME_PASSWORD_SECRET:
raise SystemExit(f"Leo runtime requires scoped password secret {RUNTIME_PASSWORD_SECRET}.")
if "PGPASSWORD" in os.environ:
raise SystemExit("Refusing inherited PGPASSWORD in Leo runtime mode.")
return
if mode != "clone-readonly":
raise SystemExit(f"Unsupported Cloud SQL credential mode: {mode}.")
canonical_db = str(args.canonical_db or "")
audit_db = str(args.db or "")
pgoptions = os.environ.get("PGOPTIONS", "")
if not re.fullmatch(r"teleo_clone_[a-z0-9_]+", canonical_db) or audit_db != canonical_db:
raise SystemExit("Clone credential mode is limited to one teleo_clone_* database.")
if args.command not in RECEIPTED_READ_COMMANDS:
raise SystemExit("Clone credential mode permits read commands only.")
if pgoptions != CLONE_READ_ONLY_PGOPTIONS:
raise SystemExit("Clone credential mode requires server-enforced default_transaction_read_only=on.")
if not user or (not password_secret and not os.environ.get("PGPASSWORD")):
raise SystemExit("Clone credential mode requires an explicit user and credential source.")
def terms_for(query: str) -> list[str]: def terms_for(query: str) -> list[str]:
terms: list[str] = [] terms: list[str] = []
for token in re.findall(r"[A-Za-z0-9][A-Za-z0-9.+#/-]*", query.lower()): for token in re.findall(r"[A-Za-z0-9][A-Za-z0-9.+#/-]*", query.lower()):
@ -269,8 +306,11 @@ def sql_array(values: list[str], cast: str = "text") -> str:
def password(args: argparse.Namespace) -> str: def password(args: argparse.Namespace) -> str:
if os.environ.get("PGPASSWORD"): validate_runtime_connection(args)
if args.credential_mode == "clone-readonly" and os.environ.get("PGPASSWORD"):
return os.environ["PGPASSWORD"] return os.environ["PGPASSWORD"]
if not args.password_secret:
raise SystemExit("Scoped Cloud SQL password secret is not configured.")
result = subprocess.run( result = subprocess.run(
[ [
"gcloud", "gcloud",
@ -287,15 +327,32 @@ def password(args: argparse.Namespace) -> str:
) )
if result.returncode != 0: if result.returncode != 0:
raise SystemExit(f"Secret access failed: {result.stderr.strip()}") raise SystemExit(f"Secret access failed: {result.stderr.strip()}")
return result.stdout.strip() credential = result.stdout.strip()
if not credential:
raise SystemExit("Scoped Cloud SQL password secret resolved to an empty value.")
return credential
def run_psql(args: argparse.Namespace, sql: str, db: str | None = None) -> str: def run_psql(args: argparse.Namespace, sql: str, db: str | None = None) -> str:
env = os.environ.copy() env = os.environ.copy()
env["PGPASSWORD"] = password(args) for key in tuple(env):
conn = f"host={args.host} port={args.port} dbname={db or args.db} user={args.user} sslmode=require" if key.startswith("PG"):
env.pop(key, None)
env.update(
{
"PGHOST": str(args.host),
"PGPORT": str(args.port),
"PGDATABASE": str(db or args.db),
"PGUSER": str(args.user),
"PGSSLMODE": "require",
"PGCONNECT_TIMEOUT": "8",
"PGPASSWORD": password(args),
}
)
if args.credential_mode == "clone-readonly":
env["PGOPTIONS"] = CLONE_READ_ONLY_PGOPTIONS
result = subprocess.run( result = subprocess.run(
["psql", conn, "-At", "-q", "-v", "ON_ERROR_STOP=1"], ["psql", "-X", "-At", "-q", "-v", "ON_ERROR_STOP=1"],
text=True, text=True,
capture_output=True, capture_output=True,
input=sql, input=sql,
@ -868,6 +925,11 @@ def query_rows(args: argparse.Namespace, query: str, limit: int) -> dict[str, An
canonical = query_canonical_rows(args, query, limit) canonical = query_canonical_rows(args, query, limit)
if canonical.get("hit_count_total", 0) or canonical.get("hits"): if canonical.get("hit_count_total", 0) or canonical.get("hits"):
return canonical return canonical
if os.environ.get("TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK", "") != "1":
canonical["canonical_fallback_reason"] = (
"no matching canonical public/persona/strategy/belief rows; audit fallback disabled"
)
return canonical
if not audit_restore_available(args): if not audit_restore_available(args):
canonical["canonical_fallback_reason"] = ( canonical["canonical_fallback_reason"] = (
"no matching canonical public/persona/strategy/belief rows; teleo_restore audit fallback unavailable" "no matching canonical public/persona/strategy/belief rows; teleo_restore audit fallback unavailable"
@ -1054,57 +1116,37 @@ def propose_core_change(args: argparse.Namespace) -> dict[str, Any]:
with params as ( with params as (
select select
{sql_literal(args.proposal_type)}::text as proposal_type, {sql_literal(args.proposal_type)}::text as proposal_type,
nullif({sql_literal(args.proposed_by)}, '') as proposed_by_handle,
nullif({sql_literal(args.channel)}, '') as channel, nullif({sql_literal(args.channel)}, '') as channel,
nullif({sql_literal(args.source_ref)}, '') as source_ref, nullif({sql_literal(args.source_ref)}, '') as source_ref,
{sql_literal(args.rationale)} as rationale, {sql_literal(args.rationale)} as rationale,
{sql_literal(payload_json)}::jsonb as payload {sql_literal(payload_json)}::jsonb as payload
), proposer as ( ), proposer as (
select a.id, a.handle select kb_stage.stage_leoclean_proposal(
from public.agents a, params p
where a.handle = p.proposed_by_handle
limit 1
), inserted as (
insert into kb_stage.kb_proposals (
proposal_type,
status,
proposed_by_handle,
proposed_by_agent_id,
channel,
source_ref,
rationale,
payload
)
select
p.proposal_type, p.proposal_type,
'pending_review', p.channel,
p.proposed_by_handle,
proposer.id,
coalesce(p.channel, 'telegram'),
p.source_ref, p.source_ref,
p.rationale, p.rationale,
p.payload p.payload
) as proposal
from params p from params p
left join proposer on true
returning *
) )
select jsonb_build_object( select jsonb_build_object(
'artifact', 'teleo_cloudsql_kb_core_change_proposal', 'artifact', 'teleo_cloudsql_kb_core_change_proposal',
'backend', {sql_literal(canonical_backend(args))}, 'backend', {sql_literal(canonical_backend(args))},
'id', id::text, 'id', proposal->>'id',
'proposal_type', proposal_type, 'proposal_type', proposal->>'proposal_type',
'status', status, 'status', proposal->>'status',
'proposed_by_handle', proposed_by_handle, 'proposed_by_handle', proposal->>'proposed_by_handle',
'proposed_by_agent_id', proposed_by_agent_id::text, 'proposed_by_agent_id', proposal->>'proposed_by_agent_id',
'channel', channel, 'channel', proposal->>'channel',
'source_ref', source_ref, 'source_ref', proposal->>'source_ref',
'rationale', rationale, 'rationale', proposal->>'rationale',
'payload', payload, 'payload', proposal->'payload',
'created_at', created_at::text, 'created_at', proposal->>'created_at',
'canonical_apply_done', false, 'canonical_apply_done', false,
'runtime_memory_write_done', false 'runtime_memory_write_done', false
)::text )::text
from inserted; from proposer;
""" """
rows = psql_json_lines(args, sql, db=args.canonical_db) rows = psql_json_lines(args, sql, db=args.canonical_db)
if not rows: if not rows:
@ -1119,7 +1161,6 @@ with params as (
{sql_literal(args.from_claim)}::uuid as from_claim, {sql_literal(args.from_claim)}::uuid as from_claim,
{sql_literal(args.to_claim)}::uuid as to_claim, {sql_literal(args.to_claim)}::uuid as to_claim,
{sql_literal(args.edge_type)}::edge_type as edge_type, {sql_literal(args.edge_type)}::edge_type as edge_type,
nullif({sql_literal(args.proposed_by)}, '') as proposed_by_handle,
nullif({sql_literal(args.channel)}, '') as channel, nullif({sql_literal(args.channel)}, '') as channel,
nullif({sql_literal(args.source_ref)}, '') as source_ref, nullif({sql_literal(args.source_ref)}, '') as source_ref,
{sql_literal(args.rationale)} as rationale {sql_literal(args.rationale)} as rationale
@ -1131,11 +1172,6 @@ with params as (
select c.id, c.text select c.id, c.text
from public.claims c, params p from public.claims c, params p
where c.id = p.to_claim where c.id = p.to_claim
), proposer as (
select a.id, a.handle
from public.agents a, params p
where a.handle = p.proposed_by_handle
limit 1
), existing_edge as ( ), existing_edge as (
select e.id select e.id
from public.claim_edges e, params p from public.claim_edges e, params p
@ -1143,23 +1179,10 @@ with params as (
and e.to_claim = p.to_claim and e.to_claim = p.to_claim
and e.edge_type = p.edge_type and e.edge_type = p.edge_type
limit 1 limit 1
), inserted as ( ), staged as (
insert into kb_stage.kb_proposals ( select kb_stage.stage_leoclean_proposal(
proposal_type,
status,
proposed_by_handle,
proposed_by_agent_id,
channel,
source_ref,
rationale,
payload
)
select
'add_edge', 'add_edge',
'pending_review', p.channel,
p.proposed_by_handle,
proposer.id,
coalesce(p.channel, 'telegram'),
p.source_ref, p.source_ref,
p.rationale, p.rationale,
jsonb_build_object( jsonb_build_object(
@ -1175,29 +1198,28 @@ with params as (
'edge_type', p.edge_type::text 'edge_type', p.edge_type::text
) )
) )
) as proposal
from params p from params p
join from_row on true join from_row on true
join to_row on true join to_row on true
left join proposer on true
returning *
) )
select jsonb_build_object( select jsonb_build_object(
'artifact', 'teleo_cloudsql_kb_edge_proposal', 'artifact', 'teleo_cloudsql_kb_edge_proposal',
'backend', {sql_literal(canonical_backend(args))}, 'backend', {sql_literal(canonical_backend(args))},
'id', id::text, 'id', proposal->>'id',
'proposal_type', proposal_type, 'proposal_type', proposal->>'proposal_type',
'status', status, 'status', proposal->>'status',
'proposed_by_handle', proposed_by_handle, 'proposed_by_handle', proposal->>'proposed_by_handle',
'proposed_by_agent_id', proposed_by_agent_id::text, 'proposed_by_agent_id', proposal->>'proposed_by_agent_id',
'channel', channel, 'channel', proposal->>'channel',
'source_ref', source_ref, 'source_ref', proposal->>'source_ref',
'rationale', rationale, 'rationale', proposal->>'rationale',
'payload', payload, 'payload', proposal->'payload',
'created_at', created_at::text, 'created_at', proposal->>'created_at',
'canonical_apply_done', false, 'canonical_apply_done', false,
'runtime_memory_write_done', false 'runtime_memory_write_done', false
)::text )::text
from inserted; from staged;
""" """
rows = psql_json_lines(args, sql, db=args.canonical_db) rows = psql_json_lines(args, sql, db=args.canonical_db)
if not rows: if not rows:
@ -1365,6 +1387,8 @@ select jsonb_build_object(
def audit_restore_available(args: argparse.Namespace) -> bool: def audit_restore_available(args: argparse.Namespace) -> bool:
if os.environ.get("TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK", "") != "1":
return False
sql = """ sql = """
select bool_and(to_regclass(table_name) is not null) select bool_and(to_regclass(table_name) is not null)
from (values from (values
@ -1675,6 +1699,7 @@ def emit_markdown(value: dict[str, Any]) -> None:
def main() -> None: def main() -> None:
args = parse_args() args = parse_args()
validate_runtime_connection(args)
read_loaders = { read_loaders = {
"search": lambda: query_rows(args, args.query, args.limit), "search": lambda: query_rows(args, args.query, args.limit),
"context": lambda: query_rows(args, args.query, args.limit), "context": lambda: query_rows(args, args.query, args.limit),

View file

@ -23,14 +23,25 @@ cloudsql_args() {
} }
run_cloudsql() { run_cloudsql() {
if cloudsql_supported; then if ! cloudsql_supported; then
extra_args=() echo "teleo-kb: command '$COMMAND' is not supported by the Cloud SQL backend" >&2
while IFS= read -r arg; do exit 64
[ -n "$arg" ] && extra_args+=("$arg")
done < <(cloudsql_args)
exec python3 "$CLOUDSQL_TOOL" "$@" "${extra_args[@]}"
fi fi
exec python3 "$KB_TOOL" "$@" if [ ! -x "$CLOUDSQL_TOOL" ]; then
echo "teleo-kb: Cloud SQL tool is missing or not executable: $CLOUDSQL_TOOL" >&2
exit 69
fi
for dependency in python3 psql gcloud; do
if ! command -v "$dependency" >/dev/null 2>&1; then
echo "teleo-kb: required Cloud SQL dependency is unavailable: $dependency" >&2
exit 69
fi
done
extra_args=()
while IFS= read -r arg; do
[ -n "$arg" ] && extra_args+=("$arg")
done < <(cloudsql_args)
exec python3 "$CLOUDSQL_TOOL" "$@" "${extra_args[@]}"
} }
case "$MODE" in case "$MODE" in
@ -46,8 +57,8 @@ case "$MODE" in
auto) auto)
# Canonical commands must fail closed on Cloud SQL errors. Falling through # Canonical commands must fail closed on Cloud SQL errors. Falling through
# to a different local database would make source-of-truth behavior depend # to a different local database would make source-of-truth behavior depend
# on health-check latency. # on health-check latency or host package state.
if [ -x "$CLOUDSQL_TOOL" ] && command -v psql >/dev/null 2>&1 && command -v gcloud >/dev/null 2>&1 && cloudsql_supported; then if cloudsql_supported; then
run_cloudsql "$@" run_cloudsql "$@"
fi fi
if command -v docker >/dev/null 2>&1; then if command -v docker >/dev/null 2>&1; then

View file

@ -0,0 +1,847 @@
\set ON_ERROR_STOP on
\getenv runtime_password TELEO_LEOCLEAN_DB_PASSWORD
-- One-time GCP staging provisioning for Leo's Cloud SQL identity. The password
-- is supplied only through the process environment and is never stored here.
--
-- The login can read the canonical allowlist and can stage a pending proposal
-- through one narrowly-scoped SECURITY DEFINER function. It cannot inherit or
-- SET ROLE into another principal, forge the proposer identity, write directly
-- to canonical/staging tables, or inspect the legacy teleo_restore audit schema.
select 'create role leoclean_kb_runtime login nosuperuser nocreatedb nocreaterole noinherit noreplication nobypassrls connection limit 8'
where not exists (select 1 from pg_catalog.pg_roles where rolname = 'leoclean_kb_runtime')
\gexec
select 'create role leoclean_kb_stage_owner nologin nosuperuser nocreatedb nocreaterole noinherit noreplication nobypassrls connection limit -1'
where not exists (select 1 from pg_catalog.pg_roles where rolname = 'leoclean_kb_stage_owner')
\gexec
select format('alter role leoclean_kb_runtime password %L', :'runtime_password')
\gexec
alter role leoclean_kb_runtime
with login nosuperuser nocreatedb nocreaterole noinherit noreplication nobypassrls connection limit 8;
alter role leoclean_kb_stage_owner
with nologin nosuperuser nocreatedb nocreaterole noinherit noreplication nobypassrls connection limit -1
password null;
alter role leoclean_kb_runtime reset all;
alter role leoclean_kb_stage_owner reset all;
-- NOINHERIT does not prevent SET ROLE. Remove every membership edge in either
-- direction so neither scoped principal can reach, or be reached through, a
-- broader role left behind by an earlier deployment.
select format('revoke %I from %I', granted_role.rolname, member_role.rolname)
from pg_catalog.pg_auth_members membership
join pg_catalog.pg_roles granted_role on granted_role.oid = membership.roleid
join pg_catalog.pg_roles member_role on member_role.oid = membership.member
where granted_role.rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
or member_role.rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
\gexec
revoke all privileges on database teleo_canonical from leoclean_kb_runtime;
revoke all privileges on database teleo_canonical from leoclean_kb_stage_owner;
revoke all privileges on database teleo_kb from leoclean_kb_runtime;
revoke all privileges on database teleo_kb from leoclean_kb_stage_owner;
grant connect on database teleo_canonical to leoclean_kb_runtime;
-- PostgreSQL normally grants TEMP to PUBLIC. A role-specific REVOKE cannot
-- override that inherited grant, while REVOKE TEMP FROM PUBLIC would be a
-- database-wide change that could break kb_apply/review or operator workflows.
-- Leave PUBLIC TEMP policy unchanged here and surface it in the final receipt.
-- TEMP cannot shadow this SECURITY DEFINER boundary: pg_catalog is first,
-- pg_temp is explicitly last, and every relation reference is schema-qualified.
-- Database CREATE, direct TEMP grants, and all unexpected direct database ACLs
-- are rejected below. A database-wide TEMP removal requires a separate role-use
-- inventory and explicit grants to every workload that still needs it.
alter role leoclean_kb_runtime in database teleo_canonical reset all;
alter role leoclean_kb_stage_owner in database teleo_canonical reset all;
alter role leoclean_kb_runtime in database teleo_kb reset all;
alter role leoclean_kb_stage_owner in database teleo_kb reset all;
alter role leoclean_kb_runtime in database teleo_canonical
set search_path = pg_catalog, public, kb_stage;
alter role leoclean_kb_runtime in database teleo_canonical
set statement_timeout = '15s';
alter role leoclean_kb_runtime in database teleo_canonical
set lock_timeout = '2s';
\connect teleo_canonical
begin;
-- The deploy principal needs temporary ownership authority to replace an
-- existing function on idempotent reruns. This membership is transaction-local
-- and is revoked before verification/commit.
select format('grant leoclean_kb_stage_owner to %I', current_user)
where current_user <> 'leoclean_kb_stage_owner'
\gexec
revoke all on schema public, kb_stage
from leoclean_kb_runtime, leoclean_kb_stage_owner;
revoke all privileges on all tables in schema public, kb_stage
from leoclean_kb_runtime, leoclean_kb_stage_owner;
revoke all privileges on all sequences in schema public, kb_stage
from leoclean_kb_runtime, leoclean_kb_stage_owner;
revoke all privileges on all functions in schema public, kb_stage
from leoclean_kb_runtime, leoclean_kb_stage_owner;
revoke all privileges on all procedures in schema public, kb_stage
from leoclean_kb_runtime, leoclean_kb_stage_owner;
-- Table-level REVOKE does not clear column ACLs. Clear every column ACL for both
-- principals before rebuilding the exact allowlist below.
select format(
'revoke all privileges (%s) on table %I.%I from %I',
pg_catalog.string_agg(pg_catalog.quote_ident(attribute.attname), ', ' order by attribute.attnum),
namespace.nspname,
relation.relname,
target.rolname
)
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
join pg_catalog.pg_attribute attribute on attribute.attrelid = relation.oid
cross join (
values ('leoclean_kb_runtime'), ('leoclean_kb_stage_owner')
) as target(rolname)
where namespace.nspname in ('public', 'kb_stage')
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and attribute.attnum > 0
and not attribute.attisdropped
group by namespace.nspname, relation.relname, target.rolname
\gexec
grant usage on schema public, kb_stage to leoclean_kb_runtime;
grant select on
public.agents,
public.claims,
public.claim_evidence,
public.claim_edges,
public.sources,
public.personas,
public.strategies,
public.beliefs,
public.blindspots,
public.agent_roles,
public.peer_models,
public.behavioral_rules,
public.contributor_rules,
public.reasoning_tools,
public.governance_gates,
kb_stage.kb_proposals
to leoclean_kb_runtime;
-- The function owner is a dedicated NOLOGIN role. Grant only the columns the
-- function reads/inserts. CREATE is temporary and is revoked immediately after
-- ownership is assigned.
grant usage, create on schema kb_stage to leoclean_kb_stage_owner;
grant usage on schema public to leoclean_kb_stage_owner;
grant select (id, handle) on public.agents to leoclean_kb_stage_owner;
grant select on kb_stage.kb_proposals to leoclean_kb_stage_owner;
grant insert (
proposal_type,
status,
proposed_by_handle,
proposed_by_agent_id,
channel,
source_ref,
rationale,
payload
) on kb_stage.kb_proposals to leoclean_kb_stage_owner;
-- Remove the prior caller-supplied-identity overload before installing the
-- session-bound form. A runtime caller can only stage as canonical agent Leo.
drop function if exists kb_stage.stage_leoclean_proposal(text, text, text, text, text, jsonb);
create or replace function kb_stage.stage_leoclean_proposal(
p_proposal_type text,
p_channel text,
p_source_ref text,
p_rationale text,
p_payload jsonb
) returns jsonb
language plpgsql
security definer
set search_path = pg_catalog, pg_temp
as $function$
declare
staged kb_stage.kb_proposals%rowtype;
proposer_id uuid;
begin
if session_user <> 'leoclean_kb_runtime' then
raise exception 'stage_leoclean_proposal is restricted to leoclean_kb_runtime'
using errcode = '42501';
end if;
if p_proposal_type not in ('revise_claim', 'revise_strategy', 'add_edge') then
raise exception 'unsupported Leo proposal type: %', p_proposal_type using errcode = '22023';
end if;
if nullif(btrim(p_source_ref), '') is null then
raise exception 'source_ref is required' using errcode = '22023';
end if;
if nullif(btrim(p_rationale), '') is null then
raise exception 'rationale is required' using errcode = '22023';
end if;
if p_payload is null or jsonb_typeof(p_payload) <> 'object' then
raise exception 'proposal payload must be a JSON object' using errcode = '22023';
end if;
select agent.id
into proposer_id
from public.agents as agent
where agent.handle = 'leo';
if proposer_id is null then
raise exception 'canonical Leo agent row is required' using errcode = '23503';
end if;
insert into kb_stage.kb_proposals (
proposal_type,
status,
proposed_by_handle,
proposed_by_agent_id,
channel,
source_ref,
rationale,
payload
) values (
p_proposal_type,
'pending_review',
'leo',
proposer_id,
coalesce(nullif(p_channel, ''), 'telegram'),
p_source_ref,
p_rationale,
p_payload
)
returning * into staged;
return to_jsonb(staged);
end
$function$;
alter function kb_stage.stage_leoclean_proposal(text, text, text, text, jsonb)
owner to leoclean_kb_stage_owner;
revoke create on schema kb_stage from leoclean_kb_stage_owner;
-- Normalize explicit ACL entries left by CREATE OR REPLACE or an earlier grant.
revoke all on function kb_stage.stage_leoclean_proposal(text, text, text, text, jsonb)
from public;
select format(
'revoke all on function kb_stage.stage_leoclean_proposal(text, text, text, text, jsonb) from %I',
grantee.rolname
)
from pg_catalog.pg_proc function_row
cross join lateral pg_catalog.aclexplode(
coalesce(
function_row.proacl,
pg_catalog.acldefault('f', function_row.proowner)
)
) as acl
join pg_catalog.pg_roles grantee on grantee.oid = acl.grantee
where function_row.oid = 'kb_stage.stage_leoclean_proposal(text,text,text,text,jsonb)'::pg_catalog.regprocedure
and grantee.rolname <> 'leoclean_kb_stage_owner'
\gexec
grant execute on function kb_stage.stage_leoclean_proposal(text, text, text, text, jsonb)
to leoclean_kb_runtime;
select format('revoke leoclean_kb_stage_owner from %I', current_user)
where current_user <> 'leoclean_kb_stage_owner'
\gexec
do $verification$
declare
unexpected text;
runtime_oid oid := 'leoclean_kb_runtime'::pg_catalog.regrole::oid;
owner_oid oid := 'leoclean_kb_stage_owner'::pg_catalog.regrole::oid;
stage_function oid := 'kb_stage.stage_leoclean_proposal(text,text,text,text,jsonb)'::pg_catalog.regprocedure::oid;
begin
if exists (
select 1
from pg_catalog.pg_roles role_row
where role_row.rolname = 'leoclean_kb_runtime'
and (not role_row.rolcanlogin
or role_row.rolsuper
or role_row.rolcreatedb
or role_row.rolcreaterole
or role_row.rolinherit
or role_row.rolreplication
or role_row.rolbypassrls
or role_row.rolconnlimit <> 8)
) then
raise exception 'leoclean_kb_runtime has unsafe role attributes';
end if;
if exists (
select 1
from pg_catalog.pg_roles role_row
where role_row.rolname = 'leoclean_kb_stage_owner'
and (role_row.rolcanlogin
or role_row.rolsuper
or role_row.rolcreatedb
or role_row.rolcreaterole
or role_row.rolinherit
or role_row.rolreplication
or role_row.rolbypassrls
or role_row.rolconnlimit <> -1)
) then
raise exception 'leoclean_kb_stage_owner has unsafe role attributes';
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I->%I', granted_role.rolname, member_role.rolname),
', '
order by granted_role.rolname, member_role.rolname
)
into unexpected
from pg_catalog.pg_auth_members membership
join pg_catalog.pg_roles granted_role on granted_role.oid = membership.roleid
join pg_catalog.pg_roles member_role on member_role.oid = membership.member
where membership.roleid in (runtime_oid, owner_oid)
or membership.member in (runtime_oid, owner_oid);
if unexpected is not null then
raise exception 'scoped Leo roles retain role memberships: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format(
'%I:%s:%s:grantable=%s',
database_row.datname,
grantee.rolname,
acl.privilege_type,
acl.is_grantable
),
', '
order by database_row.datname, grantee.rolname, acl.privilege_type
)
into unexpected
from pg_catalog.pg_database database_row
cross join lateral pg_catalog.aclexplode(
coalesce(
database_row.datacl,
pg_catalog.acldefault('d', database_row.datdba)
)
) as acl
join pg_catalog.pg_roles grantee on grantee.oid = acl.grantee
where database_row.datname in ('teleo_canonical', 'teleo_kb')
and grantee.rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
and not (
database_row.datname = 'teleo_canonical'
and grantee.rolname = 'leoclean_kb_runtime'
and acl.privilege_type = 'CONNECT'
and not acl.is_grantable
);
if unexpected is not null then
raise exception 'scoped Leo roles retain unexpected direct database ACLs: %', unexpected;
end if;
if has_database_privilege('leoclean_kb_runtime', 'teleo_canonical', 'CREATE')
or has_database_privilege('leoclean_kb_runtime', 'teleo_kb', 'CREATE')
or has_database_privilege('leoclean_kb_stage_owner', 'teleo_canonical', 'CREATE')
or has_database_privilege('leoclean_kb_stage_owner', 'teleo_kb', 'CREATE') then
raise exception 'a scoped Leo role unexpectedly has database CREATE';
end if;
select pg_catalog.string_agg(
pg_catalog.format('%s:%s:%s', dependency.classid::pg_catalog.regclass, dependency.objid, dependency.objsubid),
', '
)
into unexpected
from pg_catalog.pg_shdepend dependency
where dependency.refclassid = 'pg_catalog.pg_authid'::pg_catalog.regclass
and dependency.refobjid = runtime_oid
and dependency.deptype = 'o';
if unexpected is not null then
raise exception 'leoclean_kb_runtime unexpectedly owns database objects: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%s:%s:%s', dependency.classid::pg_catalog.regclass, dependency.objid, dependency.objsubid),
', '
)
into unexpected
from pg_catalog.pg_shdepend dependency
where dependency.refclassid = 'pg_catalog.pg_authid'::pg_catalog.regclass
and dependency.refobjid = owner_oid
and dependency.deptype = 'o'
and not (
dependency.dbid = (select database_row.oid from pg_catalog.pg_database database_row where database_row.datname = current_database())
and
dependency.classid = 'pg_catalog.pg_proc'::pg_catalog.regclass
and dependency.objid = stage_function
and dependency.objsubid = 0
);
if unexpected is not null then
raise exception 'leoclean_kb_stage_owner owns unexpected database objects: %', unexpected;
end if;
if has_schema_privilege('leoclean_kb_runtime', 'public', 'CREATE')
or has_schema_privilege('leoclean_kb_runtime', 'kb_stage', 'CREATE')
or has_schema_privilege('leoclean_kb_stage_owner', 'public', 'CREATE')
or has_schema_privilege('leoclean_kb_stage_owner', 'kb_stage', 'CREATE') then
raise exception 'a scoped Leo role unexpectedly has schema CREATE';
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I:%s', namespace.nspname, relation.relname, privilege.name),
', '
order by namespace.nspname, relation.relname, privilege.name
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
cross join (values ('INSERT'), ('UPDATE'), ('DELETE'), ('TRUNCATE'), ('REFERENCES'), ('TRIGGER')) privilege(name)
where namespace.nspname in ('public', 'kb_stage')
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and has_table_privilege('leoclean_kb_runtime', relation.oid, privilege.name);
if unexpected is not null then
raise exception 'leoclean_kb_runtime unexpectedly has table-level DML: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I.%I:%s', namespace.nspname, relation.relname, attribute.attname, privilege.name),
', '
order by namespace.nspname, relation.relname, attribute.attnum, privilege.name
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
join pg_catalog.pg_attribute attribute on attribute.attrelid = relation.oid
cross join (values ('INSERT'), ('UPDATE'), ('REFERENCES')) privilege(name)
where namespace.nspname in ('public', 'kb_stage')
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and attribute.attnum > 0
and not attribute.attisdropped
and has_column_privilege('leoclean_kb_runtime', relation.oid, attribute.attnum, privilege.name);
if unexpected is not null then
raise exception 'leoclean_kb_runtime unexpectedly has column-level DML: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I', namespace.nspname, relation.relname),
', '
order by namespace.nspname, relation.relname
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
where namespace.nspname in ('public', 'kb_stage')
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and has_table_privilege('leoclean_kb_runtime', relation.oid, 'SELECT')
and (namespace.nspname, relation.relname) not in (
('public', 'agents'),
('public', 'claims'),
('public', 'claim_evidence'),
('public', 'claim_edges'),
('public', 'sources'),
('public', 'personas'),
('public', 'strategies'),
('public', 'beliefs'),
('public', 'blindspots'),
('public', 'agent_roles'),
('public', 'peer_models'),
('public', 'behavioral_rules'),
('public', 'contributor_rules'),
('public', 'reasoning_tools'),
('public', 'governance_gates'),
('kb_stage', 'kb_proposals')
);
if unexpected is not null then
raise exception 'leoclean_kb_runtime can SELECT outside its table allowlist: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I.%I', namespace.nspname, relation.relname, attribute.attname),
', '
order by namespace.nspname, relation.relname, attribute.attnum
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
join pg_catalog.pg_attribute attribute on attribute.attrelid = relation.oid
where namespace.nspname in ('public', 'kb_stage')
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and attribute.attnum > 0
and not attribute.attisdropped
and has_column_privilege('leoclean_kb_runtime', relation.oid, attribute.attnum, 'SELECT')
and (namespace.nspname, relation.relname) not in (
('public', 'agents'),
('public', 'claims'),
('public', 'claim_evidence'),
('public', 'claim_edges'),
('public', 'sources'),
('public', 'personas'),
('public', 'strategies'),
('public', 'beliefs'),
('public', 'blindspots'),
('public', 'agent_roles'),
('public', 'peer_models'),
('public', 'behavioral_rules'),
('public', 'contributor_rules'),
('public', 'reasoning_tools'),
('public', 'governance_gates'),
('kb_stage', 'kb_proposals')
);
if unexpected is not null then
raise exception 'leoclean_kb_runtime has column SELECT outside its allowlist: %', unexpected;
end if;
if has_table_privilege('leoclean_kb_stage_owner', 'public.agents', 'SELECT')
or not has_column_privilege('leoclean_kb_stage_owner', 'public.agents', 'id', 'SELECT')
or not has_column_privilege('leoclean_kb_stage_owner', 'public.agents', 'handle', 'SELECT')
or not has_table_privilege('leoclean_kb_stage_owner', 'kb_stage.kb_proposals', 'SELECT')
or has_table_privilege('leoclean_kb_stage_owner', 'kb_stage.kb_proposals', 'INSERT') then
raise exception 'leoclean_kb_stage_owner does not have the exact read/insert grant shape';
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I.%I', namespace.nspname, relation.relname, attribute.attname),
', '
order by namespace.nspname, relation.relname, attribute.attnum
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
join pg_catalog.pg_attribute attribute on attribute.attrelid = relation.oid
where namespace.nspname in ('public', 'kb_stage')
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and attribute.attnum > 0
and not attribute.attisdropped
and has_column_privilege('leoclean_kb_stage_owner', relation.oid, attribute.attnum, 'INSERT')
and not (
namespace.nspname = 'kb_stage'
and relation.relname = 'kb_proposals'
and attribute.attname in (
'proposal_type',
'status',
'proposed_by_handle',
'proposed_by_agent_id',
'channel',
'source_ref',
'rationale',
'payload'
)
);
if unexpected is not null then
raise exception 'leoclean_kb_stage_owner can INSERT unexpected columns: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I:%s', namespace.nspname, relation.relname, privilege.name),
', '
order by namespace.nspname, relation.relname, privilege.name
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
cross join (values ('INSERT'), ('UPDATE'), ('DELETE'), ('TRUNCATE'), ('REFERENCES'), ('TRIGGER')) privilege(name)
where namespace.nspname in ('public', 'kb_stage')
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and has_table_privilege('leoclean_kb_stage_owner', relation.oid, privilege.name);
if unexpected is not null then
raise exception 'leoclean_kb_stage_owner unexpectedly has table-level DML: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I.%I:%s', namespace.nspname, relation.relname, attribute.attname, privilege.name),
', '
order by namespace.nspname, relation.relname, attribute.attnum, privilege.name
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
join pg_catalog.pg_attribute attribute on attribute.attrelid = relation.oid
cross join (values ('UPDATE'), ('REFERENCES')) privilege(name)
where namespace.nspname in ('public', 'kb_stage')
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and attribute.attnum > 0
and not attribute.attisdropped
and has_column_privilege('leoclean_kb_stage_owner', relation.oid, attribute.attnum, privilege.name);
if unexpected is not null then
raise exception 'leoclean_kb_stage_owner unexpectedly has column-level DML: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I.%I', namespace.nspname, relation.relname, attribute.attname),
', '
order by namespace.nspname, relation.relname, attribute.attnum
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
join pg_catalog.pg_attribute attribute on attribute.attrelid = relation.oid
where namespace.nspname in ('public', 'kb_stage')
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and attribute.attnum > 0
and not attribute.attisdropped
and has_column_privilege('leoclean_kb_stage_owner', relation.oid, attribute.attnum, 'SELECT')
and not (
(namespace.nspname = 'kb_stage' and relation.relname = 'kb_proposals')
or (
namespace.nspname = 'public'
and relation.relname = 'agents'
and attribute.attname in ('id', 'handle')
)
);
if unexpected is not null then
raise exception 'leoclean_kb_stage_owner can SELECT unexpected columns: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I:%s:%s', namespace.nspname, relation.relname, scoped_role.name, privilege.name),
', '
order by namespace.nspname, relation.relname, scoped_role.name, privilege.name
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
cross join (values ('leoclean_kb_runtime'), ('leoclean_kb_stage_owner')) scoped_role(name)
cross join (values ('USAGE'), ('SELECT'), ('UPDATE')) privilege(name)
where namespace.nspname in ('public', 'kb_stage')
and relation.relkind = 'S'
and has_sequence_privilege(scoped_role.name, relation.oid, privilege.name);
if unexpected is not null then
raise exception 'a scoped Leo role unexpectedly has sequence privileges: %', unexpected;
end if;
if exists (
select 1
from pg_catalog.pg_attribute attribute
where attribute.attrelid = 'kb_stage.kb_proposals'::pg_catalog.regclass
and attribute.attname in (
'proposal_type',
'status',
'proposed_by_handle',
'proposed_by_agent_id',
'channel',
'source_ref',
'rationale',
'payload'
)
and not has_column_privilege(
'leoclean_kb_stage_owner',
attribute.attrelid,
attribute.attnum,
'INSERT'
)
) then
raise exception 'leoclean_kb_stage_owner is missing a required INSERT column grant';
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I(%s)', namespace.nspname, function_row.proname, pg_catalog.pg_get_function_identity_arguments(function_row.oid)),
', '
)
into unexpected
from pg_catalog.pg_proc function_row
join pg_catalog.pg_namespace namespace on namespace.oid = function_row.pronamespace
where namespace.nspname in ('public', 'kb_stage')
and function_row.prosecdef
and function_row.oid <> stage_function
and has_function_privilege('leoclean_kb_runtime', function_row.oid, 'EXECUTE');
if unexpected is not null then
raise exception 'leoclean_kb_runtime can execute unexpected SECURITY DEFINER functions: %', unexpected;
end if;
if not exists (
select 1
from pg_catalog.pg_proc function_row
where function_row.oid = stage_function
and function_row.prosecdef
and function_row.proowner = owner_oid
and function_row.proconfig = array['search_path=pg_catalog, pg_temp']::text[]
) then
raise exception 'stage_leoclean_proposal owner/security/search_path contract is invalid';
end if;
select pg_catalog.string_agg(
pg_catalog.format(
'grantee=%s privilege=%s grantable=%s',
acl.grantee,
acl.privilege_type,
acl.is_grantable
),
', '
)
into unexpected
from pg_catalog.pg_proc function_row
cross join lateral pg_catalog.aclexplode(
coalesce(
function_row.proacl,
pg_catalog.acldefault('f', function_row.proowner)
)
) as acl
where function_row.oid = stage_function
and not (
acl.grantee in (runtime_oid, owner_oid)
and acl.privilege_type = 'EXECUTE'
and (acl.grantee = owner_oid or not acl.is_grantable)
);
if unexpected is not null then
raise exception 'stage_leoclean_proposal has unexpected ACL entries: %', unexpected;
end if;
if not has_function_privilege(
'leoclean_kb_runtime',
'kb_stage.stage_leoclean_proposal(text,text,text,text,jsonb)',
'EXECUTE'
) then
raise exception 'leoclean_kb_runtime cannot execute stage_leoclean_proposal';
end if;
end
$verification$;
commit;
-- The legacy restore database is not an audit fallback for the scoped runtime.
-- Strip direct ACLs from both scoped roles and fail if PUBLIC/group grants still
-- make any teleo_restore relation reachable.
\connect teleo_kb
begin;
select format('revoke all on schema %I from leoclean_kb_runtime, leoclean_kb_stage_owner', namespace.nspname)
from pg_catalog.pg_namespace namespace
where namespace.nspname = 'teleo_restore'
\gexec
select format('revoke all privileges on all tables in schema %I from leoclean_kb_runtime, leoclean_kb_stage_owner', namespace.nspname)
from pg_catalog.pg_namespace namespace
where namespace.nspname = 'teleo_restore'
\gexec
select format('revoke all privileges on all sequences in schema %I from leoclean_kb_runtime, leoclean_kb_stage_owner', namespace.nspname)
from pg_catalog.pg_namespace namespace
where namespace.nspname = 'teleo_restore'
\gexec
select format('revoke all privileges on all functions in schema %I from leoclean_kb_runtime, leoclean_kb_stage_owner', namespace.nspname)
from pg_catalog.pg_namespace namespace
where namespace.nspname = 'teleo_restore'
\gexec
select format('revoke all privileges on all procedures in schema %I from leoclean_kb_runtime, leoclean_kb_stage_owner', namespace.nspname)
from pg_catalog.pg_namespace namespace
where namespace.nspname = 'teleo_restore'
\gexec
select format(
'revoke all privileges (%s) on table %I.%I from %I',
pg_catalog.string_agg(pg_catalog.quote_ident(attribute.attname), ', ' order by attribute.attnum),
namespace.nspname,
relation.relname,
target.rolname
)
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
join pg_catalog.pg_attribute attribute on attribute.attrelid = relation.oid
cross join (
values ('leoclean_kb_runtime'), ('leoclean_kb_stage_owner')
) as target(rolname)
where namespace.nspname = 'teleo_restore'
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and attribute.attnum > 0
and not attribute.attisdropped
group by namespace.nspname, relation.relname, target.rolname
\gexec
do $audit_verification$
declare
unexpected text;
scoped_role text;
begin
foreach scoped_role in array array['leoclean_kb_runtime', 'leoclean_kb_stage_owner'] loop
if to_regnamespace('teleo_restore') is not null
and (has_schema_privilege(scoped_role, 'teleo_restore', 'USAGE')
or has_schema_privilege(scoped_role, 'teleo_restore', 'CREATE')) then
raise exception '% unexpectedly has teleo_restore schema access', scoped_role;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I:%s', namespace.nspname, relation.relname, privilege.name),
', '
order by relation.relname, privilege.name
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
cross join (
values ('SELECT'), ('INSERT'), ('UPDATE'), ('DELETE'), ('TRUNCATE'), ('REFERENCES'), ('TRIGGER')
) privilege(name)
where namespace.nspname = 'teleo_restore'
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and has_table_privilege(scoped_role, relation.oid, privilege.name);
if unexpected is not null then
raise exception '% unexpectedly has teleo_restore table access: %', scoped_role, unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I.%I:%s', namespace.nspname, relation.relname, attribute.attname, privilege.name),
', '
order by relation.relname, attribute.attnum, privilege.name
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
join pg_catalog.pg_attribute attribute on attribute.attrelid = relation.oid
cross join (values ('SELECT'), ('INSERT'), ('UPDATE'), ('REFERENCES')) privilege(name)
where namespace.nspname = 'teleo_restore'
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and attribute.attnum > 0
and not attribute.attisdropped
and has_column_privilege(scoped_role, relation.oid, attribute.attnum, privilege.name);
if unexpected is not null then
raise exception '% unexpectedly has teleo_restore column access: %', scoped_role, unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I:%s', namespace.nspname, relation.relname, privilege.name),
', '
order by relation.relname, privilege.name
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
cross join (values ('USAGE'), ('SELECT'), ('UPDATE')) privilege(name)
where namespace.nspname = 'teleo_restore'
and relation.relkind = 'S'
and has_sequence_privilege(scoped_role, relation.oid, privilege.name);
if unexpected is not null then
raise exception '% unexpectedly has teleo_restore sequence access: %', scoped_role, unexpected;
end if;
end loop;
end
$audit_verification$;
commit;
\connect teleo_canonical
select jsonb_build_object(
'role', rolname,
'superuser', rolsuper,
'create_db', rolcreatedb,
'create_role', rolcreaterole,
'inherit', rolinherit,
'replication', rolreplication,
'bypass_rls', rolbypassrls,
'has_role_memberships', exists (
select 1
from pg_catalog.pg_auth_members membership
where membership.roleid = pg_catalog.pg_roles.oid
or membership.member = pg_catalog.pg_roles.oid
),
'can_select_claims', has_table_privilege(rolname, 'public.claims', 'SELECT'),
'can_select_proposals', has_table_privilege(rolname, 'kb_stage.kb_proposals', 'SELECT'),
'can_insert_proposals_directly', has_table_privilege(rolname, 'kb_stage.kb_proposals', 'INSERT'),
'can_create_database_objects', has_database_privilege(rolname, 'teleo_canonical', 'CREATE'),
'can_create_temp_objects', has_database_privilege(rolname, 'teleo_canonical', 'TEMP'),
'can_stage_proposal', has_function_privilege(
rolname,
'kb_stage.stage_leoclean_proposal(text,text,text,text,jsonb)',
'EXECUTE'
)
)::text
from pg_catalog.pg_roles
where rolname = 'leoclean_kb_runtime';

View file

@ -47,7 +47,7 @@ DEFAULT_HOST = "10.61.0.3"
DEFAULT_PROJECT = "teleo-501523" DEFAULT_PROJECT = "teleo-501523"
DEFAULT_SECRET = "gcp-teleo-pgvector-standby-postgres-password" DEFAULT_SECRET = "gcp-teleo-pgvector-standby-postgres-password"
DEFAULT_MANIFEST_SQL = HERE.parent / "ops" / "postgres_parity_manifest.sql" DEFAULT_MANIFEST_SQL = HERE.parent / "ops" / "postgres_parity_manifest.sql"
REVIEWED_CLOUDSQL_TOOL_SHA256 = "7d2074a0fc5f0d48fbf8f7905a72ead8f2696c86a041fa43e078fff5500baa51" REVIEWED_CLOUDSQL_TOOL_SHA256 = "2617bb501456758131ff41a6c77b3c462e21c28c4348da319b60defb051c7c87"
REVIEWED_MANIFEST_SQL_SHA256 = "8b8cdc25d54fdd8de05eb38c6e4423d2836953eb6012d4545f5c9c71b5f0150a" REVIEWED_MANIFEST_SQL_SHA256 = "8b8cdc25d54fdd8de05eb38c6e4423d2836953eb6012d4545f5c9c71b5f0150a"
COUNT_READBACK_RE = re.compile( COUNT_READBACK_RE = re.compile(
r"DB readback:\s*claims:\s*`?(?P<claims>\d+)`?;\s*" r"DB readback:\s*claims:\s*`?(?P<claims>\d+)`?;\s*"
@ -416,6 +416,8 @@ $PYTHON "$CLOUDSQL_TOOL" \
--db "$TARGET_DATABASE" \ --db "$TARGET_DATABASE" \
--canonical-db "$TARGET_DATABASE" \ --canonical-db "$TARGET_DATABASE" \
--project "$PROJECT" \ --project "$PROJECT" \
--credential-mode clone-readonly \
--user postgres \
--password-secret "$PASSWORD_SECRET" \ --password-secret "$PASSWORD_SECRET" \
"$@" "$@"
STATUS=$? STATUS=$?

View file

@ -0,0 +1,14 @@
[Service]
UnsetEnvironment=PGPASSWORD
RuntimeDirectory=leoclean-gcp-prod-parallel
RuntimeDirectoryMode=0700
Environment=CLOUDSDK_CONFIG=/run/leoclean-gcp-prod-parallel
Environment=TELEO_KB_MODE=cloudsql
Environment=TELEO_GCP_PROJECT=teleo-501523
Environment=TELEO_CLOUDSQL_HOST=10.61.0.3
Environment=TELEO_CLOUDSQL_PORT=5432
Environment=TELEO_CLOUDSQL_DB=teleo_canonical
Environment=TELEO_CANONICAL_CLOUDSQL_DB=teleo_canonical
Environment=TELEO_CLOUDSQL_USER=leoclean_kb_runtime
Environment=TELEO_CLOUDSQL_PASSWORD_SECRET=gcp-teleo-pgvector-standby-leoclean-kb-runtime-password
Environment=TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK=0

View file

@ -115,6 +115,8 @@ def test_wrapper_binds_private_read_only_database_and_records_calls(tmp_path: Pa
assert "dbname=$TARGET_DATABASE" in wrapper assert "dbname=$TARGET_DATABASE" in wrapper
assert '--db "$TARGET_DATABASE"' in wrapper assert '--db "$TARGET_DATABASE"' in wrapper
assert '--canonical-db "$TARGET_DATABASE"' in wrapper assert '--canonical-db "$TARGET_DATABASE"' in wrapper
assert "--credential-mode clone-readonly" in wrapper
assert "--user postgres" in wrapper
assert "sslmode=require" in wrapper assert "sslmode=require" in wrapper
assert "begin transaction read only" in wrapper assert "begin transaction read only" in wrapper
assert 'export PGOPTIONS="-c default_transaction_read_only=on"' in wrapper assert 'export PGOPTIONS="-c default_transaction_read_only=on"' in wrapper
@ -306,6 +308,8 @@ def test_generated_wrapper_executes_only_clone_bound_default_read_only_tool(tmp_
"--canonical-db", "--canonical-db",
"teleo_clone_test", "teleo_clone_test",
] ]
assert tool_receipt["argv"][tool_receipt["argv"].index("--credential-mode") + 1] == "clone-readonly"
assert tool_receipt["argv"][tool_receipt["argv"].index("--user") + 1] == "postgres"
events = [json.loads(line) for line in log.read_text().splitlines()] events = [json.loads(line) for line in log.read_text().splitlines()]
assert [event["phase"] for event in events] == ["start", "end"] assert [event["phase"] for event in events] == ["start", "end"]
assert events[0]["database_identity"]["default_transaction_read_only"] == "on" assert events[0]["database_identity"]["default_transaction_read_only"] == "on"

View file

@ -9,6 +9,7 @@ import io
import json import json
import os import os
import re import re
import shutil
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from types import SimpleNamespace from types import SimpleNamespace
@ -23,6 +24,7 @@ from scripts.working_leo_open_ended_benchmark import (
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
BRIDGE_DIR = ROOT / "hermes-agent" / "leoclean-bin" BRIDGE_DIR = ROOT / "hermes-agent" / "leoclean-bin"
DB_CONTEXT_PLUGIN = ROOT / "hermes-agent" / "leoclean-plugins" / "vps" / "leo-db-context" / "__init__.py" DB_CONTEXT_PLUGIN = ROOT / "hermes-agent" / "leoclean-plugins" / "vps" / "leo-db-context" / "__init__.py"
GCP_RUNTIME_ROLE_SQL = ROOT / "ops" / "gcp_leoclean_runtime_role.sql"
def test_leoclean_bridge_files_are_present_and_parseable() -> None: def test_leoclean_bridge_files_are_present_and_parseable() -> None:
@ -67,7 +69,7 @@ def test_cloudsql_wrapper_supports_expected_review_gated_commands() -> None:
assert "canonical apply" in cloudsql_text.lower() assert "canonical apply" in cloudsql_text.lower()
def _install_wrapper_fixture(tmp_path: Path) -> tuple[Path, dict[str, str]]: def _install_wrapper_fixture(tmp_path: Path, *, missing: frozenset[str] = frozenset()) -> tuple[Path, dict[str, str]]:
profile = tmp_path / "profile" profile = tmp_path / "profile"
bin_dir = profile / "bin" bin_dir = profile / "bin"
bin_dir.mkdir(parents=True) bin_dir.mkdir(parents=True)
@ -75,11 +77,12 @@ def _install_wrapper_fixture(tmp_path: Path) -> tuple[Path, dict[str, str]]:
wrapper.write_bytes((BRIDGE_DIR / "teleo-kb").read_bytes()) wrapper.write_bytes((BRIDGE_DIR / "teleo-kb").read_bytes())
wrapper.chmod(0o700) wrapper.chmod(0o700)
cloudsql = bin_dir / "cloudsql_memory_tool.py" cloudsql = bin_dir / "cloudsql_memory_tool.py"
cloudsql.write_text( if "cloudsql-tool" not in missing:
"import json, sys\nprint(json.dumps({'backend': 'cloudsql', 'argv': sys.argv[1:]}))\n", cloudsql.write_text(
encoding="utf-8", "import json, sys\nprint(json.dumps({'backend': 'cloudsql', 'argv': sys.argv[1:]}))\n",
) encoding="utf-8",
cloudsql.chmod(0o700) )
cloudsql.chmod(0o700)
local = bin_dir / "kb_tool.py" local = bin_dir / "kb_tool.py"
local.write_text( local.write_text(
"import json, sys\nprint(json.dumps({'backend': 'local', 'argv': sys.argv[1:]}))\n", "import json, sys\nprint(json.dumps({'backend': 'local', 'argv': sys.argv[1:]}))\n",
@ -88,7 +91,13 @@ def _install_wrapper_fixture(tmp_path: Path) -> tuple[Path, dict[str, str]]:
local.chmod(0o700) local.chmod(0o700)
fake_bin = tmp_path / "fake-bin" fake_bin = tmp_path / "fake-bin"
fake_bin.mkdir() fake_bin.mkdir()
for name in ("bash", "python3"):
target = shutil.which(name)
assert target
(fake_bin / name).symlink_to(target)
for name in ("psql", "gcloud"): for name in ("psql", "gcloud"):
if name in missing:
continue
executable = fake_bin / name executable = fake_bin / name
executable.write_text("#!/usr/bin/env bash\nexit 0\n", encoding="utf-8") executable.write_text("#!/usr/bin/env bash\nexit 0\n", encoding="utf-8")
executable.chmod(0o700) executable.chmod(0o700)
@ -96,7 +105,7 @@ def _install_wrapper_fixture(tmp_path: Path) -> tuple[Path, dict[str, str]]:
**os.environ, **os.environ,
"HERMES_HOME": str(profile), "HERMES_HOME": str(profile),
"TELEO_KB_MODE": "auto", "TELEO_KB_MODE": "auto",
"PATH": f"{fake_bin}:{os.environ['PATH']}", "PATH": str(fake_bin),
} }
return wrapper, env return wrapper, env
@ -137,6 +146,40 @@ def test_cloudsql_wrapper_auto_mode_never_falls_back_after_supported_command_fai
assert "local" not in completed.stdout assert "local" not in completed.stdout
@pytest.mark.parametrize("missing", ["cloudsql-tool", "psql", "gcloud"])
def test_cloudsql_wrapper_auto_mode_fails_closed_when_prerequisite_is_missing(tmp_path: Path, missing: str) -> None:
wrapper, env = _install_wrapper_fixture(tmp_path, missing=frozenset({missing}))
completed = subprocess.run(
[str(wrapper), "search", "canonical only", "--format", "json"],
text=True,
capture_output=True,
check=False,
env=env,
)
assert completed.returncode == 69
assert "local" not in completed.stdout
assert "Cloud SQL" in completed.stderr
def test_cloudsql_wrapper_explicit_mode_rejects_unsupported_commands_without_fallback(tmp_path: Path) -> None:
wrapper, env = _install_wrapper_fixture(tmp_path)
env["TELEO_KB_MODE"] = "cloudsql"
completed = subprocess.run(
[str(wrapper), "prepare-source", "example.md"],
text=True,
capture_output=True,
check=False,
env=env,
)
assert completed.returncode == 64
assert "local" not in completed.stdout
assert "not supported by the Cloud SQL backend" in completed.stderr
def test_bridge_edge_proposals_stage_strict_apply_payloads() -> None: def test_bridge_edge_proposals_stage_strict_apply_payloads() -> None:
cloudsql_text = (BRIDGE_DIR / "cloudsql_memory_tool.py").read_text() cloudsql_text = (BRIDGE_DIR / "cloudsql_memory_tool.py").read_text()
vps_text = (BRIDGE_DIR / "kb_tool.py").read_text() vps_text = (BRIDGE_DIR / "kb_tool.py").read_text()
@ -148,6 +191,9 @@ def test_bridge_edge_proposals_stage_strict_apply_payloads() -> None:
assert "'to_claim', to_row.id::text" in text assert "'to_claim', to_row.id::text" in text
assert "'edge_type', p.edge_type::text" in text assert "'edge_type', p.edge_type::text" in text
assert "kb_stage.stage_leoclean_proposal(" in cloudsql_text
assert "insert into kb_stage.kb_proposals" not in cloudsql_text.lower()
def test_bridge_source_does_not_commit_raw_secret_values() -> None: def test_bridge_source_does_not_commit_raw_secret_values() -> None:
combined = "\n".join(path.read_text(errors="replace") for path in BRIDGE_DIR.iterdir() if path.is_file()) combined = "\n".join(path.read_text(errors="replace") for path in BRIDGE_DIR.iterdir() if path.is_file())
@ -163,6 +209,206 @@ def test_bridge_source_does_not_commit_raw_secret_values() -> None:
assert not re.search(pattern, combined), pattern assert not re.search(pattern, combined), pattern
def test_cloudsql_runtime_requires_the_exact_scoped_identity(monkeypatch: pytest.MonkeyPatch) -> None:
module = _load_module(BRIDGE_DIR / "cloudsql_memory_tool.py")
monkeypatch.delenv("PGPASSWORD", raising=False)
with pytest.raises(SystemExit, match="requires database user leoclean_kb_runtime"):
module.validate_runtime_connection(SimpleNamespace(credential_mode="runtime", user=None, password_secret=None))
with pytest.raises(SystemExit, match="requires database user leoclean_kb_runtime"):
module.validate_runtime_connection(
SimpleNamespace(
credential_mode="runtime",
user="postgres",
password_secret=module.RUNTIME_PASSWORD_SECRET,
)
)
with pytest.raises(SystemExit, match="requires database user leoclean_kb_runtime"):
module.validate_runtime_connection(
SimpleNamespace(
credential_mode="runtime",
user=f" {module.RUNTIME_DB_USER}",
password_secret=module.RUNTIME_PASSWORD_SECRET,
)
)
with pytest.raises(SystemExit, match="requires scoped password secret"):
module.validate_runtime_connection(
SimpleNamespace(
credential_mode="runtime",
user=module.RUNTIME_DB_USER,
password_secret="gcp-teleo-pgvector-standby-postgres-password",
)
)
with pytest.raises(SystemExit, match="requires scoped password secret"):
module.validate_runtime_connection(
SimpleNamespace(credential_mode="runtime", user=module.RUNTIME_DB_USER, password_secret=None)
)
def test_cloudsql_runtime_accepts_only_scoped_credentials_and_rejects_inherited_password(
monkeypatch: pytest.MonkeyPatch,
) -> None:
module = _load_module(BRIDGE_DIR / "cloudsql_memory_tool.py")
args = SimpleNamespace(
credential_mode="runtime",
user=module.RUNTIME_DB_USER,
password_secret=module.RUNTIME_PASSWORD_SECRET,
)
monkeypatch.delenv("PGPASSWORD", raising=False)
module.validate_runtime_connection(args)
monkeypatch.setenv("PGPASSWORD", "")
with pytest.raises(SystemExit, match="Refusing inherited PGPASSWORD"):
module.validate_runtime_connection(args)
def test_cloudsql_clone_mode_is_read_only_and_bound_to_one_disposable_database(
monkeypatch: pytest.MonkeyPatch,
) -> None:
module = _load_module(BRIDGE_DIR / "cloudsql_memory_tool.py")
args = SimpleNamespace(
credential_mode="clone-readonly",
user="postgres",
password_secret=None,
canonical_db="teleo_clone_canary_123",
db="teleo_clone_canary_123",
command="status",
)
monkeypatch.setenv("PGPASSWORD", "clone-only-password")
monkeypatch.setenv("PGOPTIONS", module.CLONE_READ_ONLY_PGOPTIONS)
module.validate_runtime_connection(args)
assert module.password(args) == "clone-only-password"
args.command = "propose-core-change"
with pytest.raises(SystemExit, match="read commands only"):
module.validate_runtime_connection(args)
args.command = "status"
args.canonical_db = "teleo_canonical"
with pytest.raises(SystemExit, match=r"teleo_clone_\*"):
module.validate_runtime_connection(args)
def test_cloudsql_psql_uses_only_explicit_pg_environment_fields(monkeypatch: pytest.MonkeyPatch) -> None:
module = _load_module(BRIDGE_DIR / "cloudsql_memory_tool.py")
args = SimpleNamespace(
credential_mode="runtime",
user=module.RUNTIME_DB_USER,
password_secret=module.RUNTIME_PASSWORD_SECRET,
host="10.61.0.3",
port="5432",
db="teleo_canonical",
canonical_db="teleo_canonical",
project="teleo-501523",
command="status",
)
monkeypatch.delenv("PGPASSWORD", raising=False)
monkeypatch.setenv("PGHOST", "attacker.invalid")
monkeypatch.setenv("PGSERVICE", "untrusted-service")
monkeypatch.setenv("PGPASSFILE", "/tmp/untrusted-pgpass")
monkeypatch.setenv("PGOPTIONS", "-c search_path=attacker")
captured: dict[str, object] = {}
def fake_run(command, **kwargs):
if command[0] == "gcloud":
return SimpleNamespace(returncode=0, stdout="scoped-runtime-password\n", stderr="")
captured["command"] = command
captured["kwargs"] = kwargs
return SimpleNamespace(returncode=0, stdout="ok\n", stderr="")
monkeypatch.setattr(module.subprocess, "run", fake_run)
assert module.run_psql(args, "select 1;") == "ok\n"
assert captured["command"] == ["psql", "-X", "-At", "-q", "-v", "ON_ERROR_STOP=1"]
kwargs = captured["kwargs"]
assert isinstance(kwargs, dict)
env = kwargs["env"]
assert env["PGHOST"] == "10.61.0.3"
assert env["PGPORT"] == "5432"
assert env["PGDATABASE"] == "teleo_canonical"
assert env["PGUSER"] == module.RUNTIME_DB_USER
assert env["PGSSLMODE"] == "require"
assert env["PGCONNECT_TIMEOUT"] == "8"
assert env["PGPASSWORD"] == "scoped-runtime-password"
assert "PGOPTIONS" not in env
assert "PGSERVICE" not in env
assert "PGPASSFILE" not in env
def test_cloudsql_proposal_calls_leave_leo_identity_to_the_database(monkeypatch: pytest.MonkeyPatch) -> None:
module = _load_module(BRIDGE_DIR / "cloudsql_memory_tool.py")
captured: list[str] = []
def fake_psql_json_lines(_args, sql, db=None):
captured.append(sql)
return [{"id": "proposal-id", "database": db}]
monkeypatch.setattr(module, "psql_json_lines", fake_psql_json_lines)
core_args = SimpleNamespace(
proposal_type="revise_claim",
target_kind="claim",
target_ref="claim-id",
current="old",
proposed="new",
evidence=[],
implication=[],
originator="",
channel="telegram",
source_ref="telegram:1",
rationale="Grounded correction",
canonical_db="teleo_canonical",
)
edge_args = SimpleNamespace(
from_claim="11111111-1111-4111-8111-111111111111",
to_claim="22222222-2222-4222-8222-222222222222",
edge_type="supports",
channel="telegram",
source_ref="telegram:2",
rationale="Grounded relation",
canonical_db="teleo_canonical",
)
module.propose_core_change(core_args)
module.propose_edge(edge_args)
assert "p.proposed_by_handle" not in captured[0]
assert "as proposed_by_handle" not in captured[0]
assert "p.proposed_by_handle" not in captured[1]
assert "as proposed_by_handle" not in captured[1]
assert "stage_leoclean_proposal(\n p.proposal_type,\n p.channel," in captured[0]
assert "stage_leoclean_proposal(\n 'add_edge',\n p.channel," in captured[1]
def test_gcp_runtime_role_uses_one_narrow_staging_function() -> None:
sql = GCP_RUNTIME_ROLE_SQL.read_text()
assert "security definer" in sql.lower()
assert "stage_leoclean_proposal" in sql
assert "'pending_review'" in sql
assert "revoke all privileges on all tables in schema public, kb_stage" in sql
assert "can_insert_proposals_directly" in sql
assert "can_create_database_objects" in sql
assert "can_create_temp_objects" in sql
assert "unexpectedly has database CREATE" in sql
assert "REVOKE TEMP FROM PUBLIC would be a" in sql
assert "can execute unexpected SECURITY DEFINER functions" in sql
assert "revoke all privileges (%s) on table %I.%I from %I" in sql
assert re.search(
r"grant\s+insert\s*\([^)]*proposal_type[^)]*payload[^)]*\)\s+"
r"on\s+kb_stage\.kb_proposals\s+to\s+leoclean_kb_stage_owner",
sql,
re.IGNORECASE | re.DOTALL,
)
assert not re.search(
r"grant\s+insert(?:\s*\([^)]*\))?\s+on\s+[^;]+\s+to\s+leoclean_kb_runtime",
sql,
re.IGNORECASE | re.DOTALL,
)
assert "grant update" not in sql.lower()
assert "grant delete" not in sql.lower()
def _load_module(path: Path): def _load_module(path: Path):
spec = importlib.util.spec_from_file_location(path.stem, path) spec = importlib.util.spec_from_file_location(path.stem, path)
assert spec and spec.loader assert spec and spec.loader
@ -323,11 +569,16 @@ def test_cloudsql_status_works_without_optional_audit_fallback(monkeypatch) -> N
assert all("teleo_restore.response_audit" not in sql or "to_regclass" in sql for sql, _db in calls) assert all("teleo_restore.response_audit" not in sql or "to_regclass" in sql for sql, _db in calls)
def test_cloudsql_zero_hit_search_stays_canonical_when_audit_is_unavailable(monkeypatch) -> None: def test_cloudsql_zero_hit_search_stays_canonical_when_audit_is_disabled(monkeypatch) -> None:
module = _load_module(BRIDGE_DIR / "cloudsql_memory_tool.py") module = _load_module(BRIDGE_DIR / "cloudsql_memory_tool.py")
canonical = {"artifact": "teleo_cloudsql_canonical_kb_context", "hit_count_total": 0, "hits": []} canonical = {"artifact": "teleo_cloudsql_canonical_kb_context", "hit_count_total": 0, "hits": []}
monkeypatch.setattr(module, "query_canonical_rows", lambda *_args: canonical.copy()) monkeypatch.setattr(module, "query_canonical_rows", lambda *_args: canonical.copy())
monkeypatch.setattr(module, "audit_restore_available", lambda _args: False) monkeypatch.delenv("TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK", raising=False)
monkeypatch.setattr(
module,
"audit_restore_available",
lambda _args: (_ for _ in ()).throw(AssertionError("audit availability must not be probed")),
)
monkeypatch.setattr( monkeypatch.setattr(
module, module,
"query_audit_rows", "query_audit_rows",
@ -337,7 +588,22 @@ def test_cloudsql_zero_hit_search_stays_canonical_when_audit_is_unavailable(monk
result = module.query_rows(SimpleNamespace(), "unseen query", 8) result = module.query_rows(SimpleNamespace(), "unseen query", 8)
assert result["hits"] == [] assert result["hits"] == []
assert "audit fallback unavailable" in result["canonical_fallback_reason"] assert "audit fallback disabled" in result["canonical_fallback_reason"]
def test_cloudsql_zero_hit_search_uses_audit_only_when_explicitly_enabled(monkeypatch) -> None:
module = _load_module(BRIDGE_DIR / "cloudsql_memory_tool.py")
canonical = {"artifact": "teleo_cloudsql_canonical_kb_context", "hit_count_total": 0, "hits": []}
audit = {"artifact": "teleo_cloudsql_memory_context", "hits": [{"row_id": "audit-1"}]}
monkeypatch.setenv("TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK", "1")
monkeypatch.setattr(module, "query_canonical_rows", lambda *_args: canonical.copy())
monkeypatch.setattr(module, "audit_restore_available", lambda _args: True)
monkeypatch.setattr(module, "query_audit_rows", lambda *_args: audit.copy())
result = module.query_rows(SimpleNamespace(), "unseen query", 8)
assert result["hits"] == [{"row_id": "audit-1"}]
assert result["canonical_fallback_reason"] == "no matching canonical public/persona/strategy/belief rows"
def test_vps_bridge_verifies_relative_source_artifact_against_canonical_hash(tmp_path: Path) -> None: def test_vps_bridge_verifies_relative_source_artifact_against_canonical_hash(tmp_path: Path) -> None:

View file

@ -41,7 +41,7 @@ def test_auto_deploy_prefers_github_remote_when_present():
auto_deploy = (REPO_ROOT / "deploy" / "auto-deploy.sh").read_text() auto_deploy = (REPO_ROOT / "deploy" / "auto-deploy.sh").read_text()
assert 'DEPLOY_REMOTE="${TELEO_DEPLOY_REMOTE:-}"' in auto_deploy assert 'DEPLOY_REMOTE="${TELEO_DEPLOY_REMOTE:-}"' in auto_deploy
assert 'remote get-url github' in auto_deploy assert "remote get-url github" in auto_deploy
assert 'git fetch "$DEPLOY_REMOTE" main' in auto_deploy assert 'git fetch "$DEPLOY_REMOTE" main' in auto_deploy
assert 'git rev-parse "$DEPLOY_REMOTE/main"' in auto_deploy assert 'git rev-parse "$DEPLOY_REMOTE/main"' in auto_deploy
assert 'git merge --ff-only "$DEPLOY_REMOTE/main"' in auto_deploy assert 'git merge --ff-only "$DEPLOY_REMOTE/main"' in auto_deploy
@ -50,7 +50,7 @@ def test_auto_deploy_prefers_github_remote_when_present():
def test_auto_deploy_executable_safety_net_stays_inside_synced_dirs(): def test_auto_deploy_executable_safety_net_stays_inside_synced_dirs():
auto_deploy = (REPO_ROOT / "deploy" / "auto-deploy.sh").read_text() auto_deploy = (REPO_ROOT / "deploy" / "auto-deploy.sh").read_text()
assert "for dir in \"$PIPELINE_DIR\" \"$TELEGRAM_DIR\" \"$DIAGNOSTICS_DIR\" \"$AGENT_STATE_DIR\"" in auto_deploy assert 'for dir in "$PIPELINE_DIR" "$TELEGRAM_DIR" "$DIAGNOSTICS_DIR" "$AGENT_STATE_DIR"' in auto_deploy
assert "find /opt/teleo-eval -maxdepth 3 -name '*.sh'" not in auto_deploy assert "find /opt/teleo-eval -maxdepth 3 -name '*.sh'" not in auto_deploy
assert "backups" in auto_deploy assert "backups" in auto_deploy
@ -61,7 +61,7 @@ def test_agent_healthcheck_timer_runs_both_live_telegram_agents():
assert "/opt/teleo-eval/telegram/agent_healthcheck.py" in service assert "/opt/teleo-eval/telegram/agent_healthcheck.py" in service
assert "--agents leo leo-wallet-test" in service assert "--agents leo leo-wallet-test" in service
assert "--since \"20 min ago\"" in service assert '--since "20 min ago"' in service
assert "OnUnitActiveSec=5min" in timer assert "OnUnitActiveSec=5min" in timer
assert "WantedBy=timers.target" in timer assert "WantedBy=timers.target" in timer
@ -74,7 +74,7 @@ def test_deploy_scripts_install_systemd_units_and_enable_agent_healthcheck():
assert 'sudo install -m 0644 "$unit" "$SYSTEMD_DIR/$(basename "$unit")"' in auto_deploy assert 'sudo install -m 0644 "$unit" "$SYSTEMD_DIR/$(basename "$unit")"' in auto_deploy
assert "sudo systemctl daemon-reload" in auto_deploy assert "sudo systemctl daemon-reload" in auto_deploy
assert "sudo systemctl enable --now teleo-agent-healthcheck.timer" in auto_deploy assert "sudo systemctl enable --now teleo-agent-healthcheck.timer" in auto_deploy
assert "git diff --name-only \"$OLD_SHA\" \"$NEW_SHA\" -- systemd/teleo-agent@.service" in auto_deploy assert 'git diff --name-only "$OLD_SHA" "$NEW_SHA" -- systemd/teleo-agent@.service' in auto_deploy
assert 'VPS_SYSTEMD="/etc/systemd/system"' in manual_deploy assert 'VPS_SYSTEMD="/etc/systemd/system"' in manual_deploy
assert "teleo-agent-healthcheck.timer" in manual_deploy assert "teleo-agent-healthcheck.timer" in manual_deploy
@ -100,16 +100,16 @@ def test_deploy_scripts_sync_leoclean_skills_and_restart_gateway_for_runtime_cha
assert 'LEOCLEAN_BIN_DIR="/home/teleo/.hermes/profiles/leoclean/bin"' in auto_deploy assert 'LEOCLEAN_BIN_DIR="/home/teleo/.hermes/profiles/leoclean/bin"' in auto_deploy
assert 'LEOCLEAN_SKILLS_DIR="/home/teleo/.hermes/profiles/leoclean/skills"' in auto_deploy assert 'LEOCLEAN_SKILLS_DIR="/home/teleo/.hermes/profiles/leoclean/skills"' in auto_deploy
assert 'LEOCLEAN_PLUGINS_DIR="/home/teleo/.hermes/profiles/leoclean/plugins"' in auto_deploy assert 'LEOCLEAN_PLUGINS_DIR="/home/teleo/.hermes/profiles/leoclean/plugins"' in auto_deploy
assert 'hermes-agent/leoclean-bin/' in auto_deploy assert "hermes-agent/leoclean-bin/" in auto_deploy
assert 'hermes-agent/leoclean-skills/vps/' in auto_deploy assert "hermes-agent/leoclean-skills/vps/" in auto_deploy
assert 'hermes-agent/leoclean-plugins/vps/' in auto_deploy assert "hermes-agent/leoclean-plugins/vps/" in auto_deploy
assert 'HERMES_PATCH_DIR="/home/teleo/.hermes/teleo-runtime-patches"' in auto_deploy assert 'HERMES_PATCH_DIR="/home/teleo/.hermes/teleo-runtime-patches"' in auto_deploy
assert '"status": "installed_now"' in auto_deploy assert '"status": "installed_now"' in auto_deploy
assert 'Hermes response-transform drift repaired' in auto_deploy assert "Hermes response-transform drift repaired" in auto_deploy
assert 'deploy/leoclean-gateway-restart-required.sh' in auto_deploy assert "deploy/leoclean-gateway-restart-required.sh" in auto_deploy
assert 'TELEO_AUTO_DEPLOY_REEXECED' in auto_deploy assert "TELEO_AUTO_DEPLOY_REEXECED" in auto_deploy
assert 'exec bash "$DEPLOY_CHECKOUT/deploy/auto-deploy.sh"' in auto_deploy assert 'exec bash "$DEPLOY_CHECKOUT/deploy/auto-deploy.sh"' in auto_deploy
assert 'add_restart_if_unit_active leoclean-gateway' in auto_deploy assert "add_restart_if_unit_active leoclean-gateway" in auto_deploy
assert 'sudo systemctl restart "$svc"' in auto_deploy assert 'sudo systemctl restart "$svc"' in auto_deploy
assert "sudo systemctl restart $RESTART" not in auto_deploy assert "sudo systemctl restart $RESTART" not in auto_deploy
assert "NOPASSWD: /usr/bin/systemctl restart leoclean-gateway" in sudoers assert "NOPASSWD: /usr/bin/systemctl restart leoclean-gateway" in sudoers
@ -117,11 +117,11 @@ def test_deploy_scripts_sync_leoclean_skills_and_restart_gateway_for_runtime_cha
assert 'VPS_LEOCLEAN_BIN="/home/teleo/.hermes/profiles/leoclean/bin"' in manual_deploy assert 'VPS_LEOCLEAN_BIN="/home/teleo/.hermes/profiles/leoclean/bin"' in manual_deploy
assert 'VPS_LEOCLEAN_SKILLS="/home/teleo/.hermes/profiles/leoclean/skills"' in manual_deploy assert 'VPS_LEOCLEAN_SKILLS="/home/teleo/.hermes/profiles/leoclean/skills"' in manual_deploy
assert 'VPS_LEOCLEAN_PLUGINS="/home/teleo/.hermes/profiles/leoclean/plugins"' in manual_deploy assert 'VPS_LEOCLEAN_PLUGINS="/home/teleo/.hermes/profiles/leoclean/plugins"' in manual_deploy
assert 'hermes-agent/leoclean-bin/' in manual_deploy assert "hermes-agent/leoclean-bin/" in manual_deploy
assert 'hermes-agent/leoclean-skills/vps/' in manual_deploy assert "hermes-agent/leoclean-skills/vps/" in manual_deploy
assert 'hermes-agent/leoclean-plugins/vps/' in manual_deploy assert "hermes-agent/leoclean-plugins/vps/" in manual_deploy
assert 'VPS_HERMES_PATCHES="/home/teleo/.hermes/teleo-runtime-patches"' in manual_deploy assert 'VPS_HERMES_PATCHES="/home/teleo/.hermes/teleo-runtime-patches"' in manual_deploy
assert 'systemctl is-active --quiet leoclean-gateway.service' in manual_deploy assert "systemctl is-active --quiet leoclean-gateway.service" in manual_deploy
def test_deploy_scripts_syntax_check_leoclean_database_runtime_before_restart(): def test_deploy_scripts_syntax_check_leoclean_database_runtime_before_restart():
@ -135,32 +135,22 @@ def test_deploy_scripts_syntax_check_leoclean_database_runtime_before_restart():
def test_auto_deploy_hot_syncs_leoclean_markdown_without_gateway_restart(): def test_auto_deploy_hot_syncs_leoclean_markdown_without_gateway_restart():
assert not _leoclean_gateway_restart_required( assert not _leoclean_gateway_restart_required("hermes-agent/leoclean-skills/vps/teleo-kb-bridge/SKILL.md")
"hermes-agent/leoclean-skills/vps/teleo-kb-bridge/SKILL.md" assert not _leoclean_gateway_restart_required("hermes-agent/leoclean-skills/vps/live-leo-telegram/SKILL.md")
)
assert not _leoclean_gateway_restart_required(
"hermes-agent/leoclean-skills/vps/live-leo-telegram/SKILL.md"
)
def test_auto_deploy_restarts_gateway_for_leoclean_runtime_changes(): def test_auto_deploy_restarts_gateway_for_leoclean_runtime_changes():
assert _leoclean_gateway_restart_required("hermes-agent/leoclean-bin/kb_tool.py") assert _leoclean_gateway_restart_required("hermes-agent/leoclean-bin/kb_tool.py")
assert _leoclean_gateway_restart_required( assert _leoclean_gateway_restart_required("hermes-agent/leoclean-skills/vps/teleo-kb-bridge/runtime_helper.py")
"hermes-agent/leoclean-skills/vps/teleo-kb-bridge/runtime_helper.py" assert _leoclean_gateway_restart_required("hermes-agent/leoclean-plugins/vps/leo-db-context/__init__.py")
) assert _leoclean_gateway_restart_required("hermes-agent/patches/apply_response_transform_hook.py")
assert _leoclean_gateway_restart_required(
"hermes-agent/leoclean-plugins/vps/leo-db-context/__init__.py"
)
assert _leoclean_gateway_restart_required(
"hermes-agent/patches/apply_response_transform_hook.py"
)
def test_auto_deploy_sudoers_installer_is_root_only_and_visudo_checked(): def test_auto_deploy_sudoers_installer_is_root_only_and_visudo_checked():
installer = (REPO_ROOT / "deploy" / "install-auto-deploy-sudoers.sh").read_text() installer = (REPO_ROOT / "deploy" / "install-auto-deploy-sudoers.sh").read_text()
assert 'TARGET="/etc/sudoers.d/teleo-auto-deploy"' in installer assert 'TARGET="/etc/sudoers.d/teleo-auto-deploy"' in installer
assert 'id -u' in installer assert "id -u" in installer
assert 'install -m 0440 "$SOURCE" "$TARGET"' in installer assert 'install -m 0440 "$SOURCE" "$TARGET"' in installer
assert 'visudo -cf "$TARGET"' in installer assert 'visudo -cf "$TARGET"' in installer
@ -183,3 +173,45 @@ def test_gcp_leoclean_skill_sync_targets_parallel_runtime_without_secrets():
assert "sudo rsync" not in script assert "sudo rsync" not in script
assert "TELEGRAM_BOT_TOKEN" not in script assert "TELEGRAM_BOT_TOKEN" not in script
assert "CLOUDSQL_PASSWORD" not in script assert "CLOUDSQL_PASSWORD" not in script
def test_gcp_runtime_sync_is_source_bound_and_scoped():
script = (REPO_ROOT / "deploy" / "sync-gcp-leoclean-runtime.sh").read_text()
dropin = (REPO_ROOT / "systemd" / "leoclean-gcp-prod-parallel-cloudsql.conf").read_text()
assert 'git -C "$REPO_ROOT" rev-parse HEAD' in script
assert 'git -C "$REPO_ROOT" diff --quiet HEAD' in script
assert "merge-base --is-ancestor" in script
assert "Refusing to deploy unmerged revision" in script
assert "Refusing to install runtime files without --restart" in script
assert 'DROPIN_NAME="10-cloudsql-memory.conf"' in script
assert "20-canonical-kb.conf" not in script
assert "sha256sum" in script
assert "--restart" in script
assert 'python3 "$tool_path" status --format json --redacted' in script
assert "sa-teleo-prod-vm@teleo-501523.iam.gserviceaccount.com" in script
assert "metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email" in script
assert "env -u PGPASSWORD" in script
assert 'run_scoped_status pre "$remote_tmp/$TOOL_REL"' in script
assert 'run_scoped_status post "$profile_bin/cloudsql_memory_tool.py"' in script
assert "backup_path" in script
assert "restore_path" in script
assert "rollback_runtime" in script
assert "trap 'on_exit $?' EXIT" in script
assert 'backup_path "$dropin_dir/$DROPIN_NAME" dropin' in script
assert 'dropin_stage="$dropin_dir/.${DROPIN_NAME}.livingip-new.$$"' in script
assert 'sudo mv -f -- "$dropin_stage" "$dropin_dir/$DROPIN_NAME"' in script
assert 'active_state" != "active"' in script
assert 'sub_state" != "running"' in script
assert 'sudo systemctl stop "$SERVICE"' in script
assert 'sudo systemctl start "$SERVICE"' in script
assert "UnsetEnvironment=PGPASSWORD" in dropin
assert "RuntimeDirectory=leoclean-gcp-prod-parallel" in dropin
assert "RuntimeDirectoryMode=0700" in dropin
assert "Environment=CLOUDSDK_CONFIG=/run/leoclean-gcp-prod-parallel" in dropin
assert "TELEO_KB_MODE=cloudsql" in dropin
assert "TELEO_CLOUDSQL_USER=leoclean_kb_runtime" in dropin
assert "gcp-teleo-pgvector-standby-leoclean-kb-runtime-password" in dropin
assert "TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK=0" in dropin
assert not any(line.startswith("Environment=PGPASSWORD") for line in dropin.splitlines())
assert "gcp-teleo-pgvector-standby-postgres-password" not in dropin