backend-skeleton 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -1
- package/bin/bskel.mjs +526 -20
- package/contracts/csv.mjs +100 -0
- package/handles/providers/java-spring/observe.mjs +15 -1
- package/handles/providers/java-spring/templates/ContractObservationAspect.java.tmpl +38 -0
- package/handles/providers/java-spring/templates/ObserveSchemaLoader.java.tmpl +13 -7
- package/handles/providers/java-spring/templates/ReceiptSigner.java.tmpl +174 -0
- package/handles/providers/python-fastapi/observe.mjs +5 -0
- package/handles/providers/python-fastapi/templates/observe_contract.py.tmpl +19 -0
- package/handles/providers/python-fastapi/templates/receipt_sign.py.tmpl +68 -0
- package/handles/providers/typescript-express/observe.mjs +5 -0
- package/handles/providers/typescript-express/templates/observeContract.ts.tmpl +19 -0
- package/handles/providers/typescript-express/templates/receiptSign.ts.tmpl +65 -0
- package/lib/cli.mjs +78 -2
- package/lib/doctor.mjs +11 -10
- package/lib/field-dependencies.mjs +2 -1
- package/lib/gate-definitions.mjs +8 -4
- package/lib/scan-report-paths.mjs +47 -0
- package/lib/workflow.mjs +6 -0
- package/new/index.mjs +20 -0
- package/package.json +5 -2
- package/patterns/schema.sql +18 -0
- package/patterns/store.mjs +122 -0
- package/scanners/adapters/_express-shared.mjs +7 -9
- package/scanners/adapters/java-spring.mjs +7 -9
- package/scanners/adapters/python-fastapi.mjs +68 -11
- package/scanners/db/erd.mjs +0 -0
- package/scanners/index.mjs +71 -18
- package/scanners/render.mjs +12 -3
- package/scanners/text-util.mjs +22 -0
- package/schemas/conformance-report.schema.json +12 -1
- package/schemas/observe-receipt.schema.json +10 -1
- package/schemas/pattern-record.schema.json +19 -0
- package/schemas/scan-report.schema.json +7 -3
package/README.md
CHANGED
|
@@ -32,13 +32,18 @@ check for a specific failure mode found the same way — see `DECISIONS.md` for
|
|
|
32
32
|
|
|
33
33
|
- [Status: 1.0.0](#status-100)
|
|
34
34
|
- [Quickstart](#quickstart)
|
|
35
|
+
- [Try it in 10 seconds](#try-it-in-10-seconds)
|
|
36
|
+
- [The gated workflow](#the-gated-workflow)
|
|
35
37
|
- [Starting from nothing (greenfield)](#starting-from-nothing-greenfield)
|
|
36
38
|
- [Publishing a feature's contract as OpenAPI (optional)](#publishing-a-features-contract-as-openapi-optional)
|
|
39
|
+
- [A CSV table of a feature's contract (optional)](#a-csv-table-of-a-features-contract-optional)
|
|
37
40
|
- [Database schema (optional)](#database-schema-optional)
|
|
41
|
+
- [An ERD of your database schema (optional)](#an-erd-of-your-database-schema-optional)
|
|
38
42
|
- [Applying DDL to a live database (optional)](#applying-ddl-to-a-live-database-optional)
|
|
39
43
|
- [Declaring field-to-field dependencies (optional)](#declaring-field-to-field-dependencies-optional)
|
|
40
44
|
- [Patching a config file (optional)](#patching-a-config-file-optional)
|
|
41
45
|
- [Signed gate attestations (optional)](#signed-gate-attestations-optional)
|
|
46
|
+
- [Signed observe receipts (optional)](#signed-observe-receipts-optional)
|
|
42
47
|
- [Compatibility](#compatibility)
|
|
43
48
|
- [Generated-file policy](#generated-file-policy)
|
|
44
49
|
- [Security model](#security-model)
|
|
@@ -83,6 +88,21 @@ production -- which still splits the same way it always has:
|
|
|
83
88
|
|
|
84
89
|
## Quickstart
|
|
85
90
|
|
|
91
|
+
### Try it in 10 seconds
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm install -g backend-skeleton # or: npx backend-skeleton <command>
|
|
95
|
+
cd <any-existing-repo> # must be a git repository -- that's the only requirement
|
|
96
|
+
bskel scan # zero flags: every module/controller/entity/enum this repo's
|
|
97
|
+
# adapter can see, unscored -- no preflight, no feature, no
|
|
98
|
+
# files written, no gate touched
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
That's a read-only look, not the gated workflow — for real feature work (collision-checked against
|
|
102
|
+
a specific idea, contract-gated, codegen), see below.
|
|
103
|
+
|
|
104
|
+
### The gated workflow
|
|
105
|
+
|
|
86
106
|
```bash
|
|
87
107
|
npm install -g backend-skeleton # or: npx backend-skeleton <command>
|
|
88
108
|
cd <target-repo> # must be a git repository
|
|
@@ -192,6 +212,27 @@ bskel new --stack fastapi --slug my-service \
|
|
|
192
212
|
The full parameter list, the measured API-validation matrix behind that split, and the warning
|
|
193
213
|
behaviour are in `D-greenfield-parameters` in `DECISIONS.md`.
|
|
194
214
|
|
|
215
|
+
#### Remembering your own conventions across projects (optional)
|
|
216
|
+
|
|
217
|
+
If you start several projects with the same conventions, `bskel new` can record them into a
|
|
218
|
+
database **you own** -- never bskel's own state, never a shared store:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
export MY_PATTERNS=postgres://localhost/my_patterns # once: run patterns/schema.sql against it
|
|
222
|
+
|
|
223
|
+
bskel new --stack spring --slug billing \
|
|
224
|
+
--java-version 21 --group-id com.acme --dependencies web,data-jpa,validation,flyway \
|
|
225
|
+
--record-pattern --pattern-database-url-env MY_PATTERNS
|
|
226
|
+
|
|
227
|
+
bskel pattern suggest --stack spring --pattern-database-url-env MY_PATTERNS
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`pattern suggest` prints what you've recorded, with per-value frequency, and a ready-to-paste
|
|
231
|
+
command line at the bottom -- it never runs `bskel new` for you and `bskel new` has no flag that
|
|
232
|
+
would accept a suggestion as a default. Every value in a generated project is still one you typed
|
|
233
|
+
in that invocation. Omitting `--record-pattern`/`--pattern-database-url-env` leaves `bskel new`
|
|
234
|
+
exactly as it is today. See `D-pattern-accrual` in `DECISIONS.md`.
|
|
235
|
+
|
|
195
236
|
### Publishing a feature's contract as OpenAPI (optional)
|
|
196
237
|
|
|
197
238
|
```bash
|
|
@@ -231,6 +272,31 @@ contract's paths don't reflect (`--allow-unprefixed` overrides), and stamps ever
|
|
|
231
272
|
reconciling a contract against its own export would make it confirm itself. See `D-openapi-export`
|
|
232
273
|
in `DECISIONS.md`.
|
|
233
274
|
|
|
275
|
+
### A CSV table of a feature's contract (optional)
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
bskel contract export-csv --feature 001-organization-management --out organization.csv
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
One row per operation, opened by someone who will never read a JSON Schema — a PM reviewing scope,
|
|
282
|
+
a lead deciding whether a partial contract is good enough to waive. Fourteen columns, always, in
|
|
283
|
+
the same order: `operation_id, verb, path, path_params, path_params_unverified, body,
|
|
284
|
+
request_body_required, request_body_fields, response_fields, error_fields, provenance, summary,
|
|
285
|
+
tags, security`.
|
|
286
|
+
|
|
287
|
+
**Every column is always present, even when every row leaves it blank** — a scan-only contract
|
|
288
|
+
(no `--openapi-file`) never states `summary`/`tags`/`security`, and the blank columns *are* that
|
|
289
|
+
finding, not something to hide: `export-csv` prints exactly which columns are empty for every
|
|
290
|
+
operation, and why, on stderr. Dropping an empty column would make the file's own shape depend on
|
|
291
|
+
its content, so it never does.
|
|
292
|
+
|
|
293
|
+
**Unlike `contract export`, this command is deliberately UNGATED** — it works even when the
|
|
294
|
+
`contract` gate hasn't passed yet, because its single most valuable moment is reviewing a partial
|
|
295
|
+
contract to decide whether to waive it. An unreflected global path-prefix signal (the thing
|
|
296
|
+
`contract export` hard-refuses on) downgrades to a stderr warning here instead. `--bom` prepends a
|
|
297
|
+
UTF-8 byte-order mark for Excel-on-Windows, which otherwise mangles non-ASCII text; omit it for
|
|
298
|
+
`pandas`/`csv.DictReader`/`diff`, which don't want one. See `D-contract-csv` in `DECISIONS.md`.
|
|
299
|
+
|
|
234
300
|
### Database schema (optional)
|
|
235
301
|
|
|
236
302
|
`bskel scan --db` additionally scans Flyway/Liquibase migration files (local only, no network).
|
|
@@ -248,6 +314,40 @@ report also carries `generated_at` — when the underlying data was actually cap
|
|
|
248
314
|
be judged for staleness rather than trusted blindly. See `D-cross-feature-fk-inference` in
|
|
249
315
|
`DECISIONS.md`.
|
|
250
316
|
|
|
317
|
+
### An ERD of your database schema (optional)
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
bskel db erd --database-url-env BSKEL_DB_URL --schema public --out schema.mmd
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
A Mermaid `erDiagram` of the database — paste it straight into a GitHub/GitLab/Notion/Obsidian
|
|
324
|
+
markdown file (fence it in \`\`\`mermaid) or [mermaid.live](https://mermaid.live) and it renders
|
|
325
|
+
with no extra tooling. Works two ways:
|
|
326
|
+
|
|
327
|
+
- **With `--database-url-env`**: a real, live Postgres introspection — full column types,
|
|
328
|
+
nullability, and primary/foreign keys.
|
|
329
|
+
- **Without it**: falls back to scanning Flyway/Liquibase `.sql` migration files (no network, no
|
|
330
|
+
credentials needed) — a real but **degraded** diagram, clearly marked as such in the file itself:
|
|
331
|
+
every column types as `unknown`, no `PK` badge appears anywhere, and a header block spells out
|
|
332
|
+
exactly what's missing. Useful for evaluating the tool on a repo you don't have DB credentials
|
|
333
|
+
for yet.
|
|
334
|
+
|
|
335
|
+
Two things this diagram deliberately does **not** guess:
|
|
336
|
+
|
|
337
|
+
- **Composite foreign keys.** Postgres's own `information_schema` doesn't retain which source
|
|
338
|
+
column pairs with which target column once a foreign key spans more than one column — the raw
|
|
339
|
+
data is a cross product that can include pairs that were never declared. Rather than draw a wrong
|
|
340
|
+
relationship line, a composite FK collapses to one line labeled with all its source columns
|
|
341
|
+
joined by `+`, with the ambiguity spelled out in a `%%` comment above it. (Composite *primary*
|
|
342
|
+
keys have no such problem and render fully.)
|
|
343
|
+
- **1:1 vs 1:N.** The child side of every relationship is drawn as "zero or more," never "exactly
|
|
344
|
+
one" — telling those apart needs a UNIQUE constraint check this tool doesn't perform. The header
|
|
345
|
+
says so.
|
|
346
|
+
|
|
347
|
+
Whole-schema only in this version (no `--feature`/`--tables` filtering yet) — for a very large
|
|
348
|
+
schema, `db erd` prints a note above 40 entities rather than silently producing an unreadable
|
|
349
|
+
diagram. See `D-db-erd` in `DECISIONS.md`.
|
|
350
|
+
|
|
251
351
|
### Applying DDL to a live database (optional)
|
|
252
352
|
|
|
253
353
|
`bskel patch propose --kind ddl-apply` extends the same propose/approve/apply/rollback lifecycle
|
|
@@ -344,6 +444,32 @@ bskel attest verify --file attestation.json --pubkey ~/.bskel-keys/attest-public
|
|
|
344
444
|
`attest verify`'s exit code reflects signature validity only — whether the gates inside actually
|
|
345
445
|
passed is a separate, printed summary. See `D-gate-attestation-signing` in `DECISIONS.md`.
|
|
346
446
|
|
|
447
|
+
### Signed observe receipts (optional)
|
|
448
|
+
|
|
449
|
+
`bskel observe emit` generates opt-in runtime middleware that checks real traffic against a
|
|
450
|
+
feature's contract and logs a verdict-only receipt per call (JSON Pointer + constraint kind, never
|
|
451
|
+
an observed value); `bskel observe import --receipts <path>` turns a stream of those receipts into
|
|
452
|
+
a committed report backing the `conformance` gate. By default a receipts file is trusted at face
|
|
453
|
+
value once it's structurally valid — a human could hand-fabricate one. Add `--pubkey <path>` to
|
|
454
|
+
`observe import` to verify each receipt's optional signature instead, reusing the same
|
|
455
|
+
`bskel attest keygen`-generated keypair signed gate attestations use:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
bskel attest keygen --out ~/.bskel-keys # same command as above -- one keypair, multiple uses
|
|
459
|
+
# then, per deployed app (one-time, at the app's own startup):
|
|
460
|
+
# TypeScript: import { setSigningKey } from './observe/receiptSign'; setSigningKey(pem);
|
|
461
|
+
# Java: set the bskel.observe.signing-key-pem Spring property (e.g. an env var)
|
|
462
|
+
# Python: receipt_sign.configure(os.environ.get("BSKEL_OBSERVE_SIGNING_KEY_PEM"))
|
|
463
|
+
bskel observe import --feature 001-organization-management --receipts receipts.jsonl \
|
|
464
|
+
--pubkey ~/.bskel-keys/attest-public.pem [--require-signature]
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Unset/no key configured means every receipt stays unsigned — fully backward compatible with every
|
|
468
|
+
app already using this feature. `--pubkey` alone verifies signatures where present and tolerates
|
|
469
|
+
unsigned receipts (excluding them from the report's `matched` counts, with a printed warning);
|
|
470
|
+
`--require-signature` makes any unsigned or invalid receipt abort the whole import. See the
|
|
471
|
+
"cryptographic receipt attestation" update in `D-runtime-conformance-receipts` in `DECISIONS.md`.
|
|
472
|
+
|
|
347
473
|
Every command is read-only until you explicitly run one of the mutating steps above — `bskel
|
|
348
474
|
status`/`bskel next` (no arguments needed) tell you which gate is next and print the exact
|
|
349
475
|
copy-pasteable command for it, without touching anything.
|
|
@@ -358,7 +484,7 @@ below).
|
|
|
358
484
|
|---|---|---|
|
|
359
485
|
| Node.js | `>=18` | ES2022 (`Object.hasOwn`) + ESM top-level `await` — nothing newer is used anywhere in the runtime code (verified by grep across every recent-ES-addition pattern; see `D-npm-packaging` in `DECISIONS.md`) |
|
|
360
486
|
| git | required | every gate is git-state-derived |
|
|
361
|
-
| [ripgrep](https://github.com/BurntSushi/ripgrep) (`rg`) | required for `scan`/`handles` | every scanner adapter shells out to it
|
|
487
|
+
| [ripgrep](https://github.com/BurntSushi/ripgrep) (`rg`) | required for `scan`/`handles` | every scanner adapter shells out to it at `detect()` time behind a blanket try/catch — traced live, missing `rg` does NOT throw, it silently makes every real adapter detect nothing (degrades to the low-confidence `generic-grep` fallback). `bskel scan`'s report now carries a `rg_available: false` field plus an explicit `unknowns` warning whenever this happens, so it stays distinguishable from a genuinely-unrecognized repo — see `D-zero-config-scan` in `DECISIONS.md` |
|
|
362
488
|
| `gh` (GitHub CLI) | optional | only used for `preflight`'s 3-way default-branch cross-check; already soft-guarded, never a hard requirement |
|
|
363
489
|
| `python3` | optional | only needed to run this repository's own cross-language codec test — `bskel` itself never invokes `python3` |
|
|
364
490
|
| a build wrapper (`gradlew`/`pom.xml`+`mvnw`/`package.json`) | optional | only `bskel verify --build` needs one; `handles emit` never compiles anything itself |
|