Encampment command. Registration, rosters, schedules, and paperwork for 200 participants.
I Built Three Apps to Help Run Encampment.
I commanded Operation Diamond Forge 2026 and built the apps we used to prepare for it and run it. Staff needed answers on their phones. Participant records needed to stay private.
How could staff use the information without exposing participant records?
Executive perspective
Why does this matter?
We needed to work from the same information.
As commander of Operation Diamond Forge 2026, I needed registration, staffing, rosters, schedules, and documents that staff and volunteers could actually use. I built the apps around that work. The original account records roughly 200 participants and more than 100 users during preparation and the event.
The phone was often the only computer available.
Staff needed to look up a roster, find a contact, or check an assignment while they were out with the participants. I connected registration, check-in paperwork, and shared information so those jobs were easier. Public information and private participant records needed different access.
The event finished. I still needed to review the system.
The apps supported the encampment, which ended in June 2026. My post-event audit found access-control and data-write problems. The original account records the fixes. I’ve kept those findings here because a system people used successfully can still have serious problems.
Architecture perspective
Why was it designed this way?
One app would have been easier to build.
But I didn’t want a public registration form sharing access with private participant records. I used three deployments: a write-only public portal, a dashboard for non-sensitive information, and an internal operations app. Staff could still work from the rosters, packets, and schedules for the same event.
The deployed settings disagreed with my documentation.
After the event, I found that the deployment manifest didn’t match the access restriction described in the documentation. Seven functions that handled participant data needed permission checks. I added them. I should have checked the deployed settings sooner.
Ordinary spreadsheet edits could cause trouble.
I changed writes to find columns by header name and stop if a header was missing. I also added a lock around a roster read-modify-write operation. That protected against someone rearranging columns or two people updating the roster at once.
Technical perspective
How does it actually work?
Apps Script handled the staff workflows.
Google Apps Script served the three applications. The internal app supported roster editing, documents, reconciliation, and check-in packets across eleven form templates. The mobile layouts let staff work away from a desk. Header-based writes and locking protected the shared records during updates.
I put lookups where staff already worked.
The implementation included token-based signature packets, an org chart assembled from multiple sources, and staff contact lookup from Slack. The detailed account below explains those pieces, the audit findings, and the changes I made.
The detailed project account
These details come from the account written before the September 2026 site update. I’ve rewritten the wording, but kept the measurements, findings, and qualifications. “Today” and other time references describe that earlier period. I haven’t remeasured the results or assumed that open issues were fixed.
What was needed
Civil Air Patrol is the civilian auxiliary of the United States Air Force. The 2026 Arkansas Wing encampment was a twelve-day residential training program with 200 participants from 60 squadrons across 18 states.
Those sixty units didn’t share a system. We needed registration, cadet staff applications, rosters, flight assignments, org charts, check-in paperwork, contact directories, and a daily schedule. Most participants were minors. We had to bring the information together for twelve days when staff would mostly be away from a desk.
Emailing spreadsheets between volunteers wasn’t enough for that. I needed staff to be able to update a roster from a phone, including at 2200 in a barracks hallway. That was a situation the design had to support.
Public registration and private participant records couldn’t share the same access. With the Google Apps Script deployment access model I was using, I couldn’t safely put participant-data read endpoints on the public app. Filtering a response wasn’t enough. I chose separate applications, even though one would have been easier to build.
What I built
I used three deployments, separated by who could access the data.
The public portal accepted registration and cadet staff applications. It was write-only and had no way to read or list participant records back.
The public dashboard read only non-sensitive information: schedules, published contacts, the org chart, and population totals. It never opened registration data. Staff directories required an identity check, used a cached session, and returned only an approved list of fields.
The internal operations app gave staff inline roster editing across four participant categories, section documents, activity logs, reconciliation with the external registration export, and packet generation.
I also built check-in packets across eleven form templates so staff didn’t have to assemble paperwork by hand for two hundred people. Signature packets used hashed tokens that expired after twenty-one days. The org chart combined three data sources with manual overrides for assignments the code couldn’t decide. A Slack command handled staff contact lookup where people were already communicating. I designed for phones because that was the computer staff had with them during twelve days in the field.
What it did
More than 100 people used the apps during preparation and every day of the encampment. They registered, applied for positions, checked rosters, found contacts, and worked from the same schedule.
That supported two hundred participants from sixty squadrons in eighteen states over twelve days.
Verifying it afterward
Five days after the encampment ended, I audited the system. I started by telling the audit to check the system rather than trust what the earlier documentation claimed.
The documentation said the internal operations app was restricted to the organization’s domain. The live deployment manifest allowed anyone. That setting had changed months earlier during a login change, and the documentation hadn’t caught up.
Seven API functions handled participant data without permission checks. They had relied on the domain restriction described in the documentation. I also found hardcoded column positions in the public portal’s writes, so rearranging a spreadsheet column could send data into the wrong fields. Cadet staff applications lacked the duplicate check registration already had. A roster read-modify-write had no lock. An unused fourth app was still deployed and holding five database tables.
I closed those findings the same day. I added permission checks to all seven functions, changed writes to resolve headers and stop with the missing column names, added the duplicate guard, and put a script lock around the roster update. I also removed 3.8 MB from every page load and deleted the unused application.
I initially marked one item complete from memory: I said the login layer was live, although the audit hadn’t independently confirmed it. Before publishing the original account, I opened the URL in a private window and got the login prompt. It was live. I should have checked before saying it was.
What I learned
-
Three apps meant more to maintain. With the deployment access model I was using, I needed that separation to keep a public form apart from private records.
-
I needed to read the deployed manifest. In this case, the documentation described a restriction that no longer existed.
-
Seven functions were missing permission checks. I couldn’t assume either my function checklist or the documented domain restriction was protecting the data. I needed to check both.
-
I changed spreadsheet writes to find columns by name. If someone dragged a column, the code could still find it. If a required header disappeared, the write stopped instead of putting plausible-looking data in the wrong place.
-
I deleted the unused app. It still held credentials and tables and gave us another application to secure, even though nobody was using it.