You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit f2c1553
Browse filesBrowse the repository at this point in the historyBrowse files
ci: validate every org's resources on every pull request
Nothing ran `npm run validate` before merge. A config that `apply`
refuses (a name over 40 characters, a per-provider voice schema error)
could merge green, and deploys and promotion out of main then stopped
until a fix landed. Plain `push` only warns, and can fail partway with an
API 400. The validator's other rules (structured-output lockstep,
duplicated prompts, the maxTokens floor) are warnings and don't fail it.
- ci.yml gets a Validate resources job: validate for every folder under
resources/, reporting every failing org rather than stopping at the
first. validate makes no network call; the engine's config only needs a
key to be set, so the step sets a placeholder key and an unroutable base
URL, so nothing can be sent. The job has no secrets and installs without
scripts, so forks get it too. No engine change.
- The failure message, troubleshooting guide and AGENTS.md give a
placeholder-key command to reproduce it, so nobody fetches a real key
for an offline check.
- The docs say what requiring it costs (every org gates every PR), the
merge-queue trigger it needs, and that CI validates .ts resources
without .env.<org>.
- The starter example's one-sided structured-output link is fixed, so it
validates without warnings.
- tests/ci-validate-workflow.test.ts runs the step itself against fixture
orgs: no orgs, all valid (and warning-free), one invalid org among valid
ones, an invalid folder name, and no secrets, permissions, privileged
trigger or persisted credentials.
- README, AGENTS.md (change loop), the workflows, PR checks and
troubleshooting guides, and improvements.md #37 describe it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: .github/workflows/ci.yml
+58Lines changed: 58 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -64,3 +64,61 @@ jobs:
64
64
65
65
- name: Lint workflows
66
66
run: ./actionlint -color
67
+
68
+
validate:
69
+
name: Validate resources
70
+
runs-on: ubuntu-latest
71
+
timeout-minutes: 10
72
+
73
+
steps:
74
+
- uses: actions/checkout@v4
75
+
with:
76
+
persist-credentials: false
77
+
78
+
- uses: actions/setup-node@v4
79
+
with:
80
+
node-version: 22
81
+
cache: npm
82
+
83
+
# Install scripts only build the optional audio deps for `npm run call`.
84
+
- run: npm ci --ignore-scripts
85
+
86
+
# The same validator `apply` runs before every deploy, here before merge:
87
+
# a config that `apply` would refuse never reaches main, where it would
88
+
# block deploys and promotion until someone noticed. Every org is
89
+
# validated, including resources no PR check targets.
90
+
#
91
+
# `validate` makes no network call, but loading the engine's config
92
+
# needs a key to be set. The placeholder below is never sent anywhere
93
+
# (the base URL is unroutable, so an accidental API call fails here),
94
+
# and no secret is available to this job, so it runs the same on forks.
95
+
# `.ts` resources run without `.env.<org>` here, unlike under `apply`.
96
+
- name: Validate every org
97
+
shell: bash
98
+
env:
99
+
VAPI_PRIVATE_API_KEY: validate-only-never-sent
100
+
VAPI_BASE_URL: http://127.0.0.1:9
101
+
run: |
102
+
set -euo pipefail
103
+
shopt -s nullglob
104
+
orgs=()
105
+
for dir in resources/*/; do
106
+
orgs+=("$(basename "$dir")")
107
+
done
108
+
if (( ${#orgs[@]} == 0 )); then
109
+
echo "No org folders under resources/; nothing to validate."
110
+
exit 0
111
+
fi
112
+
failed=()
113
+
for org in "${orgs[@]}"; do
114
+
echo "::group::Validate ${org}"
115
+
if ! node --import tsx src/validate-cmd.ts "$org"; then
116
+
failed+=("$org")
117
+
fi
118
+
echo "::endgroup::"
119
+
done
120
+
if (( ${#failed[@]} > 0 )); then
121
+
echo "::error::Validation failed for: ${failed[*]}. Each org's findings are in its log group above. To reproduce locally without that org's key: VAPI_PRIVATE_API_KEY=validate-only npm run validate -- <org>"
Copy file name to clipboardExpand all lines: improvements.md
+53Lines changed: 53 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -88,6 +88,7 @@ you which stack PR closes the row.**
88
88
| 34 | No pre-merge simulation signal; simulations only tested what was deployed | A PR that breaks an agent merges green |#33| RESOLVED 2026-10-01 |
89
89
| 35 | A failed promotion pushed nothing, not even state | git lost track of resources already on the platform | None | RESOLVED 2026-10-01 |
90
90
| 36 |`cleanup` deletes resources excluded by `.vapi-ignore`| A destructive cleanup can delete resources another team owns | None | RESOLVED 2026-10-03 |
91
+
| 37 | Resource validation ran only at deploy time, after merge | A config `apply` refuses could merge and block deploys and promotion |#32| RESOLVED 2026-10-03 (#76) |
91
92
92
93
**Active backlog after cleanup:**`#2`, `#6`, `#8`, `#12`, `#20`, `#24–#26`, `#31`, and the open remainder of `#27` (wiring the listing-completeness verdict into push/delete/audit, and moving `cleanup.ts` onto the shared pager). Resolved entries stay in this file as historical incident notes per the maintenance directive; stale superseded backlog rows are not duplicated.
93
94
@@ -1933,6 +1934,58 @@ orphan; after it, only the orphan.
1933
1934
1934
1935
---
1935
1936
1937
+
## 37. Resource validation ran only at deploy time, after merge
1938
+
1939
+
**[RESOLVED 2026-10-03] (#76)**
1940
+
1941
+
**Discovered:** 2026-10-03, while reviewing which static checks run before
1942
+
the PR check's simulations.
1943
+
1944
+
### Problem
1945
+
1946
+
`npm run validate` fails on shapes the API rejects mid-push (a name over 40
1947
+
characters, per-provider voice schema) and warns on silent inconsistencies
1948
+
(structured-output lockstep, duplicated prompts, the `maxTokens` floor), but
1949
+
nothing ran it before merge. A config that `apply` refuses could land on
1950
+
`main`, and was found only when someone deployed or promoted it.
1951
+
1952
+
### Current behavior (Verified)
1953
+
1954
+
-`src/apply.ts` runs `validate` before every deploy and stops on errors.
1955
+
Promotion deploys through `apply`, so it stops too, but only after the
1956
+
change merged.
1957
+
-`src/push.ts` runs the same validators but only warns unless `--strict`.
1958
+
-`ci.yml` ran the build and tests only. `tests/examples.test.ts`
1959
+
validates `examples/`, not `resources/<org>/`.
1960
+
- The PR check's payload build (`npm run check -- --dry-run`) covers only
1961
+
the resources its targets reach, and only in repos that turned PR checks on.
1962
+
1963
+
### Risk
1964
+
1965
+
A broken config merges green. Deploys and promotion out of `main` then stop
1966
+
until a fix PR lands, or, with plain `push`, the push continues and fails
1967
+
partway with an API 400.
1968
+
1969
+
### Current mitigation
1970
+
1971
+
None needed once the fix below lands.
1972
+
1973
+
### Possible fix (landed)
1974
+
1975
+
A **Validate resources** job in `.github/workflows/ci.yml` runs `validate`
1976
+
for every folder under `resources/` on every pull request, reporting every
1977
+
failing org rather than stopping at the first. `validate` makes no network
1978
+
call, but loading the engine's config requires a key, so the step sets a
1979
+
placeholder that is never sent; the job has no secrets, so it runs on forks.
1980
+
No engine change. `tests/ci-validate-workflow.test.ts` runs the step itself
1981
+
against fixture orgs.
1982
+
1983
+
### Status
1984
+
1985
+
**RESOLVED 2026-10-03.**
1986
+
1987
+
---
1988
+
1936
1989
## Out of scope (intentionally not improvements)
1937
1990
1938
1991
-**State file is identity-only and not git-ignored.** It's intentionally
0 commit comments