# HOWTO-AI: build an IS app as an agent
For an AI (or anyone) building a database-backed app for a person who asked for one.
Read DISCIPLINE.md first -- it says WHY. This says WHAT TO DO, in order.

## Your job is the SPEC, not the app
Do not write an app. Write one spec file; let `make` write the app. You are good at
turning "I want to collect X" into a correct column list. You are not reliably good at
emitting five consistent layers of code twice. The generator is deterministic where you
are not, and its gate refuses output that merely looks right.

## Steps
1. **Ask what the records are.** One row = one what? Then each column: a name, a type,
   and how a person enters it. Stop when the columns cover the question the person
   actually has. Fewer columns is better; you can add later.
2. **Copy the spec template:** `cp templates/is.php APP/TBL.php`
3. **Fill it in:** `$APP`, `$TBL`, Title, Subtitle, Path, DBName, TableName, Columns.
   - Column row: `[ col#, name, label, N, default, inputType, SQLType ]`
   - `id` first: `"hidden"`, `PRIMARY KEY INT NOT NULL AUTO_INCREMENT`
   - Text that may hold any language: `CHARACTER SET utf8mb4`. Not `utf8`. IPA,
     Chinese, emoji all need it, and retrofitting a charset after data exists is work.
   - `created_at` / `updated_at` as `TIMESTAMP`, `"hidden"`.
   - inputType is an HTML input type (`text`, `date`, `url`, `select`, `number`, ...).
   - **Never write a password literal.** Leave the store lines as generated.
4. **Generate:** `make APP=APP TABLE=TBL && make APP=APP TABLE=TBL scaffold`
5. **Prove it:** `php -l` each generated `.php`; run `./selftest.sh` from the
   distribution root; it must exit 0. If it fails, fix the TEMPLATE or your spec --
   never the generated file.
6. **Create the table.** The app's DB user can read/write rows but cannot CREATE (by
   design). `./APP/isTBL.db create_table` PRINTS the DDL; add a GRANT for the app user.
   Running it needs the owner's root access -- hand them the one command, do not
   improvise around the permission boundary.
7. **Verify what you can, with commands, and quote the output:** app URL (expect a
   redirect to login if gated), and every `.js`/`.css` the page references (expect 200 --
   a 404 here silently breaks every form).
8. **Register what you cannot verify.** Anything needing a logged-in human belongs on a
   pending-tests page as a link plus steps, not in a chat message saying "please check."
   A verification you hand back in prose is a verification nobody owns.

## Rules that are not negotiable
- Never edit a generated file. Fix the template, regenerate.
- Never print, echo, or paste a credential. Extract it server-side by script when needed.
- Never `DELETE` or `UPDATE` a row you did not `INSERT` in the same run. Not "the latest",
  not `MAX(id)` -- an id your own insert returned. If the insert did not happen, ABORT.
  (`MAX(id)` once resolved to the owner's only real record and deleted it.)
- Back out before overwriting; never mutate a released artifact.
- Report what you verified and what you did not. A claim without its command is a guess.

## Failure modes you will actually hit
- **`isapi.js` 404** -- when APP != TABLE the file is `isapi<TABLE>.js`. Any missing
  script kills every form, and the browser's only symptom is a form that reloads empty
  and eats what the user typed. Check every referenced asset resolves.
- **Editor backups leak** (`name~`, `.old`, `.safe`) -- served as plaintext. Never leave
  one in a served directory, especially not of a file that has ever held a credential.
- **PHP version fatals** -- old code that "works" can be a bare undefined constant, or
  `mysqli_num_rows()` on the `TRUE` that non-SELECT queries return. `php -l` will not see
  either; only running the path does. Exercise every operation, not just the happy one.
- **Silently dropped columns** -- if an inputType has no branch in the form builder, the
  field may vanish from the form while the column exists in the table. Confirm every
  column you declared actually renders.
