backend-skeleton 1.0.0-beta.1
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/LICENSE +21 -0
- package/README.md +284 -0
- package/bin/bskel.mjs +2384 -0
- package/contracts/completeness.mjs +176 -0
- package/contracts/emit.mjs +287 -0
- package/contracts/export.mjs +325 -0
- package/contracts/openapi.mjs +869 -0
- package/contracts/validate.mjs +147 -0
- package/handles/_engine.mjs +281 -0
- package/handles/codec.mjs +119 -0
- package/handles/conformance.mjs +74 -0
- package/handles/providers/java-spring/ast-bridge.mjs +59 -0
- package/handles/providers/java-spring/ast-helper/build.gradle +34 -0
- package/handles/providers/java-spring/ast-helper/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/handles/providers/java-spring/ast-helper/gradle/wrapper/gradle-wrapper.properties +9 -0
- package/handles/providers/java-spring/ast-helper/gradlew +248 -0
- package/handles/providers/java-spring/ast-helper/gradlew.bat +82 -0
- package/handles/providers/java-spring/ast-helper/settings.gradle +1 -0
- package/handles/providers/java-spring/ast-helper/src/main/java/com/backendskeleton/asthelper/Main.java +178 -0
- package/handles/providers/java-spring/emit.mjs +232 -0
- package/handles/providers/java-spring/patch-strategy.mjs +229 -0
- package/handles/providers/java-spring/plan.mjs +377 -0
- package/handles/providers/java-spring/templates/HandleAspect.java.tmpl +125 -0
- package/handles/providers/java-spring/templates/HandleCodec.java.tmpl +150 -0
- package/handles/providers/java-spring/templates/HandleController.java.tmpl +177 -0
- package/handles/providers/java-spring/templates/HandleRegistry.java.tmpl +107 -0
- package/handles/providers/java-spring/templates/HandleRegistryRepository.java.tmpl +8 -0
- package/handles/providers/java-spring/templates/HandleService.java.tmpl +95 -0
- package/handles/providers/java-spring/templates/HandleSnapshot.java.tmpl +75 -0
- package/handles/providers/java-spring/templates/HandleSnapshotRepository.java.tmpl +20 -0
- package/handles/providers/java-spring/templates/RecordHandleSnapshot.java.tmpl +50 -0
- package/handles/providers/java-spring/templates/ResourceResolver.java.tmpl +50 -0
- package/handles/providers/java-spring/templates/ResourceResolverStub.java.tmpl +77 -0
- package/handles/providers/java-spring/templates/migration.sql.tmpl +34 -0
- package/handles/providers/java-spring.mjs +21 -0
- package/handles/providers/python-fastapi/emit.mjs +171 -0
- package/handles/providers/python-fastapi/plan.mjs +186 -0
- package/handles/providers/python-fastapi/templates/__init__.py.tmpl +1 -0
- package/handles/providers/python-fastapi/templates/codec.py.tmpl +122 -0
- package/handles/providers/python-fastapi/templates/handle_service.py.tmpl +96 -0
- package/handles/providers/python-fastapi/templates/migration.sql.tmpl +35 -0
- package/handles/providers/python-fastapi/templates/record_snapshot.py.tmpl +155 -0
- package/handles/providers/python-fastapi/templates/registry.py.tmpl +37 -0
- package/handles/providers/python-fastapi/templates/resolver.py.tmpl +59 -0
- package/handles/providers/python-fastapi/templates/resolvers_init.py.tmpl +13 -0
- package/handles/providers/python-fastapi/templates/router.py.tmpl +140 -0
- package/handles/providers/python-fastapi/templates/tables.py.tmpl +66 -0
- package/handles/providers/python-fastapi.mjs +22 -0
- package/handles/providers/typescript-express/emit.mjs +128 -0
- package/handles/providers/typescript-express/plan.mjs +234 -0
- package/handles/providers/typescript-express/templates/codec.ts.tmpl +116 -0
- package/handles/providers/typescript-express/templates/registry.ts.tmpl +39 -0
- package/handles/providers/typescript-express/templates/resolver.ts.tmpl +55 -0
- package/handles/providers/typescript-express/templates/resolvers_index.ts.tmpl +11 -0
- package/handles/providers/typescript-express/templates/router.ts.tmpl +122 -0
- package/handles/providers/typescript-express.mjs +20 -0
- package/handles/registry.mjs +90 -0
- package/lib/cli.mjs +430 -0
- package/lib/doctor.mjs +200 -0
- package/lib/exit-codes.mjs +67 -0
- package/lib/featureid.mjs +55 -0
- package/lib/featurelifecycle.mjs +205 -0
- package/lib/fsutil.mjs +50 -0
- package/lib/gate-definitions.mjs +293 -0
- package/lib/gates.mjs +263 -0
- package/lib/handles-manifest.mjs +92 -0
- package/lib/lock.mjs +68 -0
- package/lib/patch-approvals.mjs +56 -0
- package/lib/paths.mjs +21 -0
- package/lib/repo.mjs +44 -0
- package/lib/schema-validate.mjs +56 -0
- package/lib/state.mjs +124 -0
- package/lib/template.mjs +35 -0
- package/lib/verify.mjs +206 -0
- package/lib/workflow.mjs +142 -0
- package/new/fastapi.mjs +165 -0
- package/new/index.mjs +62 -0
- package/new/params.mjs +233 -0
- package/new/spring.mjs +198 -0
- package/new/templates/fastapi/README.md +26 -0
- package/new/templates/fastapi/app/__init__.py +0 -0
- package/new/templates/fastapi/app/main.py +8 -0
- package/new/templates/fastapi/gitignore +6 -0
- package/new/templates/fastapi/pyproject.toml +14 -0
- package/package.json +50 -0
- package/scanners/adapters/_express-shared.mjs +238 -0
- package/scanners/adapters/_java-spring-analyzer.mjs +273 -0
- package/scanners/adapters/generic-grep.mjs +128 -0
- package/scanners/adapters/java-spring.mjs +301 -0
- package/scanners/adapters/javascript-express.mjs +422 -0
- package/scanners/adapters/python-fastapi.mjs +348 -0
- package/scanners/adapters/typescript-express.mjs +299 -0
- package/scanners/capabilities.mjs +90 -0
- package/scanners/conformance.mjs +59 -0
- package/scanners/db/introspect.mjs +109 -0
- package/scanners/db/migrations.mjs +126 -0
- package/scanners/index.mjs +281 -0
- package/scanners/registry.mjs +130 -0
- package/scanners/render.mjs +136 -0
- package/scanners/text-util.mjs +8 -0
- package/schemas/adapter.schema.json +23 -0
- package/schemas/agent-envelope.schema.json +21 -0
- package/schemas/contract-resolution.schema.json +28 -0
- package/schemas/feature-contract.schema.json +78 -0
- package/schemas/feature-index.schema.json +25 -0
- package/schemas/feature.schema.json +17 -0
- package/schemas/gate-event.schema.json +19 -0
- package/schemas/handles-plan.schema.json +31 -0
- package/schemas/handles-provider.schema.json +26 -0
- package/schemas/patch-approvals.schema.json +28 -0
- package/schemas/scan-report.schema.json +102 -0
- package/schemas/stack-choice.schema.json +89 -0
- package/schemas/stack-record.schema.json +20 -0
- package/schemas/state.schema.json +43 -0
- package/scripts/preflight-base-ref.sh +226 -0
- package/stack/apply.mjs +159 -0
- package/stack/bootstrap/_lib.sh +73 -0
- package/stack/bootstrap/ngrok.sh +90 -0
- package/stack/catalog/ngrok.yml +63 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:sbf:feature-contract:1",
|
|
4
|
+
"title": "backend-skeleton per-feature contract (meta-schema)",
|
|
5
|
+
"description": "Validates the SHAPE of specs/<feature_id>/contracts/<feature_id>.schema.json itself -- not a payload. See contracts/validate.mjs for payload validation against a specific operation inside a contract that already passed this meta-schema.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["sbf_contract", "feature_id", "feature_uid", "source", "operations", "warnings", "completeness"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"sbf_contract": { "const": "4" },
|
|
11
|
+
"feature_id": { "type": "string", "pattern": "^[0-9]{3}-[a-z0-9]+(-[a-z0-9]+)*$" },
|
|
12
|
+
"feature_uid": { "type": "string", "format": "uuid" },
|
|
13
|
+
"source": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"required": ["adapter", "module", "provenance"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"adapter": { "type": ["string", "null"] },
|
|
18
|
+
"module": { "type": ["string", "null"] },
|
|
19
|
+
"provenance": { "type": "string" }
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"operations": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"additionalProperties": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"additionalProperties": false,
|
|
27
|
+
"required": ["verb", "path", "pathParams", "body", "provenance"],
|
|
28
|
+
"properties": {
|
|
29
|
+
"verb": { "type": "string" },
|
|
30
|
+
"path": { "type": "string" },
|
|
31
|
+
"pathParams": { "type": "object" },
|
|
32
|
+
"body": { "enum": [true, false, "unknown"] },
|
|
33
|
+
"provenance": { "type": "string" },
|
|
34
|
+
"requestBodySchema": {
|
|
35
|
+
"description": "A2: the operation's request body projected from a real OpenAPI 3.1 document, fully inlined (no $ref) -- see contracts/openapi.mjs's inlineSchema(). Present only when reconciliation matched/adopted this operation AND its application/json schema resolved; omitted otherwise. Not deeply validated as 'is this a valid JSON Schema' here -- that would need a schema-of-schemas, out of scope.",
|
|
36
|
+
"type": "object"
|
|
37
|
+
},
|
|
38
|
+
"requestBodyRequired": { "type": "boolean" },
|
|
39
|
+
"responseSchema": {
|
|
40
|
+
"description": "A3: all documented 2xx application/json response schemas for this operation, fully inlined (no $ref); an anyOf union when 2+ distinct shapes are documented. Present only when matched/adopted AND at least one resolved.",
|
|
41
|
+
"type": "object"
|
|
42
|
+
},
|
|
43
|
+
"errorSchema": {
|
|
44
|
+
"description": "A3: all documented 4xx/5xx application/json response schemas for this operation, fully inlined (no $ref); an anyOf union when 2+ distinct shapes are documented. Present only when matched/adopted AND at least one resolved.",
|
|
45
|
+
"type": "object"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"warnings": {
|
|
51
|
+
"description": "A5: structured, not bare strings -- see contracts/completeness.mjs's WARNING_CODES for the known code table.",
|
|
52
|
+
"type": "array",
|
|
53
|
+
"items": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"additionalProperties": false,
|
|
56
|
+
"required": ["code", "severity", "subject", "message", "detail"],
|
|
57
|
+
"properties": {
|
|
58
|
+
"code": { "type": "string", "pattern": "^CONTRACT_[A-Z_]+$" },
|
|
59
|
+
"severity": { "enum": ["error", "warn"] },
|
|
60
|
+
"subject": { "type": ["string", "null"] },
|
|
61
|
+
"message": { "type": "string" },
|
|
62
|
+
"detail": { "type": "object" }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"completeness": {
|
|
67
|
+
"description": "A5: derived from operations/warnings alone, independent of any recorded waiver -- see contracts/completeness.mjs's classifyContract().",
|
|
68
|
+
"type": "object",
|
|
69
|
+
"additionalProperties": false,
|
|
70
|
+
"required": ["status", "operation_count", "endpoint_count"],
|
|
71
|
+
"properties": {
|
|
72
|
+
"status": { "enum": ["complete", "partial", "blocked"] },
|
|
73
|
+
"operation_count": { "type": "integer", "minimum": 0 },
|
|
74
|
+
"endpoint_count": { "type": "integer", "minimum": 0 }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:sbf:feature-index:1",
|
|
4
|
+
"title": "backend-skeleton feature index (.sbf/feature-index.json)",
|
|
5
|
+
"description": "D6 (D-feature-lifecycle): first real schema for this file -- previously unvalidated. `by_uid[uid]` was already an array (single-element only, written by `feature init`) -- D6 is its first real consumer: `bskel feature rename` appends rather than replaces, so the array IS the rename history (last entry is the current feature_id). `merged_into` is new, written by `bskel feature link` -- index-only, never implies the aliased feature_id's own specs/.sbf/ artifacts were touched or merged.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schema", "by_uid"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schema": { "const": "sbf.feature-index/1" },
|
|
11
|
+
"by_uid": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": { "type": "string", "pattern": "^[0-9]{3}-[a-z0-9]+(-[a-z0-9]+)*$" },
|
|
16
|
+
"minItems": 1
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"merged_into": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"description": "written by `bskel feature link <keepId> <aliasId>` -- maps aliasId -> keepId. Deliberately does not imply aliasId's own specs/.sbf/ files were touched; a human decides what to do with the two features' actual content.",
|
|
22
|
+
"additionalProperties": { "type": "string", "pattern": "^[0-9]{3}-[a-z0-9]+(-[a-z0-9]+)*$" }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:sbf:feature:1",
|
|
4
|
+
"title": "backend-skeleton feature record (specs/<feature_id>/feature.json)",
|
|
5
|
+
"description": "D6 (D-feature-lifecycle): first real schema for this file -- previously unvalidated. archived_at/archived_reason are new, added alongside `bskel feature archive`.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schema", "feature_id", "feature_uid", "created_at"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schema": { "const": "sbf.feature/1" },
|
|
11
|
+
"feature_id": { "type": "string", "pattern": "^[0-9]{3}-[a-z0-9]+(-[a-z0-9]+)*$" },
|
|
12
|
+
"feature_uid": { "type": "string", "format": "uuid" },
|
|
13
|
+
"created_at": { "type": "string" },
|
|
14
|
+
"archived_at": { "type": "string", "description": "set by `bskel feature archive` -- a soft-delete marker, not a filesystem move. Present iff archived_reason is." },
|
|
15
|
+
"archived_reason": { "type": "string", "description": "required alongside archived_at -- every archive is auditable, matching this project's --force/--reason convention elsewhere (O2, S4)." }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:sbf:gate-event:1",
|
|
4
|
+
"title": "backend-skeleton gate history event",
|
|
5
|
+
"description": "One line of a .sbf/<feature>.history.jsonl file, appended by lib/state.mjs's setGate() every time a gate's current-state snapshot changes. Deliberately does NOT include a 'stale' event -- staleness is derived at READ time (lib/gates.mjs's requireGate()), never written to disk, so there is nothing to log an event for; only the 4 real write-time transitions (pass/awaiting_disposition/force/revoke) appear here. See D-gate-history (S4) in DECISIONS.md.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schema", "event", "gate", "at"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schema": { "const": "sbf.gate-event/1" },
|
|
11
|
+
"event": { "enum": ["pass", "awaiting_disposition", "force", "revoke"] },
|
|
12
|
+
"gate": { "type": "string" },
|
|
13
|
+
"at": { "type": "string", "format": "date-time" },
|
|
14
|
+
"status": { "type": "string" },
|
|
15
|
+
"token": { "type": ["string", "null"] },
|
|
16
|
+
"forced": { "type": "boolean" },
|
|
17
|
+
"reason": { "type": ["string", "null"] }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:sbf:handles-plan:1",
|
|
4
|
+
"title": "backend-skeleton framework-neutral handles plan",
|
|
5
|
+
"description": "Validates the output of a handles provider's plan(). Top-level and resource objects both allow additionalProperties: true so each provider can carry its own extra fields (Java: service/fetchOperation; Python: publicModel/modelImport/fetchRoute/sessionDep) alongside the neutral subset the CLI renderer and dispatch layer consume. See D-handles-providers in DECISIONS.md.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"required": ["schema", "provider", "module", "resources", "notes"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schema": { "const": "sbf.handles-plan/1" },
|
|
11
|
+
"provider": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
|
12
|
+
"module": { "type": ["string", "null"] },
|
|
13
|
+
"resources": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"additionalProperties": true,
|
|
18
|
+
"required": ["type", "table", "idField", "readPath", "requiredAuthority", "willGenerateResolver"],
|
|
19
|
+
"properties": {
|
|
20
|
+
"type": { "type": "string" },
|
|
21
|
+
"table": { "type": ["string", "null"] },
|
|
22
|
+
"idField": { "type": ["string", "null"] },
|
|
23
|
+
"readPath": { "type": ["string", "null"] },
|
|
24
|
+
"requiredAuthority": { "type": "string" },
|
|
25
|
+
"willGenerateResolver": { "type": "boolean" }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"notes": { "type": "array", "items": { "type": "string" } }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:sbf:handles-provider:1",
|
|
4
|
+
"title": "backend-skeleton handles codegen provider descriptor",
|
|
5
|
+
"description": "Validates the JSON-shaped fields of the `provider` object a handles/providers/<id>.mjs module must export (plan/emit are functions, checked separately by handles/registry.mjs since a schema can't validate a function). Loaded at process start. See D-handles-providers in DECISIONS.md.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["contract", "id", "title", "requiresCapabilities", "outputs"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"contract": { "const": "sbf.handles-provider/1" },
|
|
11
|
+
"id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
|
12
|
+
"title": { "type": "string", "minLength": 1 },
|
|
13
|
+
"requiresCapabilities": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": { "enum": ["api.operations", "api.request-shape", "resource.fetch", "codegen.handles"] }
|
|
16
|
+
},
|
|
17
|
+
"outputs": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"additionalProperties": false,
|
|
20
|
+
"required": ["spec"],
|
|
21
|
+
"properties": {
|
|
22
|
+
"spec": { "type": "array", "items": { "type": "string" } }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:sbf:patch-approvals:1",
|
|
4
|
+
"title": "backend-skeleton patch-strategy approvals (specs/<feature_id>/handles/patch-approvals.json)",
|
|
5
|
+
"description": "A3 (D-patch-strategy): written by `bskel handles patch approve`. Each entry is a specific {resource, field} pair, never a wildcard -- mirrors contract-resolution.schema.json's own no-wildcard-waiver precedent (A5). Only patch-wrapper/null-means-unchanged strategies can be approved (fetch-merge-submit/unsupported are never auto-generated, approving them would have no effect) -- enforced by `bskel handles patch approve` itself, not by this schema, so a hand-edited file with an unexpected strategy string still fails validation loudly rather than silently doing nothing.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schema", "feature_id", "approvals"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schema": { "const": "sbf.patch-approvals/1" },
|
|
11
|
+
"feature_id": { "type": "string", "pattern": "^[0-9]{3}-[a-z0-9]+(-[a-z0-9]+)*$" },
|
|
12
|
+
"approvals": {
|
|
13
|
+
"type": "array",
|
|
14
|
+
"items": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"additionalProperties": false,
|
|
17
|
+
"required": ["resource", "field", "strategy", "reason", "at"],
|
|
18
|
+
"properties": {
|
|
19
|
+
"resource": { "type": "string" },
|
|
20
|
+
"field": { "type": "string" },
|
|
21
|
+
"strategy": { "enum": ["patch-wrapper", "null-means-unchanged"] },
|
|
22
|
+
"reason": { "type": "string" },
|
|
23
|
+
"at": { "type": "string", "format": "date-time" }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:sbf:scan-report:1",
|
|
4
|
+
"title": "backend-skeleton brownfield scan report",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema", "terms", "adapter", "verdict", "related_modules", "collisions", "unknowns", "files_read"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema": { "const": "sbf.scan-report/1" },
|
|
10
|
+
"feature_id": { "type": "string" },
|
|
11
|
+
"terms": { "type": "array", "items": { "type": "string" } },
|
|
12
|
+
"adapter": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
|
13
|
+
"confidence": { "enum": ["high", "low"] },
|
|
14
|
+
"api_surface_source": { "type": "string" },
|
|
15
|
+
"verdict": { "enum": ["greenfield", "adjacent", "collision"] },
|
|
16
|
+
"path_prefix_signals": { "type": "array" },
|
|
17
|
+
"related_modules": {
|
|
18
|
+
"type": "array",
|
|
19
|
+
"items": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"required": ["module", "score"],
|
|
22
|
+
"properties": {
|
|
23
|
+
"module": { "type": "string" },
|
|
24
|
+
"score": { "type": "number" },
|
|
25
|
+
"controllers": { "type": "array" },
|
|
26
|
+
"entities": { "type": "array" },
|
|
27
|
+
"enums": { "type": "array" },
|
|
28
|
+
"dtos": { "type": "array", "items": { "type": "string" } },
|
|
29
|
+
"evidence": {
|
|
30
|
+
"description": "D-scanner-evidence: every {signal, term, value, weight, file, line} match that contributed to `score` -- capped per signal type AT COLLECTION TIME (not filtered afterward), so this array's size stays bounded regardless of repo size. See `capped_signals` for which signal types actually hit that cap.",
|
|
31
|
+
"type": "array",
|
|
32
|
+
"items": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"additionalProperties": false,
|
|
35
|
+
"required": ["signal", "term", "value", "weight", "file", "line"],
|
|
36
|
+
"properties": {
|
|
37
|
+
"signal": { "enum": ["module_name", "controller_class", "controller_path", "endpoint_path", "endpoint_operation_id", "entity_table", "entity_class", "enum_name"] },
|
|
38
|
+
"term": { "type": "string" },
|
|
39
|
+
"value": { "type": ["string", "null"] },
|
|
40
|
+
"weight": { "type": "number" },
|
|
41
|
+
"file": { "type": ["string", "null"] },
|
|
42
|
+
"line": { "type": ["integer", "null"] }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"capped_signals": {
|
|
47
|
+
"description": "D-scanner-evidence: which signal types (a subset of `evidence[].signal`'s enum) had MORE raw matches than the per-signal cap -- named here once per module rather than recording every excess match individually, which is what kept `evidence` unbounded before this field existed.",
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": { "type": "string" }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"collisions": { "type": "array" },
|
|
55
|
+
"unknowns": { "type": "array", "items": { "type": "string" } },
|
|
56
|
+
"files_read": {
|
|
57
|
+
"description": "S2 (D-gate-precision, continued): the adapter's own real read-set, repo-relative -- what lib/gate-definitions.mjs's `scan` gate re-derives and hashes for a precise staleness token instead of a repo-wide head_sha proxy. Always present (an adapter with nothing to scan yet, or one that predates this field, reports an empty array), never inferred from `related_modules` (which is filtered to score>0 and blind to a newly-added file that hasn't been re-scanned yet).",
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": { "type": "string" }
|
|
60
|
+
},
|
|
61
|
+
"db_schema": {
|
|
62
|
+
"description": "A4 (D-db-schema-plane): present only when `--db` was passed. `migrations` (Plane A, always local) and `live` (Plane C, present only with --database-url-env) are independent -- a repo can have one, the other, both, or (most repos, including the real oracle repo) neither.",
|
|
63
|
+
"type": "object",
|
|
64
|
+
"additionalProperties": false,
|
|
65
|
+
"required": ["migrations", "live"],
|
|
66
|
+
"properties": {
|
|
67
|
+
"migrations": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"additionalProperties": false,
|
|
70
|
+
"required": ["tool", "files", "tables"],
|
|
71
|
+
"properties": {
|
|
72
|
+
"tool": { "enum": ["flyway", "liquibase", "none"] },
|
|
73
|
+
"files": { "type": "array", "items": { "type": "string" } },
|
|
74
|
+
"tables": { "type": "array" }
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"live": {
|
|
78
|
+
"type": ["object", "null"],
|
|
79
|
+
"additionalProperties": false,
|
|
80
|
+
"required": ["schema", "tables", "schema_hash"],
|
|
81
|
+
"properties": {
|
|
82
|
+
"schema": { "type": "string" },
|
|
83
|
+
"tables": { "type": "array" },
|
|
84
|
+
"schema_hash": { "type": "string" }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"disposition": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"properties": {
|
|
92
|
+
"mode": { "enum": ["reuse", "extend", "replace", "parallel"] },
|
|
93
|
+
"note": { "type": "string" },
|
|
94
|
+
"module": {
|
|
95
|
+
"description": "S2 (D-gate-precision, part 2): which related_modules[].module this feature disposed onto -- null for a greenfield verdict (no modules to choose from). Defaults to the top-scored module (matching contracts/emit.mjs's selectModule() default) when `scan disposition --module` wasn't passed explicitly. The `contract` gate's staleness token narrows to just this module's own files using this field.",
|
|
96
|
+
"type": ["string", "null"]
|
|
97
|
+
},
|
|
98
|
+
"at": { "type": "string", "format": "date-time" }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:sbf:stack-choice:1",
|
|
4
|
+
"title": "backend-skeleton stack catalog entry",
|
|
5
|
+
"description": "Validates a stack/catalog/<id>.yml file: what static, codegen-time artifacts a stack choice needs (files, .env.example entries, config checks) vs. what only exists once its bootstrap script actually runs.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["id", "description", "static", "runtime"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
|
11
|
+
"description": { "type": "string" },
|
|
12
|
+
"detect": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"additionalProperties": false,
|
|
15
|
+
"properties": {
|
|
16
|
+
"files": { "type": "array", "items": { "type": "string" } },
|
|
17
|
+
"env_keys": { "type": "array", "items": { "type": "string" } }
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"static": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"properties": {
|
|
24
|
+
"files": {
|
|
25
|
+
"type": "array",
|
|
26
|
+
"items": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"required": ["path", "template"],
|
|
29
|
+
"properties": {
|
|
30
|
+
"path": { "type": "string" },
|
|
31
|
+
"template": { "type": "string" },
|
|
32
|
+
"mode": { "type": "string" }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"env_example": {
|
|
37
|
+
"type": "array",
|
|
38
|
+
"items": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"required": ["key", "doc"],
|
|
41
|
+
"properties": {
|
|
42
|
+
"key": { "type": "string" },
|
|
43
|
+
"required": { "type": "boolean" },
|
|
44
|
+
"secret": { "type": "boolean" },
|
|
45
|
+
"doc": { "type": "string" }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"config_check": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"required": ["target", "externalized_pattern", "note"],
|
|
54
|
+
"properties": {
|
|
55
|
+
"target": { "type": "string" },
|
|
56
|
+
"externalized_pattern": { "type": "string" },
|
|
57
|
+
"note": { "type": "string" }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"runtime": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"additionalProperties": false,
|
|
66
|
+
"required": ["script", "produces"],
|
|
67
|
+
"properties": {
|
|
68
|
+
"script": { "type": "string" },
|
|
69
|
+
"requires": { "type": "array", "items": { "type": "string" } },
|
|
70
|
+
"readiness": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"properties": { "probe": { "type": "string" } }
|
|
73
|
+
},
|
|
74
|
+
"produces": {
|
|
75
|
+
"type": "array",
|
|
76
|
+
"items": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"required": ["key", "sink"],
|
|
79
|
+
"properties": {
|
|
80
|
+
"key": { "type": "string" },
|
|
81
|
+
"sink": { "enum": ["env"] },
|
|
82
|
+
"mode": { "enum": ["set", "append-unique"] }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:sbf:stack-record:1",
|
|
4
|
+
"title": "backend-skeleton applied-stack runtime record",
|
|
5
|
+
"description": "Validates .sbf/stack.json, written by `bskel stack apply --apply` (bin/bskel.mjs's cmdStackApply). NOT the same schema as stack-choice.schema.json, which validates a stack/catalog/<id>.yml CATALOG ENTRY (an extension author's config) -- this validates the RUNTIME RECORD of which choice was applied and what it wrote, a distinct persistence boundary. See D-persistence-integrity (S5) in DECISIONS.md for why this schema didn't exist before.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schema", "choice", "applied_files", "env_example_keys", "at"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schema": { "const": "sbf.stack/1" },
|
|
11
|
+
"choice": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
|
12
|
+
"applied_files": {
|
|
13
|
+
"description": "S2: this choice's FULL file set in this repo (its desired state), not just what the most recent --apply happened to write -- see the S2 comment at this record's write site in bin/bskel.mjs.",
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": { "type": "string" }
|
|
16
|
+
},
|
|
17
|
+
"env_example_keys": { "type": "array", "items": { "type": "string" } },
|
|
18
|
+
"at": { "type": "string", "format": "date-time" }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:sbf:state:1",
|
|
4
|
+
"title": "backend-skeleton per-feature state",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema", "feature_id", "gates"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema": { "const": "sbf.state/1" },
|
|
10
|
+
"feature_id": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "a real feature_id, or the reserved repo-scope sentinel \"_repo\" used by repo-scoped gates (preflight, stack) -- see REPO_GATE_ID in lib/gate-definitions.mjs.",
|
|
13
|
+
"pattern": "^(_repo|[0-9]{3}-[a-z0-9]+(-[a-z0-9]+)*)$"
|
|
14
|
+
},
|
|
15
|
+
"feature_uid": { "type": "string", "format": "uuid" },
|
|
16
|
+
"gates": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"additionalProperties": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"additionalProperties": false,
|
|
21
|
+
"required": ["status", "token", "at"],
|
|
22
|
+
"properties": {
|
|
23
|
+
"status": {
|
|
24
|
+
"description": "S4: the only THREE statuses ever WRITTEN to disk (see lib/gates.mjs's passGate/awaitDispositionGate/revokeGate). `not_run`, `stale`, and `pass (forced)` are derived at READ time by requireGate() by recomputing the token from current inputs -- they are never stored, so they must not appear here.",
|
|
25
|
+
"enum": ["pass", "awaiting_disposition", "revoked"]
|
|
26
|
+
},
|
|
27
|
+
"token": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
|
28
|
+
"at": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "S5: deliberately NOT format:date-time. lib/gates.mjs's checkFreshness() already has its own dedicated, more lenient handling for an unparseable `at` (Date.parse + Number.isFinite -> STALE_REASON.INVALID_TIMESTAMP, a graceful fail-closed result, not a crash) -- see D-preflight-freshness (S3) and test/gates.test.mjs's \"missing or unparseable at timestamp\" test, which deliberately writes a non-ISO string to simulate hand-edited/corrupted state. Enforcing format:date-time here would turn that already-shipped, tested, deliberate behavior into a hard read-time crash instead."
|
|
31
|
+
},
|
|
32
|
+
"forced": { "type": "boolean", "description": "a forced gate is always stored as status:\"pass\" with this set true -- there is no separate \"pass (forced)\" value on disk; requireGate() only reports that string back at read time." },
|
|
33
|
+
"reason": { "type": "string", "description": "required when forced is true, or when status is \"revoked\" -- see lib/gates.mjs's forceGate/revokeGate (\"every bypass/revocation must be auditable\")." },
|
|
34
|
+
"evidence": { "type": "object" },
|
|
35
|
+
"inputs": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"description": "S2: the raw, pre-hash input set `token` was computed from, canonically key-sorted -- stored so a stale gate can report exactly WHICH input changed instead of only that the token moved (lib/gates.mjs's diffInputs()/explainStaleness()). Absent on records written before S2; requireGate() reports those as stale with stale_reason \"no_recorded_inputs\" rather than guessing."
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|