backend-skeleton 1.3.0 → 1.5.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.
Files changed (31) hide show
  1. package/bin/bskel.mjs +308 -0
  2. package/contracts/emit.mjs +22 -6
  3. package/handles/providers/java-spring/plan.mjs +24 -3
  4. package/handles/providers/java-spring/rules.mjs +143 -0
  5. package/handles/providers/java-spring/templates/EnforceRules.java.tmpl +44 -0
  6. package/handles/providers/java-spring/templates/RuleCheck.java.tmpl +215 -0
  7. package/handles/providers/java-spring/templates/RuleEnforcementAspect.java.tmpl +132 -0
  8. package/handles/providers/java-spring/templates/RuleSetLoader.java.tmpl +138 -0
  9. package/handles/providers/python-fastapi/rules.mjs +133 -0
  10. package/handles/providers/python-fastapi/templates/enforce_rules.py.tmpl +128 -0
  11. package/handles/providers/python-fastapi/templates/rule_check.py.tmpl +178 -0
  12. package/handles/providers/python-fastapi/templates/rule_set.py.tmpl +59 -0
  13. package/handles/providers/typescript-express/plan.mjs +15 -2
  14. package/handles/providers/typescript-express/rules.mjs +129 -0
  15. package/handles/providers/typescript-express/templates/enforceRules.ts.tmpl +93 -0
  16. package/handles/providers/typescript-express/templates/ruleCheck.ts.tmpl +209 -0
  17. package/handles/providers/typescript-express/templates/ruleSet.ts.tmpl +61 -0
  18. package/lib/cli.mjs +37 -0
  19. package/lib/gate-definitions.mjs +27 -1
  20. package/lib/workflow.mjs +9 -0
  21. package/package.json +2 -1
  22. package/rules/compile.mjs +433 -0
  23. package/rules/derived.mjs +87 -0
  24. package/rules/diagnostics.mjs +147 -0
  25. package/rules/store.mjs +141 -0
  26. package/rules/vocabulary.mjs +172 -0
  27. package/scanners/adapters/_java-spring-analyzer.mjs +49 -0
  28. package/scanners/adapters/java-spring.mjs +154 -3
  29. package/scanners/index.mjs +10 -0
  30. package/schemas/feature-contract.schema.json +12 -1
  31. package/schemas/feature-rules.schema.json +139 -0
@@ -0,0 +1,139 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "urn:sbf:feature-rules:1",
4
+ "title": "backend-skeleton compiled feature business rules",
5
+ "description": "Validates specs/<feature_id>/rules/<feature_id>.rules.json -- the DETERMINISTIC, COMPILED artifact rules/compile.mjs produces from a feature contract plus an optional hand-authored specs/<feature_id>/rules.yaml. This is the only rules file any provider emitter reads. See D-business-rules in DECISIONS.md. The authored YAML source is deliberately NOT validated by a schema: rules/compile.mjs resolves every rule against the contract's own requestBodySchema and produces far more actionable refusals than a structural schema could (naming the real known fields, the field's real declared type, the real enum states), so a second, weaker structural gate in front of it would only ever produce worse messages for the same input.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["sbf_feature_rules", "feature_id", "feature_uid", "contract_ref", "operations", "derived", "unsupported"],
9
+ "$defs": {
10
+ "derivedExpr": {
11
+ "description": "R5/Phase 3's closed expression grammar for a derived field's formula -- exactly one of an op node (binary, add/sub/mul/div only -- rules/vocabulary.mjs's DERIVED_OPS), a `ref` leaf (a named input, becomes a generated function parameter), or a `const` leaf (a compile-time literal). Deliberately not a general expression language: no loops, no function calls, no I/O, no operator beyond this frozen set.",
12
+ "oneOf": [
13
+ {
14
+ "type": "object",
15
+ "additionalProperties": false,
16
+ "required": ["op", "args"],
17
+ "properties": {
18
+ "op": { "enum": ["add", "sub", "mul", "div"] },
19
+ "args": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "$ref": "#/$defs/derivedExpr" } }
20
+ }
21
+ },
22
+ {
23
+ "type": "object",
24
+ "additionalProperties": false,
25
+ "required": ["ref"],
26
+ "properties": { "ref": { "type": "string", "minLength": 1 } }
27
+ },
28
+ {
29
+ "type": "object",
30
+ "additionalProperties": false,
31
+ "required": ["const"],
32
+ "properties": { "const": { "type": "number" } }
33
+ }
34
+ ]
35
+ }
36
+ },
37
+ "properties": {
38
+ "sbf_feature_rules": { "const": "1" },
39
+ "feature_id": { "type": "string", "pattern": "^[0-9]{3}-[a-z0-9]+(-[a-z0-9]+)*$" },
40
+ "feature_uid": { "type": "string", "format": "uuid" },
41
+ "contract_ref": {
42
+ "description": "sha256 of the contract file this artifact was compiled against. A rule is only meaningful against the contract whose pointers/types/enums it was verified against, so pairing a compiled artifact with a different contract must be detectable -- the `rules` gate hashes both, and every provider stamps this value into the emitted runtime resource (the same role observe's own contract_ref plays in <feature>.observed-schema.json).",
43
+ "type": "string"
44
+ },
45
+ "operations": {
46
+ "description": "Predicate rules, keyed by the contract's own operationId. An operation with no rules is absent entirely rather than present-and-empty.",
47
+ "type": "object",
48
+ "additionalProperties": {
49
+ "type": "object",
50
+ "additionalProperties": false,
51
+ "properties": {
52
+ "field": {
53
+ "description": "Single-pointer constraints. `origin: contract` entries were projected from the operation's own requestBodySchema (facts the user's OpenAPI document already asserts, transported -- see R4); `origin: declared` entries were hand-authored. Deliberately excludes required/type/pattern, which handles/observe-schema-projection.mjs already projects and every generated checker already enforces -- so one violation is never reported twice.",
54
+ "type": "array",
55
+ "items": {
56
+ "type": "object",
57
+ "additionalProperties": false,
58
+ "required": ["id", "pointer", "assert", "value", "origin"],
59
+ "properties": {
60
+ "id": { "type": "string", "minLength": 1 },
61
+ "pointer": { "type": "string", "pattern": "^/[^/]+$" },
62
+ "assert": { "enum": ["minLength", "maxLength", "minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf", "enum"] },
63
+ "value": {},
64
+ "origin": { "enum": ["contract", "declared"] }
65
+ }
66
+ }
67
+ },
68
+ "cross": {
69
+ "description": "Multi-pointer constraints. `types` records each pointer's contract-declared scalar type at compile time, so a generated runtime never has to re-derive it -- and so a later contract change that alters a type is visible as a real diff in this artifact.",
70
+ "type": "array",
71
+ "items": {
72
+ "type": "object",
73
+ "additionalProperties": false,
74
+ "required": ["id", "pointers", "assert", "types", "origin"],
75
+ "properties": {
76
+ "id": { "type": "string", "minLength": 1 },
77
+ "pointers": { "type": "array", "minItems": 2, "items": { "type": "string", "pattern": "^/[^/]+$" } },
78
+ "assert": { "enum": ["lt", "lte", "gt", "gte", "eq", "neq", "requiredIf", "mutuallyExclusive"] },
79
+ "types": { "type": "array", "minItems": 2, "items": { "enum": ["string", "number", "integer", "boolean"] } },
80
+ "origin": { "enum": ["contract", "declared"] }
81
+ }
82
+ }
83
+ },
84
+ "transition": {
85
+ "description": "State-transition guards: a literal from[] -> to[] allow-list over one scalar pointer whose contract schema declares an enum. Compilation refuses a transition on a field with no enum, because without a closed state set a typo'd state name becomes a guard that silently never fires.",
86
+ "type": "array",
87
+ "items": {
88
+ "type": "object",
89
+ "additionalProperties": false,
90
+ "required": ["id", "pointer", "from", "to", "origin"],
91
+ "properties": {
92
+ "id": { "type": "string", "minLength": 1 },
93
+ "pointer": { "type": "string", "pattern": "^/[^/]+$" },
94
+ "from": { "type": "array", "minItems": 1, "items": { "type": "string" } },
95
+ "to": { "type": "array", "minItems": 1, "items": { "type": "string" } },
96
+ "origin": { "enum": ["contract", "declared"] }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ },
103
+ "derived": {
104
+ "description": "Derived/computed field rules. Unlike the three predicate kinds above, a derived rule PRODUCES a value rather than answering true/false about one, so it cannot be executed by a predicate checker and compiles to a generated pure-function class instead (R5). Resource-scoped, not operation-scoped -- unlike field/cross/transition, a derived rule has no `operation` and is never checked against the contract's requestBodySchema.",
105
+ "type": "array",
106
+ "items": {
107
+ "type": "object",
108
+ "additionalProperties": false,
109
+ "required": ["id", "resource", "field", "params", "expr", "origin"],
110
+ "properties": {
111
+ "id": { "type": "string", "minLength": 1 },
112
+ "resource": { "type": "string", "minLength": 1 },
113
+ "field": { "type": "string", "minLength": 1 },
114
+ "params": {
115
+ "description": "Every distinct {ref} name in `expr`, in first-appearance order -- this becomes the generated pure function's own parameter list (rules/derived.mjs's collectParams()).",
116
+ "type": "array",
117
+ "items": { "type": "string", "minLength": 1 }
118
+ },
119
+ "expr": { "$ref": "#/$defs/derivedExpr" },
120
+ "origin": { "const": "declared" }
121
+ }
122
+ }
123
+ },
124
+ "unsupported": {
125
+ "description": "Every constraint found in the contract that this vocabulary cannot express, recorded rather than silently dropped -- the same honesty mechanism handles/observe-schema-projection.mjs's own `unsupported[]` JSON-Pointer list provides. An auditor reading this artifact can always tell what is NOT enforced.",
126
+ "type": "array",
127
+ "items": {
128
+ "type": "object",
129
+ "additionalProperties": false,
130
+ "required": ["code", "subject", "reason"],
131
+ "properties": {
132
+ "code": { "type": "string", "pattern": "^RULE_[A-Z_]+$" },
133
+ "subject": { "type": ["string", "null"] },
134
+ "reason": { "type": "string" }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ }