Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14" # the vendored gam's own Python: test_gam_exit_codes unmarshals its bytecode
python-version: "3.14" # the suite's; the build's own Python reads its exit codes below
- name: Install (hash-locked, wheels only)
run: |
python -m pip install --require-hashes --only-binary :all: -r requirements/dev.txt
Expand All @@ -110,16 +110,36 @@ jobs:
PY
- name: Restore the committed command catalog (fetch_gam.sh replaces resources/gam7 wholesale)
run: git checkout -- gamgui/resources/gam7/command_catalog.json
# The build's *_RC table is marshalled bytecode, which loads only in the Python that froze it (3.15
# for 7.48.26, ahead of the dev lock's wheels). So that Python, set up beside the suite's and left
# off PATH, reads it with the stdlib-only script, and the suite gets the table as JSON.
- name: Which Python froze the vendored gam
id: buildpy
run: |
version=$(python -I scripts/gam_rc_table.py --python-version gamgui/resources/gam7/gam)
echo "version=$version" >> "$GITHUB_OUTPUT"
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
id: gampy
with:
python-version: ${{ steps.buildpy.outputs.version }}
allow-prereleases: true
update-environment: false
- name: Read the build's exit-code table with its own Python (stdlib only, nothing installed)
env:
GAM_PYTHON: ${{ steps.gampy.outputs.python-path }}
run: |
"$GAM_PYTHON" -I scripts/gam_rc_table.py gamgui/resources/gam7/gam > "$RUNNER_TEMP/gam_rc.json"
- name: The whole suite against the REAL reference (the command contract, and every argv sent to the mock)
run: pytest -q
env:
# tests/test_gam_exit_codes.py reads the build's *_RC table here; a skip would hide that it didn't.
# tests/test_gam_exit_codes.py compares the build's *_RC table here; a skip would hide that it didn't.
EXIT_CODES_REQUIRE_GAM: "1"
GAM_RC_TABLE: ${{ runner.temp }}/gam_rc.json

app:
# Freezes the .app exactly as `make app` does, so a spec, lock or signing change that breaks the
# bundle fails here rather than at the next local build. The runner has no signing identity, so
# build_app.sh signs ad-hoc (hardened runtime). The pinned gam's libpython needs macOS >= 26.5.
# build_app.sh signs ad-hoc (hardened runtime). The pinned gam's libpython needs macOS >= 26.6.
name: app (build + smoke check)
runs-on: macos-latest
timeout-minutes: 20
Expand Down
31 changes: 30 additions & 1 deletion .github/workflows/gam-watch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,40 @@ jobs:
# Never expand an expression inside run: — pass it through env so the shell sees data.
run: python scripts/bump_gam.py "v$LATEST"

# The new build's *_RC table loads only in the Python that froze it (see ci.yml's gam-compat). A
# failure here leaves no table, so the suite below goes red and the PR still opens, flagged.
- name: Which Python froze the new gam
if: steps.check.outputs.todo == 'true'
id: buildpy
continue-on-error: true
run: |
version=$(python -I scripts/gam_rc_table.py --python-version gamgui/resources/gam7/gam)
echo "version=$version" >> "$GITHUB_OUTPUT"

- name: Set up the build's own Python (off PATH; the suite stays on 3.14)
if: steps.check.outputs.todo == 'true'
id: gampy
continue-on-error: true
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ steps.buildpy.outputs.version }}
allow-prereleases: true
update-environment: false

- name: Read the new build's exit-code table with its own Python (stdlib only)
if: steps.check.outputs.todo == 'true'
continue-on-error: true
env:
GAM_PYTHON: ${{ steps.gampy.outputs.python-path }}
run: |
"$GAM_PYTHON" -I scripts/gam_rc_table.py gamgui/resources/gam7/gam > "$RUNNER_TEMP/gam_rc.json"

- name: Run the suite (drift guards included)
if: steps.check.outputs.todo == 'true'
id: tests
env:
EXIT_CODES_REQUIRE_GAM: "1" # the new build's *_RC table must be read, not skipped
EXIT_CODES_REQUIRE_GAM: "1" # the new build's *_RC table must be compared, not skipped
GAM_RC_TABLE: ${{ runner.temp }}/gam_rc.json
run: |
set +e
python -m pytest -q > /tmp/pytest.log 2>&1
Expand Down
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ Each of these exists because something went wrong. Several have tests guarding t
ran on a bare POST — against what the preview showed: a confirm step that posts the live form
runs the changes its preview held (single-use `web/previews.py` token), never a rebuild of that
form. `tests/test_write_routes_guarded.py` holds every POST route to this or a stated exemption.
3. **Only read-only commands may become runnable automatically.** Of 1075 catalog entries, 538 run:
26 hand-curated (the only ones that can *change* anything) plus 512 grammar-derived commands
3. **Only read-only commands may become runnable automatically.** Of 1077 catalog entries, 540 run:
26 hand-curated (the only ones that can *change* anything) plus 514 grammar-derived commands
auto-promoted by `core/catalog/readbuilder.py` **because they are confidently `READ_ONLY` and not
flagged uncertain**. Everything else is inert, syntax display only. Adding write coverage is a
deliberate act of curation. (Two agent-facing docs once claimed only the 26 curated commands run —
Expand Down Expand Up @@ -113,7 +113,7 @@ make gam TAG=vX.Y.Z # re-vendor GAM (fails closed on an unpinned
make app # build dist/GamGUI.app
```

GAM is pinned at `EXPECTED_GAM_VERSION` in `core/gam/commands.py` (currently 7.48.22); three drift
GAM is pinned at `EXPECTED_GAM_VERSION` in `core/gam/commands.py` (currently 7.48.26); three drift
guards (`test_required_command_tokens_present`, `test_catalog_matches_grammar`,
`test_pinned_version_consistent`) fail if a bump breaks a command we use. Bump with
`scripts/bump_gam.py vX.Y.Z` — runbook in [build-packaging](docs/domains/build-packaging.md); the
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ Actively developed and used against live Google Workspace tenants. Working today
password goes on a copyable, printable sheet, or Google emails it via GAM's `notify`), the role's
OU and signature, its groups and shared calendars, a Google Tasks checklist for whoever does the
setup, and a templated welcome email — for one person, or a CSV of hires as a background job.
- **Command Builder** — browse/search the full categorized GAM catalog (1,075 commands, 538 of them
runnable: 26 hand-curated, the only ones that can change anything, plus 512 read-only commands
- **Command Builder** — browse/search the full categorized GAM catalog (1,077 commands, 540 of them
runnable: 26 hand-curated, the only ones that can change anything, plus 514 read-only commands
derived from GAM's grammar); curated commands get typed slots, drag-a-user targeting, a guarded
preview → run, linear sequencing, and results export (CSV download or straight to a Google
Sheet). Result tables slice like grep — a live row filter plus an **External only** toggle that
Expand Down
6 changes: 3 additions & 3 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ around that is not on this list. See [CONTRIBUTING.md](CONTRIBUTING.md).

## Later

- **Grow the buildable command set — specifically the writes.** Of 1,075 catalog entries, 538 can
run today: 26 hand-curated commands (the only ones that can *change* anything) plus 512
- **Grow the buildable command set — specifically the writes.** Of 1,077 catalog entries, 540 can
run today: 26 hand-curated commands (the only ones that can *change* anything) plus 514
grammar-derived commands auto-promoted because they are confidently read-only. The other 537 —
every write, and anything whose risk could not be inferred with confidence — are inert, syntax
display only. That asymmetry is the safety boundary: a command can become runnable automatically
Expand Down Expand Up @@ -78,7 +78,7 @@ discover later.
asked for it, so the effort goes elsewhere. The connector/core split is where you would start if
that changed.
- **A free-text `gam …` box.** The tempting escape hatch, and the trade-off is narrower than it
sounds. *Reads are already open*: 512 grammar-derived read commands run today with no curation at
sounds. *Reads are already open*: 514 grammar-derived read commands run today with no curation at
all (the handful whose output is a secret or a file — backup codes, browser tokens, and every
`get` download: Drive files and Docs, Keep attachments, ChromeOS device files, photos — are
audited when run; SECURITY.md lists them). The line is drawn at **writes** — every command that can change something is hand-modeled, so
Expand Down
Loading
Loading