@terpjs/spec 0.15.0 → 0.17.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 +6 -6
- package/VERSION +1 -1
- package/catalog/backend/list_routes_paginate.json +2 -2
- package/catalog/backend/migration_history_is_intact.json +22 -0
- package/catalog/backend/no_hardcoded_credentials.json +1 -1
- package/catalog/backend/safe_methods_are_read_only.json +1 -1
- package/catalog/backend/schemas_exclude_sensitive_fields.json +1 -1
- package/catalog/frontend/escape-hatch.json +1 -1
- package/catalog/frontend/generated-client-only.json +2 -2
- package/catalog/frontend/layout-contract.json +3 -3
- package/catalog/frontend/no-cross-module-imports.json +1 -1
- package/catalog/frontend/no-deep-imports.json +2 -2
- package/catalog/frontend/no-dom-html-injection.json +2 -2
- package/catalog/frontend/no-eval.json +1 -1
- package/catalog/frontend/no-inline-styling.json +2 -2
- package/catalog/frontend/no-style-imports.json +1 -1
- package/catalog/frontend/no-unsafe-href.json +1 -1
- package/catalog/frontend/no-unsafe-target-blank.json +1 -1
- package/catalog/frontend/router-links.json +2 -2
- package/catalog/frontend/token-styled-elements.json +2 -2
- package/corpus/backend/migration_history_is_intact/compliant-01/modules/notes/migrations/versions/0001_base.py +10 -0
- package/corpus/backend/migration_history_is_intact/compliant-01/modules/notes/migrations/versions/0002_change.py +10 -0
- package/corpus/backend/migration_history_is_intact/violation-01/modules/notes/migrations/versions/0001_base.py +10 -0
- package/corpus/backend/migration_history_is_intact/violation-01/modules/notes/migrations/versions/0002_change.py +10 -0
- package/corpus/backend/no_hardcoded_credentials/compliant-03/modules/billing/schemas.py +20 -0
- package/corpus/frontend/generated-client-only/compliant-01/src/modules/widgets/Widget.tsx +1 -1
- package/corpus/frontend/generated-client-only/compliant-02/src/modules/widgets/Widget.tsx +1 -1
- package/corpus/frontend/generated-client-only/compliant-04/src/modules/widgets/Widget.tsx +1 -1
- package/corpus/frontend/layout-contract/compliant-01/src/modules/widgets/Widget.tsx +1 -1
- package/corpus/frontend/layout-contract/violation-01/src/modules/widgets/Widget.tsx +1 -1
- package/corpus/frontend/no-cross-module-imports/compliant-01/src/modules/widgets/Widget.tsx +1 -1
- package/corpus/frontend/no-deep-imports/compliant-01/src/modules/widgets/Widget.tsx +1 -1
- package/corpus/frontend/no-deep-imports/violation-01/src/modules/widgets/Widget.tsx +1 -1
- package/corpus/frontend/no-dom-html-injection/compliant-01/src/modules/widgets/Widget.tsx +1 -1
- package/corpus/frontend/no-eval/compliant-01/src/modules/widgets/Widget.tsx +1 -1
- package/corpus/frontend/no-inline-styling/compliant-01/src/modules/widgets/Widget.tsx +1 -1
- package/corpus/frontend/no-style-imports/compliant-01/src/modules/widgets/Widget.tsx +1 -1
- package/corpus/frontend/router-links/compliant-01/src/modules/widgets/Widget.tsx +1 -1
- package/corpus/frontend/token-styled-elements/compliant-01/src/modules/widgets/Widget.tsx +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ The standalone suite enforces the split
|
|
|
138
138
|
| `title` | One-line statement of the invariant. |
|
|
139
139
|
| `intent` | Why the rule exists — the drift or threat it prevents. |
|
|
140
140
|
| `layer` | Cheapest faithful verification for a *new* stack: see below. |
|
|
141
|
-
| `enforcement` | How the reference implementation enforces it (first entry: the `build-time` check). A `runtime` entry names the fail-closed runtime control pairing with it (the two-layer discipline a Level 3 stack must reproduce for the rules that require it); a `black-box` entry names the `@
|
|
141
|
+
| `enforcement` | How the reference implementation enforces it (first entry: the `build-time` check). A `runtime` entry names the fail-closed runtime control pairing with it (the two-layer discipline a Level 3 stack must reproduce for the rules that require it); a `black-box` entry names the `@terpjs/conformance` probe title. For frontend rules, `reported_as` is the ESLint rule id violations surface as — several catalog rules share one core rule id, so the adapter publishes a `catalogRuleId()` mapping and findings are attributed through it. |
|
|
142
142
|
| `runtime` | **Mandatory.** The rule's runtime-applicability classification (`required` / `not-applicable` / `deferred`) plus a `rationale` (mandatory for exemptions) and, for `deferred`, a `tracking` reference naming where the deferral is tracked — see “Runtime applicability” below. |
|
|
143
143
|
| `restricted_surface` | Frontend prohibition rules only: the `restricted-surface.json` keys the rule realises — the structural citation the spec suite resolves (every key must be claimed by some rule; a prose mention in `intent` must agree with the field). |
|
|
144
144
|
| `opt_out` | The *reference realisation* of the abstract escape-hatch contract (see below). |
|
|
@@ -159,7 +159,7 @@ The standalone suite enforces the split
|
|
|
159
159
|
|
|
160
160
|
The classification is a judgment about *porting cost*, not a limit on the
|
|
161
161
|
reference implementation — today every rule is enforced by `terp.arch` or
|
|
162
|
-
`@
|
|
162
|
+
`@terpjs/eslint-boundaries` regardless of layer.
|
|
163
163
|
|
|
164
164
|
### Runtime applicability (the two-layer discipline, per rule)
|
|
165
165
|
|
|
@@ -222,7 +222,7 @@ consumers acting on findings without re-reading the catalog), and a
|
|
|
222
222
|
can be tracked across line-shifting edits). Attribution is always to the
|
|
223
223
|
stack-neutral catalog id — the reference ESLint adapter, whose core rule ids
|
|
224
224
|
are shared between several catalog rules, publishes this mapping as
|
|
225
|
-
`catalogRuleId()` in `@
|
|
225
|
+
`catalogRuleId()` in `@terpjs/eslint-boundaries`.
|
|
226
226
|
|
|
227
227
|
## Check-report format
|
|
228
228
|
|
|
@@ -380,7 +380,7 @@ rule can ship with its gap explicit and reviewed.
|
|
|
380
380
|
## Conformance levels
|
|
381
381
|
|
|
382
382
|
- **Level 1 — black-box:** the app passes the runnable conformance suite
|
|
383
|
-
(`@
|
|
383
|
+
(`@terpjs/conformance`) for the capabilities it claims, including the
|
|
384
384
|
`standard:` probes the `black-box` catalog entries name.
|
|
385
385
|
- **Level 2 — static rule pack:** additionally, a checker validated against
|
|
386
386
|
this corpus enforces the `static-portable` rules for the app's language(s),
|
|
@@ -388,8 +388,8 @@ rule can ship with its gap explicit and reviewed.
|
|
|
388
388
|
- **Level 3 — full harness:** additionally, the `static-bespoke` rules, the
|
|
389
389
|
paired `runtime` controls of every rule whose `runtime.applicability` is
|
|
390
390
|
`required`, and the governed escape-hatch budget ratchet are enforced (today:
|
|
391
|
-
the `terp.arch` + `@
|
|
392
|
-
`terp.core` / `@
|
|
391
|
+
the `terp.arch` + `@terpjs/eslint-boundaries` reference harness on top of
|
|
392
|
+
`terp.core` / `@terpjs/react-core`).
|
|
393
393
|
|
|
394
394
|
## Growing the spec
|
|
395
395
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.17.0
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"kind": "black-box",
|
|
15
|
-
"tool": "@
|
|
15
|
+
"tool": "@terpjs/conformance",
|
|
16
16
|
"ref": "standard: list routes return a capped Page envelope"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"opt_out": "# arch-allow-list-routes-paginate: <reason>",
|
|
26
26
|
"runtime": {
|
|
27
27
|
"applicability": "required",
|
|
28
|
-
"rationale": "At boot, the route scan on the composition seam (the same seam as _validate_router_response_models) walks every composed route \u2014 decorator, imperative add_api_route, and nested included routers alike \u2014 and raises a BootError naming this rule and the route when a route declares a bare list[...] / Sequence[...] response_model instead of Page[T]. PaginationDep's fail-closed skip/limit caps and the @
|
|
28
|
+
"rationale": "At boot, the route scan on the composition seam (the same seam as _validate_router_response_models) walks every composed route \u2014 decorator, imperative add_api_route, and nested included routers alike \u2014 and raises a BootError naming this rule and the route when a route declares a bare list[...] / Sequence[...] response_model instead of Page[T]. PaginationDep's fail-closed skip/limit caps and the @terpjs/conformance Page-envelope probe remain in place on top of this in-app refusal."
|
|
29
29
|
},
|
|
30
30
|
"guide_topic": "service",
|
|
31
31
|
"corpus": true
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/migration_history_is_intact",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "Each migration history is one unbroken chain from a single first revision",
|
|
5
|
+
"intent": "A revision whose parent is missing, or a second revision claiming to start the history, means an already-authored migration was deleted or replaced rather than built upon. Every database that applied the removed revision then becomes unupgradable, and no schema-drift check can see it: a database rebuilt from the rewritten history is perfectly consistent with the models, so the build stays green while every provisioned environment is stranded. New schema changes are added on top of the existing chain.",
|
|
6
|
+
"layer": "static-portable",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_migration_history_is_intact"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"reference": "Within each migrations/versions directory, every revision's declared parent (one id, or each id of a merge revision's tuple) must name a revision defined in that same directory, and at most one revision may declare no parent.",
|
|
15
|
+
"opt_out": "# arch-allow-migration-history-is-intact: <reason>",
|
|
16
|
+
"runtime": {
|
|
17
|
+
"applicability": "not-applicable",
|
|
18
|
+
"rationale": "Chain integrity is a property of the authored revision files and is checked from source alone. The complementary runtime control (terp.migrations.assert_no_orphaned_revisions, which refuses a database whose applied revisions the code no longer defines) enforces a different invariant on a different input: it needs a live database and cannot be observed from the source tree."
|
|
19
|
+
},
|
|
20
|
+
"guide_topic": "migrations",
|
|
21
|
+
"corpus": true
|
|
22
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "backend/no_hardcoded_credentials",
|
|
3
3
|
"surface": "backend",
|
|
4
4
|
"title": "App modules do not hard-code credentials or recognizable secret tokens",
|
|
5
|
-
"intent": "A credential-shaped assignment to a non-empty string literal is almost always a secret that should come from sealed config / environment wiring, not source. The rule also rejects common high-confidence secret literal formats anywhere in a module so leaked keys are caught even when assigned to a bland variable name. As a security rule this also scans test and migration files inside a module — a real secret is a leak wherever it is committed.",
|
|
5
|
+
"intent": "A credential-shaped assignment to a non-empty string literal is almost always a secret that should come from sealed config / environment wiring, not source. The rule also rejects common high-confidence secret literal formats anywhere in a module so leaked keys are caught even when assigned to a bland variable name. As a security rule this also scans test and migration files inside a module — a real secret is a leak wherever it is committed. One shape is exempt: an enum member whose literal is its own name (SECRET_REFERENCE = \"secret_reference\") is vocabulary, not secret material.",
|
|
6
6
|
"layer": "static-portable",
|
|
7
7
|
"enforcement": [
|
|
8
8
|
{
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"enforcement": [
|
|
8
8
|
{
|
|
9
9
|
"kind": "build-time",
|
|
10
|
-
"tool": "@
|
|
10
|
+
"tool": "@terpjs/eslint-boundaries",
|
|
11
11
|
"ref": "BOUNDARY_SPEC.restrictedGlobals",
|
|
12
12
|
"reported_as": "no-restricted-globals"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
|
-
"reference": "useTerpClient() + unwrap from @
|
|
15
|
+
"reference": "useTerpClient() + unwrap from @terpjs/react-core for request/response; useRealtimeChannel() from @terpjs/react-core for typed SSE/WebSocket subscriptions (one-use ticket minted through the generated client).",
|
|
16
16
|
"opt_out": "// terp-allow-generated-client-only: <reason>",
|
|
17
17
|
"runtime": {
|
|
18
18
|
"applicability": "not-applicable",
|
|
@@ -7,18 +7,18 @@
|
|
|
7
7
|
"enforcement": [
|
|
8
8
|
{
|
|
9
9
|
"kind": "build-time",
|
|
10
|
-
"tool": "@
|
|
10
|
+
"tool": "@terpjs/eslint-boundaries",
|
|
11
11
|
"ref": "terp/layout-contract",
|
|
12
12
|
"reported_as": "terp/layout-contract"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"kind": "runtime",
|
|
16
|
-
"tool": "@
|
|
16
|
+
"tool": "@terpjs/react-core",
|
|
17
17
|
"ref": "verifySlotChildren"
|
|
18
18
|
}
|
|
19
19
|
],
|
|
20
20
|
"opt_out": "// terp-allow-layout-contract: <reason>",
|
|
21
|
-
"reference": "frontend/layout-contract.json + the layoutContract bootstrap option (ADR 0079); verifySlotChildren in @
|
|
21
|
+
"reference": "frontend/layout-contract.json + the layoutContract bootstrap option (ADR 0079); verifySlotChildren in @terpjs/react-core is the runtime DOM check.",
|
|
22
22
|
"runtime": {
|
|
23
23
|
"applicability": "required"
|
|
24
24
|
},
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"enforcement": [
|
|
8
8
|
{
|
|
9
9
|
"kind": "build-time",
|
|
10
|
-
"tool": "@
|
|
10
|
+
"tool": "@terpjs/eslint-boundaries",
|
|
11
11
|
"ref": "BOUNDARY_SPEC.internalImportPatterns",
|
|
12
12
|
"reported_as": "no-restricted-imports"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
|
-
"reference": "@
|
|
15
|
+
"reference": "@terpjs/*/src/* and @terpjs/*/dist/* imports are refused; import from the @terpjs/* package root.",
|
|
16
16
|
"opt_out": "// terp-allow-no-deep-imports: <reason>",
|
|
17
17
|
"runtime": {
|
|
18
18
|
"applicability": "not-applicable",
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"enforcement": [
|
|
8
8
|
{
|
|
9
9
|
"kind": "build-time",
|
|
10
|
-
"tool": "@
|
|
10
|
+
"tool": "@terpjs/eslint-boundaries",
|
|
11
11
|
"ref": "terp/no-dom-html-injection",
|
|
12
12
|
"reported_as": "terp/no-dom-html-injection"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
|
-
"reference": "The Markdown component from @
|
|
15
|
+
"reference": "The Markdown component from @terpjs/react-core for rich text.",
|
|
16
16
|
"opt_out": "// terp-allow-no-dom-html-injection: <reason>",
|
|
17
17
|
"runtime": {
|
|
18
18
|
"applicability": "not-applicable",
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"enforcement": [
|
|
8
8
|
{
|
|
9
9
|
"kind": "build-time",
|
|
10
|
-
"tool": "@
|
|
10
|
+
"tool": "@terpjs/eslint-boundaries",
|
|
11
11
|
"ref": "BOUNDARY_SPEC.restrictedAttributes",
|
|
12
12
|
"reported_as": "no-restricted-syntax"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
|
-
"reference": "Layout via Stack, DetailList and the page archetypes from @
|
|
15
|
+
"reference": "Layout via Stack, DetailList and the page archetypes from @terpjs/react-core; theming via the app's token source.",
|
|
16
16
|
"opt_out": "// terp-allow-no-inline-styling: <reason>",
|
|
17
17
|
"runtime": {
|
|
18
18
|
"applicability": "not-applicable",
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"enforcement": [
|
|
8
8
|
{
|
|
9
9
|
"kind": "build-time",
|
|
10
|
-
"tool": "@
|
|
10
|
+
"tool": "@terpjs/eslint-boundaries",
|
|
11
11
|
"ref": "BOUNDARY_SPEC.restrictInAppAnchors",
|
|
12
12
|
"reported_as": "no-restricted-syntax"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
|
-
"reference": "Link from @
|
|
15
|
+
"reference": "Link from @terpjs/react-core (router-integrated, role-aware).",
|
|
16
16
|
"opt_out": "// terp-allow-router-links: <reason>",
|
|
17
17
|
"runtime": {
|
|
18
18
|
"applicability": "not-applicable",
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"enforcement": [
|
|
8
8
|
{
|
|
9
9
|
"kind": "build-time",
|
|
10
|
-
"tool": "@
|
|
10
|
+
"tool": "@terpjs/eslint-boundaries",
|
|
11
11
|
"ref": "BOUNDARY_SPEC.restrictedElements",
|
|
12
12
|
"reported_as": "no-restricted-syntax"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
|
-
"reference": "Button, Input, Select, Textarea, DataView, ConfirmDialog and Stack as=\"form\" from @
|
|
15
|
+
"reference": "Button, Input, Select, Textarea, DataView, ConfirmDialog and Stack as=\"form\" from @terpjs/react-core (BOUNDARY_SPEC.restrictedElements maps each element to its replacement).",
|
|
16
16
|
"opt_out": "// terp-allow-token-styled-elements: <reason>",
|
|
17
17
|
"runtime": {
|
|
18
18
|
"applicability": "not-applicable",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Compliant: enum vocabulary whose literal is the member's own name.
|
|
2
|
+
|
|
3
|
+
A member like ``SECRET_REFERENCE = "secret_reference"`` names a parameter kind —
|
|
4
|
+
the string carries no secret material, it *is* the identifier's wire spelling.
|
|
5
|
+
Flagging it would push authors to spell the same vocabulary as ``auto()`` purely
|
|
6
|
+
to dodge the rule, hiding the wire value from readers of the schema.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from enum import StrEnum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ParameterType(StrEnum):
|
|
13
|
+
TEXT = "text"
|
|
14
|
+
NUMBER = "number"
|
|
15
|
+
SECRET_REFERENCE = "secret_reference"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TokenKind(StrEnum):
|
|
19
|
+
ACCESS_TOKEN = "access_token"
|
|
20
|
+
REFRESH_TOKEN = "refresh_token"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import Button from "@
|
|
1
|
+
import Button from "@terpjs/react-core/src/Button";
|
|
2
2
|
export const Widget = () => <Button>x</Button>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terpjs/spec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "The Terp Standard — stack-neutral rule catalog, violation corpus, finding format, and refused-surface declaration (ADRs 0080/0081; packaged per ADR 0082, published per ADR 0086). Data only: consumers resolve the spec root via require.resolve('@terpjs/spec/package.json').",
|
|
5
5
|
"files": [
|
|
6
6
|
"VERSION",
|