Scaffold Bare Metal Feature Defaults
Use this skill when the user wants to choose or write down the feature defaults for a new AI-native application in the Bare Metal Software style - sign-in, roles, tenancy, audit trail, files, search, reports, backups, jobs, and the rest of the two-dozen…
This skill is Labor as Code™ for one chapter of Bare Metal Software: "The Features Every App Needs, and One Choice for Each." Read that chapter for the reasoning behind each default — this skill only executes the method (fill in the sheet, commit it to the spec) and does not carry the argument for why any one default is right. It also does not decide anything the book leaves to the reader: which features actually apply to this project, whether the project is enterprise or prosumer, whether Rust is acceptable, and whether any "change it only when" condition has actually been met are always the user's calls.
The book's own fieldwork prompt for this chapter is "Fill In the Feature Sheet" — this skill is that prompt turned into a repeatable procedure, with the book's own default table as the ground truth instead of something reconstructed from memory each time.
Before running
Confirm these with the user before producing anything, in plain conversation. Do not guess any of them:
- Enterprise or prosumer. An enterprise app is one hosted system many organizations use (tenancy, per-customer SQLite files, Stripe subscriptions, single sign-on eventually). A prosumer app is one a single serious user installs and runs on their own machine (one data folder, a one-time purchase, self-updating signed binary). Get this right first — a dozen of the defaults below split on it.
- What this application actually is — the real product, not a generic CRUD app. From that, and only from that, decide with the user which features in the catalog below are actually in scope. Do not run every row against every project: an internal tool with no payment, no mobile app, and no field devices has no business filling in Billing, Mobile release pipelines, or Edge device rows. Draft a candidate in-scope list and have the user confirm or edit it before step 1.
- Is Rust acceptable? The book's default stack is one Rust binary for the reasons its preface gives (single-file program, compiler safety, no runtime to install, a small container image). If the user's organization has ruled Rust out, the fallback is a JavaScript platform that compiles to a single binary (Deno or Bun) — note this once, up front, and do not re-litigate it feature by feature.
- Does this project handle regulated or sensitive data (health information, payment data, other regulated PII)? Several defaults below (virus scanning, business associate agreements, the runtime model's data-handling default) change meaningfully if so, and the answer has to come from the user, not be inferred from the product description.
Step 1: Confirm the in-scope feature list
Using the user's answers above, propose which rows from the table below actually apply to this project. Typical exclusions: Billing (no paid product yet), Offline/sync (no mobile or edge component), the mobile and edge device rows (web-only product). Get explicit confirmation before moving on — an unconfirmed scope list produces a spec full of defaults for features the project doesn't have.
Step 2: Walk the feature table
This is the book's own table, one row per in-scope feature. For each row, state the default and its reversal condition exactly as written below — do not paraphrase them into something vaguer, and do not substitute a different default because it "feels more modern." The whole point of the chapter is that these are settled choices, not a menu.
| Feature | Default | Change it only when |
|---|---|---|
| Sign-in | Passkeys + email link, mature auth library, session rows in SQLite | An enterprise customer requires single sign-on; add OpenID Connect only |
| Roles | Four roles (owner, admin, member, viewer) in one table; one permission function | A paying customer needs a rule the four roles cannot express |
| State | The lowest level of the State Complexity Ladder that fits each kind of state; files first | Queries, concurrency, or operations need the next level, measured |
| Tenants | One SQLite file per customer | Thousands of customers or cross-customer queries, measured |
| Audit | Append-only table written in the same transaction as every change | A regulator requires storage your own admins cannot touch |
| Files | On disk by content hash; row in SQLite; object store for backup only | More than one server must serve the same files |
| Search | SQLite FTS5 | Millions of documents and a relevance team |
| Reports | Nightly Parquet export + DuckDB; CSV/Excel download | Numbers must be minutes old, not hours |
| PDFs | Print-styled HTML rendered by a headless browser | A fixed official form is required |
| Imports | Staging table, validate, error workbook back | Source is a live system, not a file |
| Jobs | Jobs table + worker threads in the same program | More than one server must share the queue |
| Schedules | Schedules table read by the program’s worker threads | A job must run while the program is stopped; the host scheduler starts a one-off container |
| Live updates | Server-sent events | Browser must send many messages per second |
| Cache | None | A measured slow query; in-memory map only |
| Outbox table + provider over SMTP/HTTP; inbound via provider webhook | Provider pricing changes the business | |
| Notifications | Table + badge + daily digest; urgent kinds immediate | Users ask for different timing |
| API | JSON over HTTPS at /api/v1; hashed API keys; OpenAPI generated from the spec | A partner requires OAuth |
| Webhooks | Subscriptions table, HMAC-signed, six retries over a day | Never |
| Billing | Stripe Checkout + portal; entitlements table; signed license file (prosumer) | Selling where Stripe does not serve |
| Settings/flags | One settings table with scopes | Never |
| Backups | Litestream + nightly copies + monthly automatic restore drill | Never |
| Logs/health | JSON lines to standard output, kept by the container runtime; /health; /status page | Customer ops team requires OpenTelemetry |
| Front door | Inside the program: rustls TLS, ACME certificates, rate and size limits, timeouts | A second host arrives; a load balancer takes over TLS and limits |
| Updates | New image replaces the container + forward-only migrations; self-update (prosumer) | Never |
| Offline/sync | Device SQLite + change log + one sync endpoint; conflicts kept | Never |
| Languages | English only; strings table when a customer pays for a second | A paying customer needs another language |
| Admin | /admin pages in the same program | Never |
| Runtime model | Plain HTTPS, schema-checked output, no write access, eval set, audit every call | No vendor will sign the agreement you need; run a model on your own host |
| Identifiers, time, money | UUIDv7; UTC ISO 8601 text; whole cents | Never |
| Uploads | Allow-list by first bytes; size limit at the front door; no virus scan | A customer contract requires scanning |
| Host | Debian stable, container runtime, keys-only SSH, three ports, hardened container | Never |
| Build | Lock file, pinned toolchain, vendored dependencies, static signed binary in a minimal image | Never |
| Deployment | Container-first: one image, one Compose file, one volume; scripts in the repository build and deploy it | More hosts than one Compose file per host can manage, measured |
| Signing (prosumer) | Windows code signing and macOS notarization from release one | Never |
| Third-party scripts | None on any page, ever | Never |
Step 3: Test every "never" and every condition against today's evidence
For each in-scope row, ask the user one question: does the "change it only when" condition already hold, today, with evidence?
- If no (the common case), the default stands. Move on.
- If yes, do not silently swap in the alternative. Stop and ask the user to state the measurement that moved them (a load number, a contract clause, a customer count) — this is the book's own rule: "write down the measurement that moved you." A default changed without a stated measurement is exactly the failure mode this chapter exists to prevent, so do not proceed past this point on the user's assertion alone; get the actual number or document.
- Every row that escalates gets one short file in the project's
decisions/folder: the feature, the default, the alternative chosen, and the measurement. Draft this file's contents but do not invent a measurement that wasn't given to you.
Step 4: Write the spec section
Turn the confirmed table (defaults plus any escalated rows) into the project's own specification document, one sentence per feature, in the plain-language style the book itself uses for the table — this is meant to be read by both a human and a harness, so keep each line a single settled statement of the default:
Sign-in: passkeys, plus a sign-in link by email for devices without
passkeys. Session is a random token in a cookie with a matching row
in SQLite; an admin can revoke by deleting the row.
Roles: four roles (owner, admin, member, viewer) in one table. All
permission checks go through one function.
[... one line per confirmed feature ...]
Step 5: Scaffold the repository shape, if asked
Only if the user wants the actual folder structure created (not every user does — some just want the spec written), lay out the shape the book describes as "the repository the harness works in":
mkdir -p spec constitutional-tests ops decisions
spec/— the feature-defaults document from Step 4, plus one file per boundary as the project grows.constitutional-tests/— the tests, models, and proofs the harness may never weaken to make its own change pass.ops/— the Dockerfile, the Compose file, and the deploy, backup, restore, and check scripts, once they exist.decisions/— one short file per default the project changed from the book's own default, and the measurement that moved it (Step 3).
Do not scaffold application code from this skill. This skill's job ends at a written, confirmed spec and (optionally) the empty folder shape a harness will fill in from that spec.
The check that catches this chapter's own failure mode
Before calling the sheet done, reread every row that is NOT the book's default. For each one, confirm there is a real, stated, measured condition attached to it — not "it seemed safer," not "we might need it later," not "that's how we did it at my last company." The chapter opens with the warning this check exists to catch: "Add one [vendor or service] per feature and a small app is carrying twenty dependencies before it has served its first customer." A feature sheet with several unjustified deviations from the defaults is that failure happening in real time. Flag every such row back to the user before treating the sheet as finished.