@starci/eslint-canon-be 3.2.13 → 3.2.15
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/CHANGELOG.md +13 -1
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +207 -15
- package/runtime/knowledge/hfs/fe-stack.yaml +218 -0
- package/runtime/knowledge/hfs/rules.yaml +51 -48
- package/runtime/knowledge/hfs/slots.yaml +319 -179
- package/runtime/knowledge/patterns/fe/folder.yaml +24 -18
- package/runtime/knowledge/sonar-gate.yaml +10 -2
- package/runtime/modules/kernel/removed-vocabulary.yaml +48 -0
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +0 -2
- package/runtime/scripts/hfs/architecture/config.mjs +3 -2
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +6 -2
- package/runtime/scripts/hfs/architecture/error-masked.mjs +10 -1
- package/runtime/scripts/hfs/architecture/frontend.mjs +10 -3
- package/runtime/scripts/hfs/architecture/hfs.mjs +6 -3
- package/runtime/scripts/hfs/architecture/index.mjs +2 -0
- package/runtime/scripts/hfs/architecture/owners.mjs +19 -16
- package/runtime/scripts/hfs/architecture/route-files-thin.mjs +4 -1
- package/runtime/scripts/hfs/architecture/server-action.mjs +75 -20
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +2 -2
- package/runtime/scripts/hfs/architecture/supabase.mjs +36 -210
- package/runtime/scripts/hfs/architecture/tiers.mjs +1 -33
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +110 -54
- package/runtime/scripts/hfs/architecture/type-context.mjs +4 -1
- package/runtime/scripts/hfs/edition-slots.mjs +13 -4
- package/runtime/scripts/hfs/manifest-shape.mjs +3 -3
- package/runtime/scripts/hfs/rule-catalog.mjs +21 -1
- package/runtime/scripts/hfs/rules/fe-no-tests.mjs +4 -5
- package/runtime/scripts/hfs/architecture/supabase-results.mjs +0 -182
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
# (`at`) that emits one of the rule's codes. scripts/checks/check-hfs-rules.mjs holds all of this to the packages, the
|
|
13
13
|
# scripts and modules/kernel/failure-codes.yaml.
|
|
14
14
|
#
|
|
15
|
-
# Every rule is an error
|
|
15
|
+
# Every rule is an error. There is no baseline, allowlist or suppression file for a product. The runtime gate
|
|
16
16
|
# (the StarCi runtime repository judged by knowledge/hfs/runtime-slots.yaml) has exactly one, the shrink-only sections of
|
|
17
17
|
# modules/kernel/allowlist.yaml, held by RT_ALLOWLIST_SPRAWL. Codes are quoted: the failure-code
|
|
18
18
|
# scanner reads this file as an emitter of every code it names.
|
|
19
|
-
schema: starci/hfs-rules@
|
|
20
|
-
version:
|
|
19
|
+
schema: starci/hfs-rules@3
|
|
20
|
+
version: 3.0.0
|
|
21
21
|
gates:
|
|
22
22
|
pre-commit: the managed .husky/pre-commit at the app root - starci app hygiene (staged .starciwork and .starcistacks paths, secrets guard), typecheck, ESLint per side and stylelint on the staged files, prettier --check, the unit specs the staged files touch
|
|
23
23
|
pre-push: typecheck, lint (starci app lint), format:check and the unit specs affected since origin/main; runs every pre-commit rule again
|
|
@@ -463,7 +463,6 @@ rules:
|
|
|
463
463
|
kinds: [lint, design]
|
|
464
464
|
gates: [land, ci]
|
|
465
465
|
failureCodes: ["BE_BACKGROUND_UNOWNED"]
|
|
466
|
-
editions: [full]
|
|
467
466
|
enforcers:
|
|
468
467
|
- {kind: eslint-be, id: background-unowned}
|
|
469
468
|
- id: R47
|
|
@@ -472,7 +471,6 @@ rules:
|
|
|
472
471
|
kinds: [check, codemod, lint]
|
|
473
472
|
gates: [land, ci]
|
|
474
473
|
failureCodes: ["BE_TEST_TOPOLOGY", "BE_CONTRACT_UNGUARDED"]
|
|
475
|
-
editions: [full]
|
|
476
474
|
enforcers:
|
|
477
475
|
- {kind: hfs, id: test-topology, at: scripts/hfs/rules/test-topology.mjs}
|
|
478
476
|
- {kind: machine, id: test-kind-unsupported, at: scripts/hfs/architecture/hfs.mjs}
|
|
@@ -488,7 +486,6 @@ rules:
|
|
|
488
486
|
kinds: [lint, design]
|
|
489
487
|
gates: [settle, land, ci]
|
|
490
488
|
failureCodes: ["BE_SPEC_QUALITY"]
|
|
491
|
-
editions: [full]
|
|
492
489
|
enforcers:
|
|
493
490
|
- {kind: eslint-be, id: spec-no-source-read}
|
|
494
491
|
- {kind: eslint-be, id: e2e-uses-production-transport}
|
|
@@ -521,7 +518,7 @@ rules:
|
|
|
521
518
|
- {kind: eslint-fe, id: no-hardcoded-endpoint-fallback}
|
|
522
519
|
- id: R50
|
|
523
520
|
code: "FE_TRANSPORT_OWNER"
|
|
524
|
-
law: "One
|
|
521
|
+
law: "One transport per front end: the browser reaches the back end only through the auth and no-auth Apollo clients and their link chain (retry, error, timeout, token refresh, access token, http) in `fe/apps/<app>/src/modules/api/graphql/clients/` when one app calls the back end, or `fe/packages/<family>-api/src/graphql/clients/` shared by every app that does when several do (an app that calls no back end, such as a marketing landing, holds no transport), and through the one axios instance of `modules/api/rest` for uploads, presigned URLs and auth endpoints; no other `fetch`, HTTP or GraphQL client, and no token or locale held in module state; a component reads through an SWR hook of `hooks/swr` whose key carries the identity of its result, and a mutation hook is tied to its resource."
|
|
525
522
|
kinds: [lint, design]
|
|
526
523
|
gates: [settle, land, ci]
|
|
527
524
|
failureCodes: ["FE_TRANSPORT_OWNER", "FE_EFFECT_FETCH", "FE_SWR_KEY_IDENTITY", "FE_SWR_MUTATION_RESOURCE_IDENTITY"]
|
|
@@ -536,7 +533,7 @@ rules:
|
|
|
536
533
|
- {kind: eslint-fe, id: swr-data-lifecycle}
|
|
537
534
|
- id: R51
|
|
538
535
|
code: "FE_HTTP_STATUS_COLLAPSE"
|
|
539
|
-
law: "One `
|
|
536
|
+
law: "One `GraphQLResponse<T>` envelope (`success`, `message`, `error`, `data`) per front end, declared in `modules/api/graphql/types.ts` (or the shared api package's); a failure is never collapsed into empty data, and an UNAUTHENTICATED or FORBIDDEN answer (401, 403) becomes `refused`: the error link refreshes the token once, then the session sends the user to sign-in."
|
|
540
537
|
kinds: [lint, design]
|
|
541
538
|
gates: [settle, land, ci]
|
|
542
539
|
failureCodes: ["FE_HTTP_STATUS_COLLAPSE", "FE_OUTCOME_KIND_UNHANDLED"]
|
|
@@ -547,7 +544,7 @@ rules:
|
|
|
547
544
|
- {kind: eslint-fe, id: one-outcome-union}
|
|
548
545
|
- id: R52
|
|
549
546
|
code: "FE_WIRE_GENERATED"
|
|
550
|
-
law: "Wire types generated from
|
|
547
|
+
law: "Wire types are generated by graphql-codegen from `be/contracts/<app>/schema.graphql`, read in place and never copied, into `modules/api/graphql/__generated__`; an operation's request and response types re-export the generated ones and are never typed by hand."
|
|
551
548
|
kinds: [lint, check, design]
|
|
552
549
|
gates: [settle, land, ci]
|
|
553
550
|
failureCodes: ["FE_WIRE_GENERATED"]
|
|
@@ -589,7 +586,7 @@ rules:
|
|
|
589
586
|
- {kind: eslint-fe, id: client-reaches-server}
|
|
590
587
|
- id: R56
|
|
591
588
|
code: "FE_HOOKS_ARE_HOOKS"
|
|
592
|
-
law: "`hooks/` hold hooks and one shared file per domain; a product hook is defined only there and imported through its index."
|
|
589
|
+
law: "`hooks/` hold hooks only: `hooks/swr` (one SWR wrapper per modules/api operation), `hooks/rhf` (forms), `hooks/zustand/<store>`, `hooks/socketio`, `hooks/effects`, `hooks/reuseables` and `hooks/<domain>` with one shared file per domain; a product hook is defined only there and imported through its index."
|
|
593
590
|
kinds: [lint, codemod]
|
|
594
591
|
gates: [settle, land, ci]
|
|
595
592
|
failureCodes: ["FE_HOOKS_ARE_HOOKS", "FE_CUSTOM_HOOK_LOCATION", "FE_BLOCK_PRODUCT_HOOK_DEFINITION", "FE_COMPONENT_DEEP_HOOK_IMPORT"]
|
|
@@ -990,13 +987,13 @@ rules:
|
|
|
990
987
|
- {kind: eslint-fe, id: component-purity}
|
|
991
988
|
- id: R93
|
|
992
989
|
code: "FE_VENDOR_BOUNDARY"
|
|
993
|
-
law: "Vendor primitives and icons reach components only through their named owner:
|
|
990
|
+
law: "Vendor primitives and icons reach components only through their named owner: the one icon set of knowledge/hfs/fe-stack.yaml through the icon leaf at the glyph scale, every vendor primitive behind a named owner; the Grammar package is imported only through its selected code and style entry and keeps its declared contract."
|
|
994
991
|
kinds: [lint, codemod]
|
|
995
992
|
gates: [pre-commit, pre-push, settle, land, ci]
|
|
996
993
|
failureCodes: ["FE_VENDOR_BOUNDARY", "ARCH_GRAMMAR_CONTRACT_INVALID", "ARCH_GRAMMAR_EXPORT_BYPASS"]
|
|
997
994
|
enforcers:
|
|
998
995
|
- {kind: eslint-fe, id: no-vendor-icon-outside-icon-leaf}
|
|
999
|
-
- {kind: eslint-fe, id:
|
|
996
|
+
- {kind: eslint-fe, id: one-icon-set}
|
|
1000
997
|
- {kind: eslint-fe, id: no-off-scale-glyph-size}
|
|
1001
998
|
- {kind: eslint-fe, id: no-decorative-icon-in-metric-cell}
|
|
1002
999
|
- {kind: eslint-fe, id: vendor-primitive-has-named-owner}
|
|
@@ -1040,7 +1037,6 @@ rules:
|
|
|
1040
1037
|
kinds: [lint]
|
|
1041
1038
|
gates: [pre-push, settle, land, ci]
|
|
1042
1039
|
failureCodes: ["BE_TEST_BUILDER_HOME"]
|
|
1043
|
-
editions: [full]
|
|
1044
1040
|
enforcers:
|
|
1045
1041
|
- {kind: eslint-be, id: persisting-builder-in-builders-slot}
|
|
1046
1042
|
- id: R99
|
|
@@ -1049,7 +1045,6 @@ rules:
|
|
|
1049
1045
|
kinds: [lint]
|
|
1050
1046
|
gates: [pre-push, settle, land, ci]
|
|
1051
1047
|
failureCodes: ["BE_TEST_ROW_BY_BUILDER"]
|
|
1052
|
-
editions: [full]
|
|
1053
1048
|
enforcers:
|
|
1054
1049
|
- {kind: eslint-be, id: spec-no-raw-insert}
|
|
1055
1050
|
- {kind: eslint-be, id: spec-no-repeated-row-literal}
|
|
@@ -1059,7 +1054,6 @@ rules:
|
|
|
1059
1054
|
kinds: [lint]
|
|
1060
1055
|
gates: [pre-push, settle, land, ci]
|
|
1061
1056
|
failureCodes: ["BE_TEST_CONSTRAINTS_ON"]
|
|
1062
|
-
editions: [full]
|
|
1063
1057
|
enforcers:
|
|
1064
1058
|
- {kind: eslint-be, id: tests-keep-constraints}
|
|
1065
1059
|
- id: R101
|
|
@@ -1068,7 +1062,6 @@ rules:
|
|
|
1068
1062
|
kinds: [lint]
|
|
1069
1063
|
gates: [pre-push, settle, land, ci]
|
|
1070
1064
|
failureCodes: ["BE_TEST_BUILDER_ARRANGES"]
|
|
1071
|
-
editions: [full]
|
|
1072
1065
|
enforcers:
|
|
1073
1066
|
- {kind: eslint-be, id: builder-arranges-only}
|
|
1074
1067
|
- id: R102
|
|
@@ -1077,7 +1070,6 @@ rules:
|
|
|
1077
1070
|
kinds: [check, lint]
|
|
1078
1071
|
gates: [land, ci]
|
|
1079
1072
|
failureCodes: ["BE_SPEC_PLACEMENT"]
|
|
1080
|
-
editions: [full]
|
|
1081
1073
|
enforcers:
|
|
1082
1074
|
- {kind: hfs, id: spec-placement, at: scripts/hfs/rules/spec-placement.mjs}
|
|
1083
1075
|
- {kind: eslint-be, id: spec-placement}
|
|
@@ -1366,7 +1358,6 @@ rules:
|
|
|
1366
1358
|
kinds: [check, codemod]
|
|
1367
1359
|
gates: [settle, land, ci]
|
|
1368
1360
|
failureCodes: ["BE_EVENT_CLASS_CONTRACT"]
|
|
1369
|
-
editions: [full]
|
|
1370
1361
|
enforcers:
|
|
1371
1362
|
- {kind: hfs, id: event-class-contract, at: scripts/hfs/rules/event-bus.mjs}
|
|
1372
1363
|
- id: R152
|
|
@@ -1375,7 +1366,6 @@ rules:
|
|
|
1375
1366
|
kinds: [lint]
|
|
1376
1367
|
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1377
1368
|
failureCodes: ["BE_OUTBOX_WRITE_TX"]
|
|
1378
|
-
editions: [full]
|
|
1379
1369
|
enforcers:
|
|
1380
1370
|
- {kind: eslint-be, id: outbox-write-tx}
|
|
1381
1371
|
- id: R153
|
|
@@ -1384,7 +1374,6 @@ rules:
|
|
|
1384
1374
|
kinds: [lint]
|
|
1385
1375
|
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1386
1376
|
failureCodes: ["BE_EVENT_CONSUMER_SHAPE"]
|
|
1387
|
-
editions: [full]
|
|
1388
1377
|
enforcers:
|
|
1389
1378
|
- {kind: eslint-be, id: event-consumer-shape}
|
|
1390
1379
|
- id: R154
|
|
@@ -1393,7 +1382,6 @@ rules:
|
|
|
1393
1382
|
kinds: [lint]
|
|
1394
1383
|
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1395
1384
|
failureCodes: ["BE_QUEUE_PRODUCER_SHAPE"]
|
|
1396
|
-
editions: [full]
|
|
1397
1385
|
enforcers:
|
|
1398
1386
|
- {kind: eslint-be, id: queue-producer-shape}
|
|
1399
1387
|
- id: R155
|
|
@@ -1420,7 +1408,6 @@ rules:
|
|
|
1420
1408
|
kinds: [lint]
|
|
1421
1409
|
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1422
1410
|
failureCodes: ["BE_JOB_WRITE_OUTSIDE_OWNER"]
|
|
1423
|
-
editions: [full]
|
|
1424
1411
|
enforcers:
|
|
1425
1412
|
- {kind: eslint-be, id: job-write-owner}
|
|
1426
1413
|
- id: R158
|
|
@@ -1429,7 +1416,6 @@ rules:
|
|
|
1429
1416
|
kinds: [lint]
|
|
1430
1417
|
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1431
1418
|
failureCodes: ["BE_JOB_FENCE_REQUIRED"]
|
|
1432
|
-
editions: [full]
|
|
1433
1419
|
enforcers:
|
|
1434
1420
|
- {kind: eslint-be, id: job-fence-required}
|
|
1435
1421
|
- id: R159
|
|
@@ -1438,7 +1424,6 @@ rules:
|
|
|
1438
1424
|
kinds: [lint]
|
|
1439
1425
|
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1440
1426
|
failureCodes: ["BE_JOB_RUN_KEY"]
|
|
1441
|
-
editions: [full]
|
|
1442
1427
|
enforcers:
|
|
1443
1428
|
- {kind: eslint-be, id: job-run-key}
|
|
1444
1429
|
- id: R160
|
|
@@ -1447,7 +1432,6 @@ rules:
|
|
|
1447
1432
|
kinds: [lint]
|
|
1448
1433
|
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1449
1434
|
failureCodes: ["BE_JOB_SHAPE"]
|
|
1450
|
-
editions: [full]
|
|
1451
1435
|
enforcers:
|
|
1452
1436
|
- {kind: eslint-be, id: job-shape}
|
|
1453
1437
|
- id: R161
|
|
@@ -1456,7 +1440,6 @@ rules:
|
|
|
1456
1440
|
kinds: [lint]
|
|
1457
1441
|
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1458
1442
|
failureCodes: ["BE_PROJECTION_WRITE_OWNER"]
|
|
1459
|
-
editions: [full]
|
|
1460
1443
|
enforcers:
|
|
1461
1444
|
- {kind: eslint-be, id: projection-write-owner}
|
|
1462
1445
|
- id: R162
|
|
@@ -1465,7 +1448,6 @@ rules:
|
|
|
1465
1448
|
kinds: [lint]
|
|
1466
1449
|
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1467
1450
|
failureCodes: ["BE_PROJECTION_SHAPE"]
|
|
1468
|
-
editions: [full]
|
|
1469
1451
|
enforcers:
|
|
1470
1452
|
- {kind: eslint-be, id: projection-shape}
|
|
1471
1453
|
- id: R163
|
|
@@ -1498,7 +1480,6 @@ rules:
|
|
|
1498
1480
|
kinds: [check, codemod]
|
|
1499
1481
|
gates: [land, ci]
|
|
1500
1482
|
failureCodes: ["HFS_EVENT_CONTRACT"]
|
|
1501
|
-
editions: [full]
|
|
1502
1483
|
enforcers:
|
|
1503
1484
|
- {kind: hfs, id: services-event-contract, at: scripts/hfs/rules/services.mjs}
|
|
1504
1485
|
- id: R167
|
|
@@ -1524,7 +1505,6 @@ rules:
|
|
|
1524
1505
|
kinds: [check]
|
|
1525
1506
|
gates: [land, ci]
|
|
1526
1507
|
failureCodes: ["BE_SAGA_STEP_COMPENSATION"]
|
|
1527
|
-
editions: [full]
|
|
1528
1508
|
enforcers:
|
|
1529
1509
|
- {kind: hfs, id: saga-step-compensation, at: scripts/hfs/rules/saga.mjs}
|
|
1530
1510
|
- id: R170
|
|
@@ -1533,7 +1513,6 @@ rules:
|
|
|
1533
1513
|
kinds: [check]
|
|
1534
1514
|
gates: [land, ci]
|
|
1535
1515
|
failureCodes: ["BE_SAGA_STATE_VERSIONED"]
|
|
1536
|
-
editions: [full]
|
|
1537
1516
|
enforcers:
|
|
1538
1517
|
- {kind: hfs, id: saga-state-versioned, at: scripts/hfs/rules/saga.mjs}
|
|
1539
1518
|
- id: R171
|
|
@@ -1542,7 +1521,6 @@ rules:
|
|
|
1542
1521
|
kinds: [check]
|
|
1543
1522
|
gates: [land, ci]
|
|
1544
1523
|
failureCodes: ["BE_SAGA_EVENT_CONTRACT"]
|
|
1545
|
-
editions: [full]
|
|
1546
1524
|
enforcers:
|
|
1547
1525
|
- {kind: hfs, id: saga-event-contract, at: scripts/hfs/rules/saga.mjs}
|
|
1548
1526
|
- id: R172
|
|
@@ -1551,7 +1529,6 @@ rules:
|
|
|
1551
1529
|
kinds: [check]
|
|
1552
1530
|
gates: [land, ci]
|
|
1553
1531
|
failureCodes: ["BE_SAGA_CONSUMER_DEDUPE"]
|
|
1554
|
-
editions: [full]
|
|
1555
1532
|
enforcers:
|
|
1556
1533
|
- {kind: hfs, id: saga-consumer-dedupe, at: scripts/hfs/rules/saga.mjs}
|
|
1557
1534
|
- id: R173
|
|
@@ -1593,7 +1570,6 @@ rules:
|
|
|
1593
1570
|
kinds: [check]
|
|
1594
1571
|
gates: [settle, land, ci]
|
|
1595
1572
|
failureCodes: ["BE_CONTRACT_BREAKING"]
|
|
1596
|
-
editions: [full]
|
|
1597
1573
|
enforcers:
|
|
1598
1574
|
- {kind: hfs, id: contract-compat, at: scripts/hfs/rules/contract-compat.mjs}
|
|
1599
1575
|
- id: R178
|
|
@@ -1646,7 +1622,7 @@ rules:
|
|
|
1646
1622
|
- {kind: eslint-be, id: webhook-verify-first}
|
|
1647
1623
|
- id: R184
|
|
1648
1624
|
code: "BE_REALTIME_SHAPE"
|
|
1649
|
-
law: "A realtime file is the one door its role names: `<channel>.gateway.ts` holds one `@WebSocketGateway` class with `@SubscribeMessage` handlers and `<x>.subscription.ts` holds one `@Resolver` class with `@Subscription` operations, and neither carries a query, a mutation, a field resolver, a REST route or the other protocol."
|
|
1625
|
+
law: "A realtime file is the one door its role names: `<channel>.gateway.ts` holds one `@WebSocketGateway` class with `@SubscribeMessage` handlers and `<x>.subscription.ts` holds one `@Resolver` class with `@Subscription` operations, and neither carries a query, a mutation, a field resolver, a REST route or the other protocol. A lite app serves the same socket.io gateway from its api process and no graphql-ws subscription (R212 case-2)."
|
|
1650
1626
|
kinds: [lint]
|
|
1651
1627
|
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1652
1628
|
failureCodes: ["BE_REALTIME_SHAPE"]
|
|
@@ -1870,12 +1846,29 @@ rules:
|
|
|
1870
1846
|
- {kind: runtime, id: slot-id-shape, at: scripts/checks/check-slot-id-shape.mjs}
|
|
1871
1847
|
- id: R212
|
|
1872
1848
|
code: "HFS_EDITION_INVALID"
|
|
1873
|
-
law: "hfs.json names an edition the manifest knows (full, or lite; absent means full), and a lite app holds none of
|
|
1849
|
+
law: "hfs.json names an edition the manifest knows (full, or lite; absent means full). Lite = full minus self-hosted infrastructure: the same monorepo, code structure, slots, patterns and back-end unit specs, every pattern port implemented on Supabase (Postgres, Auth, Storage, Supabase Queues/pgmq for queue and event bus, Postgres locks, pg_cron), the realtime pattern on the same socket.io gateway run inside the be api process (one instance, no Redis adapter), and one front-end standard for both editions (knowledge/hfs/fe-stack.yaml); a lite app holds none of the infrastructure lite does not run: no e2e or contract spec, no `test:e2e`, `test:contract`, `test:stack` or `test:browser` script, no e2e workflow or browser journey, no UAT record, no test container, toxiproxy or browser-runner dependency, no BullMQ, Kafka, Redis or graphql-ws dependency (the libraries lite infraOwners own nowhere), no .starcistacks infra/runtime/seeds tree and no supabase/functions."
|
|
1874
1850
|
kinds: [check]
|
|
1875
1851
|
gates: [pre-push, settle, land, ci]
|
|
1876
1852
|
failureCodes: ["HFS_EDITION_INVALID", "HFS_EDITION_FORBIDDEN_PRESENT"]
|
|
1877
1853
|
enforcers:
|
|
1878
1854
|
- {kind: hfs, id: edition, at: scripts/hfs/rules/edition.mjs}
|
|
1855
|
+
cases:
|
|
1856
|
+
- id: case-1
|
|
1857
|
+
date: "2026-10-12"
|
|
1858
|
+
source: owner
|
|
1859
|
+
quote: "For a lite app the back end is exactly the same too, only deployed more simply; the fe and be of a lite app are exactly the same as a full app, except there is no e2e - unit tests stay; a lite app has no infra, a monorepo + Supabase; fe and be in a monorepo like a full app. (translated from Vietnamese)"
|
|
1860
|
+
ruling: "Lite = the full architecture minus self-hosted infrastructure. The same be/apps (api, cli, optional worker), be/src/{features,modules}, fe/apps and fe/packages, supabase/, .starcistacks and .starciwork; the same CQRS shape and patterns declared in hfs.json the same way. Only adapters and delivery differ, all on Supabase: schema authority in supabase/migrations; the queue pattern on Supabase Queues (pgmq) behind the same platform/queue ports (outbox to queue, processors, retry and backoff by visibility timeout and read_ct, archive on dead letter), no BullMQ or Redis; the event-bus pattern on pgmq-backed topics (or in process for a single api) behind the same EventPublisher/EventConsumer ports, no Kafka; realtime on Supabase Realtime (replaced by case-2: the socket.io gateway in the api process); cache and locks on Postgres (advisory locks or an unlogged table); schedules on pg_cron (+ pg_net) or the platform scheduler; storage and auth on Supabase. Back-end unit specs are required exactly as in full (unitRoles, logicRoles, 100 per file, @starci/jest-preset doubles, builders); integration specs are optional and run only on the local Supabase CLI stack (`starci supabase start`, no toxiproxy, no own containers); there is no e2e or contract world and no app.ci-e2e. .starcistacks holds env and secret custody only. The front end and the grammar stay exactly as in full: no tests (R97). The queue transport question was confirmed the same day: Supabase Queues (pgmq). Upgrade lite -> full stays additive: swap the adapters, add the e2e and contract world and the stack."
|
|
1861
|
+
- id: case-2
|
|
1862
|
+
date: "2026-10-13"
|
|
1863
|
+
source: owner
|
|
1864
|
+
quote: "Lite and full apps are the same code structure; lite only swaps the infrastructure for Supabase, has no self-hosted infrastructure and no e2e, and saga and jobs are built exactly like full. The front end of lite and full follows starci-academy: the browser calls the back end directly through modules/api/graphql (Apollo auth and no-auth clients with the link chain) and axios only for non-GraphQL calls, hooks/swr wraps every operation and components call hooks only, modules/toast reports mutation outcomes. Forbidden: Server Actions, Next route handlers except health/live, front-end data reads from Supabase (Supabase on the front end is auth only) and the rpc + polling command pattern; modules/db goes away. Every back-end feature exposes GraphQL for the front end; heavy work is a mutation that enqueues a job and the front end follows its progress. (translated from Vietnamese)"
|
|
1865
|
+
ruling: "Lite and full share one code structure; the edition changes only the infrastructure (Supabase Postgres, Auth, Storage, pgmq and pg_cron instead of self-hosted services), runs the socket.io gateway inside the be api process (one instance, no Redis adapter; be.feature.realtime.websocket is legal in lite and the Supabase Realtime door is gone) and has no e2e. The front end is one standard for both editions, catalogued in knowledge/hfs/fe-stack.yaml (one canonical library, HFS home and pattern per concern, versions in canon-pins.yaml): Apollo GraphQL in modules/api/graphql with codegen from be/contracts/<app>/schema.graphql, axios in modules/api/rest for uploads, presigned URLs and auth endpoints, SWR hooks in hooks/swr, react-hook-form with zod in hooks/rhf, redux for app-wide state, zustand for local state, socket.io-client in hooks/socketio, modules/toast for mutation outcomes and modules/auth as the only front-end Supabase importer (auth only). The front end serves no API: no Server Action anywhere (FE_SERVER_ACTION_FORBIDDEN) and no route handler beyond health/live and the fe.route.callback slot (FE_ROUTE_HANDLER_FORBIDDEN); a package outside the catalog's choice is FE_STACK_FORBIDDEN. Back-end features expose GraphQL (be.transport.graphql) for the front end and keep REST for webhooks, the public API and OAuth callbacks; heavy work is a mutation that enqueues a fenced job or saga, and the front end polls a status query with SWR refreshInterval or listens on a socket namespace."
|
|
1866
|
+
- id: case-3
|
|
1867
|
+
date: "2026-10-13"
|
|
1868
|
+
source: owner
|
|
1869
|
+
quote: "As the student proposes. (translated from Vietnamese; the owner adopted the proposed HFS standard for front end and back end, lite = full)"
|
|
1870
|
+
ruling: "One HFS tree for both editions: the repository root, be/ (apps, contracts, features, modules, tests) and fe/apps/<app>/src have the same shape in lite and full, and code above the platform/integrations seam is the same. The editions differ only behind that seam: the database and its migration authority (lite supabase/migrations; full TypeORM entities and migrations in be.persistence), the auth provider (lite Supabase Auth through fe.modules.auth and a JWKS-verified back end; full the app's identity provider), object storage (lite Supabase Storage; full S3-compatible storage), queue and jobs (lite pgmq and pg_cron; full BullMQ) behind platform/queue, realtime (lite the socket.io gateway in the api process, one instance; full socket.io scaled with a Redis adapter), the event-bus adapter (lite outbox and pgmq; full outbox and Kafka) and hosting; lite has no e2e. Migration baseline at the cutover of an existing app: the live schema is dumped into one baseline migration written to the current standard and marked applied on production without a re-run, the older migrations move to app.supabase.migrations.archive (supabase/migrations/_archive/, never applied and never judged), and new migrations append after the baseline. Assets: static templates, copy and presets are TS constants in the owning domain capability (<x>.templates.ts) and user-editable ones are seed rows; small build-time binaries such as fonts live in be.assets (be/src/assets/<kind>/); large and user media live in object storage and are fetched at runtime; there is no be/resources/."
|
|
1871
|
+
|
|
1879
1872
|
- id: R213
|
|
1880
1873
|
code: "DB_MIGRATION_SHAPE"
|
|
1881
1874
|
law: "A migration is `supabase/migrations/<ts14>_<kebab>.sql`; the stamp is a valid UTC instant not in the future, new migrations sort strictly after every migration on the base branch, and a migration already on the base is immutable (a diff or a removal is a finding); a file that does not parse as PostgreSQL is a shape failure."
|
|
@@ -1910,27 +1903,19 @@ rules:
|
|
|
1910
1903
|
- {kind: hfs, id: db-config-types, at: scripts/hfs/rules/database.mjs}
|
|
1911
1904
|
- id: R217
|
|
1912
1905
|
code: "FE_SUPABASE_CLIENT_OWNER"
|
|
1913
|
-
law: "
|
|
1906
|
+
law: "Supabase has one door per side: on the front end `@supabase/supabase-js` and `@supabase/ssr` are imported only by `fe.modules.auth` and only their auth API is called (sign-in, sign-out, session, access token; never `.from`, `.rpc`, `.storage` or `.channel`, so the front end reads and writes no data through Supabase), and on the back end only `be.integrations.supabase` imports them, every back-end client carrying the committed `Database` type; the front end holds no credential: `fe/` names no service-role or `*_SECRET`, `*_PRIVATE`, `*_TOKEN`, `*_PASSWORD` value (public configuration is read once by `modules/config`)."
|
|
1914
1907
|
kinds: [check, design]
|
|
1915
1908
|
gates: [pre-push, settle, land, ci]
|
|
1916
|
-
failureCodes: ["FE_SUPABASE_CLIENT_OWNER", "BE_SUPABASE_CLIENT_OWNER", "
|
|
1909
|
+
failureCodes: ["FE_SUPABASE_CLIENT_OWNER", "BE_SUPABASE_CLIENT_OWNER", "FE_SERVICE_ROLE_FORBIDDEN"]
|
|
1917
1910
|
enforcers:
|
|
1918
1911
|
- {kind: machine, id: supabase-client-owner, at: scripts/hfs/architecture/supabase.mjs}
|
|
1919
1912
|
- {kind: machine, id: supabase-service-role, at: scripts/hfs/architecture/supabase.mjs}
|
|
1920
|
-
- id: R218
|
|
1921
|
-
code: "FE_DB_RESULT_TYPED"
|
|
1922
|
-
law: "A Supabase result is typed by `Database` inference and never swallowed: no explicit generic on `single`/`maybeSingle`/`returns`, no cast or non-null assertion on a result, every list read carries `.limit()` or a keyset bound, every awaited call reads `error` or hands the whole result to `toOutcome`, and a failure never becomes empty data."
|
|
1923
|
-
kinds: [check, design]
|
|
1924
|
-
gates: [pre-push, settle, land, ci]
|
|
1925
|
-
failureCodes: ["FE_DB_RESULT_TYPED", "FE_DB_ERROR_HANDLED"]
|
|
1926
|
-
enforcers:
|
|
1927
|
-
- {kind: machine, id: supabase-result, at: scripts/hfs/architecture/supabase.mjs}
|
|
1928
1913
|
- id: R219
|
|
1929
|
-
code: "
|
|
1930
|
-
law: "The principal is verified, never trusted:
|
|
1914
|
+
code: "BE_SUPABASE_JWT_VERIFIED"
|
|
1915
|
+
law: "The principal is verified, never trusted: a back-end principal comes only from a Supabase access token verified against the project JWKS (signature, issuer, audience, expiry, pinned algorithm), never decoded unverified and never from the request body."
|
|
1931
1916
|
kinds: [check, design]
|
|
1932
1917
|
gates: [pre-push, settle, land, ci]
|
|
1933
|
-
failureCodes: ["
|
|
1918
|
+
failureCodes: ["BE_SUPABASE_JWT_VERIFIED"]
|
|
1934
1919
|
enforcers:
|
|
1935
1920
|
- {kind: machine, id: supabase-principal, at: scripts/hfs/architecture/supabase.mjs}
|
|
1936
1921
|
- id: R220
|
|
@@ -2153,3 +2138,21 @@ rules:
|
|
|
2153
2138
|
failureCodes: ["RT_OP_JUDGE_UNDECLARED"]
|
|
2154
2139
|
enforcers:
|
|
2155
2140
|
- {kind: runtime, id: op-judge, at: scripts/checks/check-op-judge.mjs}
|
|
2141
|
+
- id: R243
|
|
2142
|
+
code: "FE_SERVER_ACTION_FORBIDDEN"
|
|
2143
|
+
law: "The front end serves no API, in every app and edition (R212 case-2): no file under `fe/` carries a `\"use server\"` directive (module or function level), and the only route handlers are `app/health/live/route.ts` and the auth callback of the `fe.route.callback` slot; reads and writes reach the back end through `modules/api` and the SWR hooks, and a webhook, cron, public API or third-party callback is a back-end feature."
|
|
2144
|
+
kinds: [check, lint]
|
|
2145
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
2146
|
+
failureCodes: ["FE_SERVER_ACTION_FORBIDDEN", "FE_ROUTE_HANDLER_FORBIDDEN"]
|
|
2147
|
+
enforcers:
|
|
2148
|
+
- {kind: machine, id: server-action-forbidden, at: scripts/hfs/architecture/server-action.mjs}
|
|
2149
|
+
- {kind: eslint-fe, id: no-server-action}
|
|
2150
|
+
- id: R244
|
|
2151
|
+
code: "FE_STACK_FORBIDDEN"
|
|
2152
|
+
law: "A front end uses the one canonical library of each concern of knowledge/hfs/fe-stack.yaml: no front-end package.json (an app or a `fe/packages/*` package) depends on, and no source file under `fe/` imports, a package that the catalog lists under a concern's `forbids`; a missing concern is added to the catalog (with its pin in canon-pins.yaml), never installed beside it."
|
|
2153
|
+
kinds: [check, lint]
|
|
2154
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
2155
|
+
failureCodes: ["FE_STACK_FORBIDDEN"]
|
|
2156
|
+
enforcers:
|
|
2157
|
+
- {kind: hfs, id: fe-stack, at: scripts/hfs/rules/fe-stack.mjs}
|
|
2158
|
+
- {kind: eslint-fe, id: fe-stack-import}
|