backend-skeleton 1.0.0-beta.8 → 1.0.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 +122 -12
- package/bin/bskel.mjs +738 -12
- package/contracts/completeness.mjs +10 -0
- package/contracts/emit.mjs +5 -1
- package/contracts/export.mjs +26 -3
- package/contracts/openapi.mjs +29 -3
- package/handles/_engine.mjs +79 -29
- package/handles/providers/java-spring/plan.mjs +22 -9
- package/handles/providers/java-spring/templates/HandleController.java.tmpl +7 -3
- package/handles/providers/java-spring/templates/ResourceResolver.java.tmpl +12 -6
- package/handles/providers/python-fastapi/templates/record_snapshot.py.tmpl +101 -39
- package/handles/providers/typescript-express/emit.mjs +23 -23
- package/handles/providers/typescript-express/observe.mjs +101 -0
- package/handles/providers/typescript-express/templates/contractCheck.ts.tmpl +136 -0
- package/handles/providers/typescript-express/templates/observeContract.ts.tmpl +146 -0
- package/handles/providers/typescript-express/templates/observedSchema.ts.tmpl +116 -0
- package/lib/attest.mjs +40 -0
- package/lib/cli.mjs +169 -2
- package/lib/cross-feature-collisions.mjs +286 -0
- package/lib/diff.mjs +35 -0
- package/lib/field-dependencies.mjs +355 -0
- package/lib/fsutil.mjs +7 -2
- package/lib/gate-definitions.mjs +117 -1
- package/lib/gates.mjs +5 -1
- package/lib/http-server.mjs +358 -0
- package/lib/lock.mjs +68 -15
- package/lib/patch-kinds.mjs +52 -0
- package/lib/patch-transactions.mjs +206 -0
- package/lib/serve-ui.html +328 -0
- package/lib/workflow.mjs +39 -3
- package/package.json +5 -2
- package/scanners/adapters/java-spring.mjs +6 -0
- package/scanners/adapters/python-fastapi.mjs +9 -1
- package/scanners/adapters/typescript-express.mjs +6 -0
- package/scanners/db/ddl-apply.mjs +253 -0
- package/scanners/db/introspect.mjs +61 -32
- package/scanners/db/migrations.mjs +73 -18
- package/schemas/cross-feature-report.schema.json +66 -0
- package/schemas/cross-feature-resolution.schema.json +28 -0
- package/schemas/field-dependency.schema.json +49 -0
- package/schemas/gate-attestation.schema.json +22 -0
- package/schemas/gate-export.schema.json +58 -0
- package/schemas/patch-transaction.schema.json +182 -0
- package/schemas/scan-report.schema.json +6 -4
- package/schemas/stack-choice.schema.json +12 -1
- package/stack/apply.mjs +4 -1
- package/stack/catalog/ngrok.yml +8 -2
- package/stack/config-apply.mjs +168 -0
package/README.md
CHANGED
|
@@ -23,10 +23,20 @@ of just another thing to double-check by hand.
|
|
|
23
23
|
commits behind the real default branch and never noticed. Every gate in this tool is a regression
|
|
24
24
|
check for a specific failure mode found the same way — see `DECISIONS.md` for the full record.
|
|
25
25
|
|
|
26
|
-
## Status:
|
|
26
|
+
## Status: 1.0.0
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
As of `1.0.0`, this project makes an explicit API-stability promise: **`bskel`'s CLI surface --
|
|
29
|
+
command/flag names and their meaning, every `--json` output shape (all schemas under `schemas/`),
|
|
30
|
+
gate names and pass/fail semantics, and exit codes -- is stable.** A change that breaks any of
|
|
31
|
+
those requires a major version bump, never a patch or minor release; a new command, a new optional
|
|
32
|
+
flag, or a new additive field on an existing JSON shape is always minor-version-safe (every schema
|
|
33
|
+
under `schemas/` is `additionalProperties: false` specifically so a genuinely new field shows up as
|
|
34
|
+
a real, visible schema change rather than something an existing consumer could silently miss). See
|
|
35
|
+
`D-stable-api-contract` in `DECISIONS.md` for the full policy and what's explicitly excluded from
|
|
36
|
+
it.
|
|
37
|
+
|
|
38
|
+
That promise is about the *interface*, not a claim that every subsystem has been proven in
|
|
39
|
+
production -- which still splits the same way it always has:
|
|
30
40
|
|
|
31
41
|
- **`scan`, `contract` (including `export`), and `new`** are the most exercised paths — real,
|
|
32
42
|
measured verification against a real production Spring Boot repo (see `DECISIONS.md`), plus a
|
|
@@ -38,19 +48,17 @@ stable. Split by actual maturity, not by feature list:
|
|
|
38
48
|
independently correct roles instead of silently sharing one) are both implemented -- see
|
|
39
49
|
`DECISIONS.md`'s `D-handle-registry-enforcement`/`D-resolver-authorization-action-aware`. Real
|
|
40
50
|
gaps remain and are explicitly still open, not closed: registry enforcement is opt-in, off by
|
|
41
|
-
default; authorization inference
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Version numbers, install instructions, and a real feedback path will firm up as this gets used
|
|
48
|
-
against more real repos.
|
|
51
|
+
default; authorization inference now recognizes both `@PreAuthorize(hasRole(...))` and
|
|
52
|
+
`hasAuthority(...)` (see `DECISIONS.md`'s `D-resolver-authorization-action-aware`), but
|
|
53
|
+
`hasAnyRole`/`hasAnyAuthority` (list-shape), ownership, and tenant policy are still unaddressed;
|
|
54
|
+
and Java/Python are the only providers either applies to -- TypeScript Express has no persistent
|
|
55
|
+
handle table at all. Treat `handles emit`'s output as a scaffold to finish by hand, not a
|
|
56
|
+
production-ready subsystem, until a real deployment happens.
|
|
49
57
|
|
|
50
58
|
## Quickstart
|
|
51
59
|
|
|
52
60
|
```bash
|
|
53
|
-
npm install -g backend-skeleton
|
|
61
|
+
npm install -g backend-skeleton # or: npx backend-skeleton <command>
|
|
54
62
|
cd <target-repo> # must be a git repository
|
|
55
63
|
|
|
56
64
|
bskel doctor # what's on PATH, which scanner adapter detects this repo, and why
|
|
@@ -66,6 +74,9 @@ bskel scan disposition --feature 001-organization-management --mode reuse --note
|
|
|
66
74
|
|
|
67
75
|
bskel contract emit --feature 001-organization-management
|
|
68
76
|
# feature_id-scoped JSON Schema contract, from real source annotations
|
|
77
|
+
bskel scan cross-feature-check --feature 001-organization-management
|
|
78
|
+
# refuses to proceed if this feature's resourceType/table/operationId
|
|
79
|
+
# collides with another feature -- required before handles emit
|
|
69
80
|
bskel handles plan --feature 001-organization-management
|
|
70
81
|
bskel handles emit --feature 001-organization-management
|
|
71
82
|
# UUID-addressable field handles + generated resolver code
|
|
@@ -172,6 +183,105 @@ read from `.env`) for live, read-only Postgres introspection (`information_schem
|
|
|
172
183
|
inside a `BEGIN TRANSACTION READ ONLY`) and a source-vs-live drift report. Both are informational
|
|
173
184
|
additions to the scan report — neither blocks any gate. See `D-db-schema-plane` in `DECISIONS.md`.
|
|
174
185
|
|
|
186
|
+
The same `--db`/`--database-url-env` flags, passed to `bskel scan cross-feature-check`, add a 4th
|
|
187
|
+
collision signal: a real live (or migration-file-derived) Postgres foreign-key edge whose two
|
|
188
|
+
tables are declared by two *different* features surfaces as a `db_foreign_key` finding, direction-
|
|
189
|
+
and confidence-scored the same way the existing NAME-identity signals are. Every `fk_check` in the
|
|
190
|
+
report also carries `generated_at` — when the underlying data was actually captured, so a
|
|
191
|
+
`persisted`/`migrations`-mode correlation (reused from an earlier scan, not a fresh connection) can
|
|
192
|
+
be judged for staleness rather than trusted blindly. See `D-cross-feature-fk-inference` in
|
|
193
|
+
`DECISIONS.md`.
|
|
194
|
+
|
|
195
|
+
### Applying DDL to a live database (optional)
|
|
196
|
+
|
|
197
|
+
`bskel patch propose --kind ddl-apply` extends the same propose/approve/apply/rollback lifecycle
|
|
198
|
+
`config_apply` uses to a second kind: hand-authored `CREATE`/`ALTER`/`DROP TABLE`/`INDEX`/`SCHEMA`
|
|
199
|
+
statements, run inside a real Postgres transaction and only `COMMIT`ted once the introspected
|
|
200
|
+
schema actually matches the declared postcondition — anything else `ROLLBACK`s automatically, never
|
|
201
|
+
partially applies:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
bskel patch propose --feature 001-organization-management --kind ddl-apply \
|
|
205
|
+
--database-url-env BSKEL_DB_URL --sql-file migrations/add_tax_rate.sql --schema public
|
|
206
|
+
bskel patch approve --feature 001-organization-management --transaction <id> --reason "..."
|
|
207
|
+
bskel patch apply --feature 001-organization-management --transaction <id>
|
|
208
|
+
# a transaction that DROPs one or more tables requires retyping the sorted, comma-joined table
|
|
209
|
+
# name(s) as --confirm instead of the transaction id -- the same "type the resource name to
|
|
210
|
+
# delete" pattern GitHub uses for its own irreversible actions
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Rollback of an *applied* `ddl-apply` transaction is refused outright by design — the only path back
|
|
214
|
+
is a new, forward transaction with hand-written reverse DDL, never an automated revert. The
|
|
215
|
+
allowlist structurally excludes anything that can't run inside a transaction block (e.g. `CREATE
|
|
216
|
+
INDEX CONCURRENTLY`) and anything outside `TABLE`/`INDEX`/`SCHEMA` DDL. `bskel serve
|
|
217
|
+
--database-url-env <NAME> [--sign-key <path>] [--require-sign-key]` exposes the same lifecycle
|
|
218
|
+
through the browser UI's own propose/approve/apply routes; `--require-sign-key` refuses to start
|
|
219
|
+
the DDL surface at all unless a signing key was also given. See `D-ddl-apply` in `DECISIONS.md` for
|
|
220
|
+
the full design and every explicitly-deferred boundary (non-Postgres databases, connection
|
|
221
|
+
pooling, production safety rails).
|
|
222
|
+
|
|
223
|
+
### Declaring field-to-field dependencies (optional)
|
|
224
|
+
|
|
225
|
+
When one feature's data actually depends on another feature's (e.g. a `WidgetDto.name` that's
|
|
226
|
+
populated from `OrganizationDto.taxRate`), `bskel dependency declare` records that link and passes
|
|
227
|
+
the `dependencies` gate for it. Declaring a dependency also warns the *source* feature next time
|
|
228
|
+
someone re-runs `bskel status`/`bskel next` there, so a downstream consumer doesn't silently break:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
bskel dependency declare --feature 001-widget-management --resource WidgetDto --field name \
|
|
232
|
+
--source-feature 002-organization-management --source-resource OrganizationDto --source-field taxRate \
|
|
233
|
+
--reason "widget display name mirrors the owning org's rate tier" [--memo "..."]
|
|
234
|
+
bskel dependency list --feature 001-widget-management --json
|
|
235
|
+
bskel dependency remove --feature 001-widget-management --resource WidgetDto --field name \
|
|
236
|
+
--source-feature 002-organization-management --source-resource OrganizationDto --source-field taxRate \
|
|
237
|
+
--reason "no longer coupled"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`bskel serve [--port N] [--host <addr>]` starts a small local HTTP server (loopback-only by
|
|
241
|
+
default, matching this project's "safe default, explicit override" convention) that serves a
|
|
242
|
+
read-only browser UI at `/` for the whole repo's dependency graph, backed by `GET /api/graph`. The
|
|
243
|
+
UI's own POST/DELETE calls to `/api/features/:id/dependencies` go through the exact same
|
|
244
|
+
`declareDependency`/`removeDependency` functions the CLI above uses — nothing is duplicated
|
|
245
|
+
between the two — and, like every other mutating command in this project, both take a JSON body,
|
|
246
|
+
not query parameters. `GET`/`HEAD` responses carry `Access-Control-Allow-Origin: *`; the mutating
|
|
247
|
+
routes never do, so only same-origin requests (the bundled UI itself) can write.
|
|
248
|
+
|
|
249
|
+
### Patching a config file (optional)
|
|
250
|
+
|
|
251
|
+
`bskel stack apply`'s `config_check` sometimes reports `needs-manual-patch` — a target file exists
|
|
252
|
+
but isn't wired up the way the chosen stack (e.g. `ngrok`) needs. `bskel patch propose/approve/
|
|
253
|
+
apply/rollback` closes that gap for the catalog entries that declare a machine-applicable fix,
|
|
254
|
+
using a comment-preserving edit with a content-addressed preimage check (refuses to apply if the
|
|
255
|
+
target changed since you approved it) and a real rollback:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
bskel patch propose --feature 001-organization-management --choice ngrok \
|
|
259
|
+
--target src/main/resources/application.yaml
|
|
260
|
+
bskel patch approve --feature 001-organization-management --transaction <id> --reason "..."
|
|
261
|
+
bskel patch apply --feature 001-organization-management --transaction <id>
|
|
262
|
+
# ...or, to undo: bskel patch rollback --feature 001-organization-management --transaction <id> --reason "..."
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`bskel patch list --feature <id>` shows every transaction and its status. See
|
|
266
|
+
`D-patch-transactions` in `DECISIONS.md`.
|
|
267
|
+
|
|
268
|
+
### Signed gate attestations (optional)
|
|
269
|
+
|
|
270
|
+
`bskel gate export` already produces a CI-independent report of every gate's current status. Add
|
|
271
|
+
`--sign --key <privateKeyPath>` to detached-sign it (Ed25519, via Node's own `crypto` module — no
|
|
272
|
+
new dependency), then verify it offline, on any machine, without network access or trusting
|
|
273
|
+
whatever produced it:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
bskel attest keygen --out ~/.bskel-keys # writes attest-private.pem (0600) + attest-public.pem
|
|
277
|
+
bskel gate export --feature 001-organization-management \
|
|
278
|
+
--sign --key ~/.bskel-keys/attest-private.pem --out attestation.json
|
|
279
|
+
bskel attest verify --file attestation.json --pubkey ~/.bskel-keys/attest-public.pem
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`attest verify`'s exit code reflects signature validity only — whether the gates inside actually
|
|
283
|
+
passed is a separate, printed summary. See `D-gate-attestation-signing` in `DECISIONS.md`.
|
|
284
|
+
|
|
175
285
|
Every command is read-only until you explicitly run one of the mutating steps above — `bskel
|
|
176
286
|
status`/`bskel next` (no arguments needed) tell you which gate is next and print the exact
|
|
177
287
|
copy-pasteable command for it, without touching anything.
|