@starci/eslint-canon-fe 8.4.39 → 8.4.41

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 (67) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +10 -0
  3. package/app-shell.mjs +161 -0
  4. package/callback-shape.mjs +1 -13
  5. package/docs/app-shell.md +50 -0
  6. package/docs/grammar-boundary.md +25 -1
  7. package/docs/surface-rules.md +41 -0
  8. package/grammar-boundary.mjs +112 -0
  9. package/hooks-folder.mjs +9 -3
  10. package/icon.mjs +31 -1
  11. package/index.mjs +6 -0
  12. package/lib/app-shell-cluster.mjs +66 -0
  13. package/lib/app-shell-create.mjs +33 -0
  14. package/lib/app-shell-owner-source.mjs +124 -0
  15. package/lib/app-shell-owner.mjs +65 -0
  16. package/lib/app-shell-scope.mjs +79 -0
  17. package/lib/app-shell-visible.mjs +92 -0
  18. package/lib/app-shell.mjs +148 -0
  19. package/lib/bindings.mjs +15 -0
  20. package/lib/config.mjs +1 -0
  21. package/lib/fe-stack.mjs +8 -4
  22. package/lib/scope.mjs +2 -2
  23. package/lib/typed-class-values.mjs +72 -0
  24. package/lib/why.mjs +42 -2
  25. package/lint-escape-hatch.mjs +2 -2
  26. package/menu-entries.mjs +133 -0
  27. package/package.json +1 -1
  28. package/runtime/knowledge/hfs/canon-pins.yaml +65 -9
  29. package/runtime/knowledge/hfs/fe-stack.yaml +23 -5
  30. package/runtime/knowledge/hfs/slots.yaml +113 -53
  31. package/runtime/knowledge/sonar-gate.yaml +1 -1
  32. package/runtime/modules/goal/source-phrases.yaml +95 -0
  33. package/runtime/modules/kernel/removed-vocabulary.yaml +62 -0
  34. package/runtime/modules/models/registry.yaml +3 -1
  35. package/runtime/modules/models/runtimes.yaml +2 -0
  36. package/runtime/scripts/api/git/ls-tree.mjs +5 -0
  37. package/runtime/scripts/api/git/show.mjs +6 -0
  38. package/runtime/scripts/hfs/architecture/dead-exports.mjs +10 -10
  39. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +1 -1
  40. package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +53 -0
  41. package/runtime/scripts/hfs/architecture/reachability.mjs +1 -1
  42. package/runtime/scripts/hfs/architecture/sql-owner.mjs +17 -6
  43. package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +105 -0
  44. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +38 -52
  45. package/runtime/scripts/hfs/architecture/supabase-keys.mjs +261 -0
  46. package/runtime/scripts/hfs/architecture/transport-owner.mjs +49 -3
  47. package/runtime/scripts/hfs/architecture/typescript.mjs +1 -1
  48. package/runtime/scripts/hfs/declaration-shape.mjs +4 -4
  49. package/runtime/scripts/hfs/declaration-slots.mjs +14 -3
  50. package/runtime/scripts/hfs/manifest-shape.mjs +10 -6
  51. package/runtime/scripts/hfs/rule-params-shape.mjs +18 -6
  52. package/runtime/scripts/hfs/rules/database-constants.mjs +26 -0
  53. package/runtime/scripts/hfs/rules/database-migrations.mjs +489 -0
  54. package/runtime/scripts/hfs/slot-side-problems.mjs +3 -3
  55. package/runtime/scripts/hfs/slots.mjs +2 -1
  56. package/runtime/scripts/hfs/view.mjs +2 -1
  57. package/runtime/scripts/lib/in-order.mjs +71 -0
  58. package/runtime/scripts/lib/language.mjs +94 -1
  59. package/runtime/scripts/lib/package-at.mjs +62 -0
  60. package/runtime/scripts/lib/same-text.mjs +5 -0
  61. package/runtime/scripts/lib/secret-patterns.mjs +58 -9
  62. package/runtime/scripts/lib/source-phrases.mjs +34 -0
  63. package/runtime/scripts/lib/ts-analysis.mjs +10 -0
  64. package/size-and-state-budget.mjs +3 -3
  65. package/surface-rules.mjs +230 -0
  66. package/swr-family.mjs +136 -7
  67. package/transport.mjs +2 -1
@@ -7,7 +7,7 @@
7
7
  # catalog READ this file through scripts/hfs/slots.mjs; none of them hardcodes a path.
8
8
  # Shape: modules/schemas/hfs-slots.schema.yaml. The repository side is hfs.json: modules/schemas/hfs-repo.schema.yaml.
9
9
  schema: starci/hfs-slots@3
10
- version: 3.6.0
10
+ version: 3.7.0
11
11
 
12
12
  versioning:
13
13
  # MAJOR.MINOR.PATCH of this manifest. A repository pins only the MAJOR in hfs.json ("hfs": 3).
@@ -42,7 +42,9 @@ versioning:
42
42
  # 3.5.0 is a minor (alpha.27): it ADDS the opt-in app.browser-config slot, the managed browser/tsconfig.json that type-checks the browser
43
43
  # journey (enabled with app.browser by `browser: true`).
44
44
  # 3.6.0 is a minor (alpha.30): it ADDS the opt-in be.feature.realtime.sse slot, the server-sent-events door of the realtime kind (owner decision
45
- # 2026-10-24: lite may serve SSE, and SSE is the default door for one-way push), and lets fe.modules.api hold the SSE stream readers (sse/).
45
+ # 2026-10-24: lite may serve SSE, and SSE is the default door for one-way push), and lets fe.modules.api hold the SSE stream operations (sse/).
46
+ # 3.7.0 is a minor (alpha.31): it ADDS the optional fe.transport.stream slot, the fetch reader of server-sent-events streams (the stream half of the
47
+ # transport client layer), and lets fe.hooks.swr hold the SWR subscription hooks (api/sse/subscriptions/useSubscribe<X>Swr.ts, one useSWRSubscription per stream operation).
46
48
 
47
49
  # Field meaning, per slot.
48
50
  # id stable name, never changes meaning inside a major.
@@ -109,15 +111,18 @@ testValues: [unit-beside, e2e, none]
109
111
  # The editions a repository declares in hfs.json `edition` (absent means full). ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite IS
110
112
  # the full architecture with Supabase backing services - the same monorepo, slots, CQRS shape and patterns (queue,
111
113
  # event bus, saga, reactors, projection, realtime, webhooks, fenced job), declared in hfs.json the same way, and the same unit
112
- # specs (unitRoles, logicRoles, 100 per file, @starci/jest-preset doubles, builders). What differs is ADAPTERS and DELIVERY, all
113
- # on Supabase except realtime: schema authority in supabase/migrations, queue and event-bus ports on Supabase Queues (pgmq),
114
- # cache and locks on Postgres, schedules on pg_cron (+ pg_net), storage and auth on Supabase. Realtime uses the socket.io gateway
115
- # in the api process, one instance and no Redis adapter. Integration and provider sandbox contract specs are optional;
114
+ # specs (unitRoles, logicRoles, 100 per file, @starci/jest-preset doubles, builders). What differs is ADAPTERS and DELIVERY
115
+ # (ruling:R212/case-10): schema authority in supabase/migrations, the queue port on BullMQ over the Redis hfs.json
116
+ # sides.be.backing declares (as in full, fed by the Postgres outbox), event-bus ports on pgmq-backed topics or in-process
117
+ # delivery, cache and locks on Postgres or the declared Redis, schedules on pg_cron (+ pg_net) enqueueing through the queue
118
+ # outbox, storage and auth on Supabase. Realtime uses SSE or the socket.io gateway
119
+ # in the api process, which scales horizontally and stateless (memory hub for one replica or tests; the Redis hub and @socket.io/redis-adapter when Redis is declared, else the Postgres hub and @socket.io/postgres-adapter, for N replicas; websocket only, no sticky sessions). Integration and provider sandbox contract specs are optional;
116
120
  # declared patterns owe their integration proofs (R284). Lite has no e2e world. Production uses declared docker-swarm under
117
- # .starcistacks/production/infra/swarm/ (owner ruling 2026-10-14); no self-hosted database or broker. The front end is full's (R97).
121
+ # .starcistacks/production/infra/swarm/ (owner ruling 2026-10-14); lite runs no self-hosted database, and runs only the backing
122
+ # services sides.be.backing declares. The front end is full's (R97).
118
123
  # Lite is this same manifest filtered: a slot whose `editions` lacks lite does not exist for a lite app, `litePresence` replaces
119
- # presence and a `lite` map replaces the fields it names. Upgrade lite -> full adds only (swap the adapters, add the e2e and
120
- # self-hosted backing services); downgrade is not supported. Lite contracts are allowed (owner decision 2026-10-14).
124
+ # presence and a `lite` map replaces the fields it names. The edition is chosen once at scaffold; there is no upgrade
125
+ # or downgrade between lite and full. Lite contracts are allowed (owner decision 2026-10-14).
121
126
  editions: [full, lite]
122
127
 
123
128
  # The two sides of an app: the folder of each is its profile name (be/, fe/). `reads` lists the only paths of the OTHER side a side
@@ -189,6 +194,9 @@ ruleParams:
189
194
  # R259: btree_gist supplies UUID equality for the booking interval exclusion constraint.
190
195
  # R259: unaccent is a first-party Supabase extension used by Vietnamese-content search functions.
191
196
  extensions: [pgmq, pg_cron, pgcrypto, pg_trgm, vector, uuid-ossp, pg_net, supabase_vault, unaccent, btree_gist]
197
+ # R258 DB_CRON_OWNED: THE queue outbox of a Supabase schema (the relay hands its unsent rows to BullMQ, ruling:R212/case-10). A pg_cron
198
+ # job enqueues only by inserting one row into this table, writing only these columns, one of which is `queue`.
199
+ queueOutbox: {table: public.queue, columns: [queue, payload, created_at, correlation_id]}
192
200
  common:
193
201
  # HFS_SIZE_GROWTH: a file above soft may not grow against its parent commit; a new file stays within soft. Slot
194
202
  # budgets (component.tsx 300, hooks 200, modules 400) are stricter than this cap where they apply.
@@ -210,12 +218,19 @@ ruleParams:
210
218
  "node:https": [platform/http]
211
219
  http: [platform/http]
212
220
  https: [platform/http]
221
+ "@aws-sdk/client-s3": [integrations/object-storage] # the ObjectStorage port: one S3 adapter for MinIO, S3, Spaces and Supabase Storage
222
+ "@aws-sdk/s3-request-presigner": [integrations/object-storage]
213
223
  cache-manager: [integrations/cache, integrations/redis]
214
224
  "@nestjs/cache-manager": [integrations/cache, integrations/redis] # the CACHE_MANAGER token: caching goes through the cache capability
215
225
  redis: [integrations/cache, integrations/redis]
216
- ioredis: [integrations/cache, integrations/redis]
226
+ ioredis: [integrations/cache, integrations/redis, platform/realtime]
227
+ "@keyv/redis": [integrations/cache, integrations/redis]
228
+ cache-manager-redis-yet: [integrations/cache, integrations/redis]
229
+ cache-manager-ioredis-yet: [integrations/cache, integrations/redis] # the Redis hub and the sharded socket.io adapter of platform/realtime
217
230
  bullmq: [platform/queue] # pattern queue: BullMQ jobs and job schedulers (the relay of the outbox into queues)
218
231
  "@nestjs/bullmq": [platform/queue]
232
+ bull: [platform/queue]
233
+ "@nestjs/bull": [platform/queue]
219
234
  kafkajs: [platform/event-bus] # pattern event-bus: Kafka is the only event transport
220
235
  "@nestjs/schedule": [] # no @Cron / @Interval / @Timeout anywhere: a schedule is a BullMQ job scheduler of a queue
221
236
  eventemitter2: []
@@ -224,6 +239,8 @@ ruleParams:
224
239
  "node:timers": [platform/retry, platform/event-bus, platform/queue, platform/http]
225
240
  "node:timers/promises": [platform/retry, platform/event-bus, platform/queue, platform/http]
226
241
  winston: [platform/logging]
242
+ "node:async_hooks": [platform/logging]
243
+ nestjs-cls: []
227
244
  dayjs: [platform/clock, platform/primitives]
228
245
  moment: [platform/clock, platform/primitives]
229
246
  date-fns-tz: [platform/clock, platform/primitives]
@@ -234,6 +251,9 @@ ruleParams:
234
251
  socket.io: [platform/realtime]
235
252
  graphql-subscriptions: [platform/realtime]
236
253
  graphql-ws: [platform/realtime]
254
+ "@socket.io/postgres-adapter": [platform/realtime] # the stateless socket.io adapter of a scaled lite back end without Redis
255
+ "@socket.io/redis-adapter": [platform/realtime] # the sharded socket.io adapter of a scaled back end on the declared Redis
256
+ pg: [platform/realtime] # the LISTEN connection of the Postgres hub; every other query goes through TypeORM
237
257
  # R87 BE_CQRS_SHAPE no-event-bus: the in-process event vocabulary the rule refuses. `imports` maps a module
238
258
  # specifier to the member names that are an in-process event; `[]` flags the package itself (its emitter packages
239
259
  # are the `[]` owners of infraOwners above). `classes` maps a module specifier to the class names that are an
@@ -249,8 +269,13 @@ ruleParams:
249
269
  # R48 BE_SPEC_QUALITY spec-infra-double-from-kit: in a `<name>.service.spec.ts` a provider `{ provide: TOKEN, useValue: X }` takes X from
250
270
  # the kit named here (the package root of `kit`). The token name is normalised to UPPER_SNAKE (`EntityManager` -> ENTITY_MANAGER), the
251
271
  # FIRST entry whose `token` regex matches decides, and `fallback` decides for every other token. `forms` say what X may be:
272
+ # Inbox-like names resolve their token declaration first: genuine platform/inbox aliases keep fakeInbox, while a business service
273
+ # or queue producer containing Inbox keeps its collaborator double. An unresolved origin retains the lexical table choice.
252
274
  # call = `double(...)`, new = `new Double(...)`, curried = `double(...)(...)`, object = a plain object literal (REAL values),
253
275
  # primitive = a string/number/boolean literal, array = an array literal (multi-providers of doubles). A const bound to such a value counts.
276
+ # An entry may add `from`, a regex over the path of the file that DECLARES its port's token: the declaration decides, not the import written
277
+ # in the spec. A token declared in another module that ends alike is a different port (`CORRELATION_IDS` of logging is not `IDS` of ids), and
278
+ # the owner's token stays that port however it is re-exported or renamed. A token that cannot be resolved is judged by its spelling.
254
279
  specDoubles:
255
280
  kit: "@starci/jest-preset"
256
281
  doubles:
@@ -263,7 +288,7 @@ ruleParams:
263
288
  - {token: "(?:^|_)INBOX(?:_|$)", double: fakeInbox, forms: [call]}
264
289
  - {token: "(?:^|_)CACHE(?:_|$)", double: fakeCache, forms: [call]}
265
290
  - {token: "(?:^|_)(?:LOCK|LEASE|FENCE|HOLD)(?:_|$)", double: fakeLock, forms: [call]}
266
- - {token: "(?:^|_)(?:IDS|ID_GENERATOR|ID_FACTORY)$", double: fakeIds, forms: [call]}
291
+ - {token: "(?:^|_)(?:IDS|ID_GENERATOR|ID_FACTORY)$", from: "/src/modules/platform/ids/", double: fakeIds, forms: [call]}
267
292
  fallback: {double: mock, forms: [call, primitive, array]}
268
293
  # R85 BE_RAW_INJECT injected-param-name: the names of the ports every class injects the same way (BE-CONVENTION 1.6: the parameter name
269
294
  # is the camelCase of the port). An entry matches a constructor parameter that carries an injector and is annotated with a type
@@ -281,7 +306,7 @@ ruleParams:
281
306
  # R89 BE_SOURCE_FORM: the closed role-suffix vocabulary of a source file name (<kebab-name>.<suffix>.ts; BE-CONVENTION 1.15)
282
307
  # and the suffixes that are never a role. index.ts, main.ts, app.module.ts and the migrations of be.persistence are the
283
308
  # only names outside it.
284
- suffixes: [module, module-definition, decorators, options, config, connection, service, schema, command, query, handler, templates, compensation, saga-state, saga-step, saga, contracts, port, resolver, controller, gateway, webhook, subscription, consumer, cli, event, queue, projection, projection-entity, processor, step, input, type, args, request, response, mapper, policy, entity, sql, rows, error, messages, log-events, cache-keys, guard, interceptor, filter, client, spec, integration-spec, e2e-spec, contract-spec, global-setup, builder]
309
+ suffixes: [module, module-definition, decorators, options, config, connection, service, schema, command, query, handler, templates, compensation, saga-state, saga-step, saga, contracts, port, resolver, controller, gateway, webhook, subscription, consumer, cli, event, queue, projection, projection-entity, processor, step, input, type, args, request, response, mapper, policy, entity, sql, rows, error, messages, log-events, cache-keys, guard, interceptor, filter, client, adapter, spec, integration-spec, e2e-spec, contract-spec, global-setup, builder]
285
310
  # R47 BE_CONTRACT_UNGUARDED: the world's shape vocabulary. `helper` names the function a contract spec imports to erase the values of a JSON
286
311
  # payload (`shapeOf`); a contract spec proves a fake's fixtures by asserting helper(real) equals helper(fixture).
287
312
  contractShape: {helper: shapeOf}
@@ -300,7 +325,10 @@ ruleParams:
300
325
  # options, config, command, query, contracts, port, event, queue, entity, sql, rows, messages, log-events, error, decorators, cache-keys,
301
326
  # projection-entity, connection, and every file with no role: index.ts, main.ts, migrations) is declaration or composition and is never measured.
302
327
  # A unit spec beside such a file (`<name>.<role>.spec.ts`) is legal but not required: the file owes 100, not a spec of its own.
303
- logicRoles: [service, schema, policy, projection, guard, mapper, client, filter, interceptor, processor, step, saga, saga-step, compensation, consumer, webhook]
328
+ logicRoles: [service, schema, policy, projection, guard, mapper, client, adapter, filter, interceptor, processor, step, saga, saga-step, compensation, consumer, webhook]
329
+ # R281 BE_SPEC_FORM_ALL_ROLES. THE logic roles whose file declares values Nest never constructs (a zod schema): its spec parses the declared
330
+ # value directly and builds no testing module; every other logic role is built through `Test.createTestingModule`.
331
+ valueRoles: [schema]
304
332
  # R203 BE_FEATURE_THIN. THE roles a feature-tier file is judged thin by: a file `<name>.<role>.ts` of one of these roles inside a slot of tier
305
333
  # feature maps parameters and makes ONE delegating call into modules/ (a service, the command or query bus, the event bus, a queue producer): no
306
334
  # branch, loop, ternary, try or throw, no computation; orchestration (step order, compensation, claim, fencing, retry) lives in platform/*.
@@ -308,12 +336,14 @@ ruleParams:
308
336
  # BE_PATTERN_SPEC_MISSING: a declared pattern (hfs.json sides.be.patterns) is proven on the test world. Each id below needs a test titled
309
337
  # "<pattern>/<id>: ..." in an e2e or integration spec (be/src/tests/e2e, be/src/tests/integration).
310
338
  patternScenarios:
339
+ logging: [correlation-survives-queue]
311
340
  event-bus: [atomic-with-transaction, relay-only-after-commit, redelivery-then-dead-letter, duplicate-delivery-is-a-noop]
312
341
  queue: [enqueue-atomic-with-transaction, relay-job-id-is-outbox-id, scheduler-fires-once]
313
342
  webhooks: [bad-signature-is-refused, replay-is-refused, accepted-delivery-publishes-once]
314
- realtime: [subscriber-receives-push, foreign-topic-is-isolated, disconnect-releases-subscription]
343
+ realtime: [subscriber-receives-push, foreign-topic-is-isolated, disconnect-releases-subscription, push-crosses-replicas, resume-after-reconnect]
315
344
  fenced-job: [claim-bumps-token, zombie-fenced-out, redispatch-isolated]
316
345
  projection: [recompute-is-idempotent, replay-rebuilds]
346
+ idempotency: [repeat-returns-same-outcome, reused-key-other-body-is-refused]
317
347
  # BE_KIND_DECLARATION: the patterns an app that uses a trigger kind declares in hfs.json sides.be.patterns; the platform capabilities of those
318
348
  # patterns (the slots of tier platform that name the pattern) must exist.
319
349
  kindPatterns:
@@ -338,23 +368,29 @@ ruleParams:
338
368
  api: {topic: api, variable: feature, trigger: api, patterns: [], needs: [service], defaults: {action: "@name"}}
339
369
  cli: {topic: cli, variable: group, trigger: cli, patterns: [], needs: [service], wire: {file: src/features/cli/cli.module.ts, import: "./@@group@@/@@group@@.module", symbol: "@@Group@@Module"}}
340
370
  bannedSuffixes: [use-case, repository, fixture, factory, store, worker, scheduler, cron, job, listener, dto, util, utils, helper, helpers, rules, types, constants, int-spec, harness-spec, test]
371
+ # ruling:R212/case-10 (R212 HFS_BACKING_UNDECLARED / HFS_BACKING_UNUSED, R276 STACKS_LITE_SELFHOSTED_DB): THE backing services a back
372
+ # end may declare in hfs.json sides.be.backing. `clients` are the libraries that consume the service: one of them as a dependency
373
+ # is its consumer, and under lite none of them may be a dependency while the service is undeclared. `patterns` are the declared
374
+ # patterns that need the service. `images` are the image repositories (the last path segment) a stack runs it from.
375
+ # `editions` limits admission: owner:lite-vector-is-pgvector keeps vector search on Supabase pgvector in lite.
376
+ backing:
377
+ redis: {clients: [bullmq, "@nestjs/bullmq", bull, "@nestjs/bull", ioredis, redis, "@keyv/redis", cache-manager-redis-yet, cache-manager-ioredis-yet, "@socket.io/redis-adapter"], patterns: [queue, fenced-job], images: [redis, redis-stack, redis-stack-server]}
378
+ qdrant: {clients: ["@qdrant/js-client-rest", "@qdrant/qdrant-js"], patterns: [], images: [qdrant], editions: [full]}
341
379
  # Edition lite (ruleParams(manifest, 'be', 'lite') merges these keys over the base; a map merges one level deep): the schema
342
380
  # authority is the Supabase migration tree (supabase/migrations/*.sql, forward-only), so a lite back end holds no TypeORM
343
381
  # migration or entity under be/ (R34/R35 read schemaAuthority; be.persistence's lite overlay allows neither). The unit roles,
344
- # logic roles and spec doubles are full's (owner ruling 2026-10-12 (owner:lite-production-swarm), with the Work boundary amended by ruling:R212/case-4). infraOwners: the self-hosted libraries are owned nowhere -
345
- # platform/queue and platform/event-bus run Supabase Queues (pgmq) through the shared EntityManager, cache and locks are
346
- # Postgres (advisory locks or an unlogged table), and realtime is the same socket.io gateway as full, run inside the api
347
- # process with no Redis adapter (owner ruling 2026-10-13).
382
+ # logic roles and spec doubles are full's (owner ruling 2026-10-12 (owner:lite-production-swarm), with the Work boundary amended by ruling:R212/case-4). infraOwners: full's owners,
383
+ # bullmq in platform/queue and ioredis/redis in integrations/cache and integrations/redis (ruling:R212/case-10: the queue is
384
+ # Redis in both editions; a backing library is legal only when hfs.json sides.be.backing declares its service, which
385
+ # HFS_BACKING_UNDECLARED checks), except Kafka, which is owned nowhere (platform/event-bus runs Supabase Queues (pgmq) through
386
+ # the shared EntityManager); realtime is the same socket.io gateway as full, run inside the api process, scaled horizontally and stateless (hub redis when
387
+ # Redis is declared, else hub postgres with @socket.io/postgres-adapter; no sticky sessions).
348
388
  lite:
349
389
  schemaAuthority: supabase
350
390
  infraOwners:
351
- bullmq: []
352
- "@nestjs/bullmq": []
353
391
  kafkajs: []
354
- redis: []
355
- ioredis: []
356
- graphql-subscriptions: []
357
- graphql-ws: []
392
+ "@qdrant/js-client-rest": []
393
+ "@qdrant/qdrant-js": []
358
394
 
359
395
  slots:
360
396
 
@@ -730,7 +766,7 @@ slots:
730
766
  - id: app.starcistacks
731
767
  since: 3.1.0
732
768
  profiles: [app]
733
- editionWhy: "owner:lite-production-swarm: lite requires declared docker-swarm production with Supabase backing; full also admits Compose development, Kubernetes and Terraform."
769
+ editionWhy: "owner:lite-production-swarm: lite requires declared docker-swarm production with Supabase backing; owner:dev-docker-prod-swarm keeps dependencies in Docker Linux containers in dev and the app and dependencies on Swarm in prod except hosted Supabase."
734
770
  exemplifiedBy:
735
771
  - {path: packages/hfs/templates/app/skeleton/.starcistacks/, edition: full}
736
772
  - {path: packages/hfs/templates/app/skeleton-lite/.starcistacks/, edition: lite}
@@ -916,7 +952,7 @@ slots:
916
952
  tracked: external
917
953
  tier: none
918
954
  tests: none
919
- goesTo: "a be feature: webhooks/<provider> for a third-party callback, api for an endpoint, a job of a pgmq-backed queue or a cli command on a schedule (pg_cron / pg_net)"
955
+ goesTo: "a be feature: webhooks/<provider> for a third-party callback, api for an endpoint, a job of a queue (BullMQ on the declared Redis, its tick enqueued through the queue outbox) or a cli command on a schedule (pg_cron / pg_net)"
920
956
  since: 2.1.0
921
957
 
922
958
  # ----- side root (both sides): what a standalone repository root holds, less the app-root files ----------
@@ -1436,7 +1472,7 @@ slots:
1436
1472
  since: 1.0.0
1437
1473
  - id: be.feature.realtime
1438
1474
  profiles: [be]
1439
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4) and ruling:R212/case-5 (owner decision 2026-10-24): lite serves server-sent events (the default door for one-way push) or runs socket.io in the api process, one instance without Redis; full may scale its gateway."
1475
+ editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4) and ruling:R212/case-5 (owner decision 2026-10-24): both editions serve server-sent events (the default door for one-way push), the socket.io gateway (two-way) and the restricted GraphQL subscription (ruling:R212/case-7) from the api process (owner:lite-graphql-subscription-door), and scale on the hub of their declared infrastructure (BE-REALTIME-7)."
1440
1476
  path: "src/features/realtime/<channel>/"
1441
1477
  presence: opt-in # a push channel: reads and pushes only (an SSE controller, a socket gateway or a GraphQL subscription), no domain write
1442
1478
  pattern: realtime # declaring the pattern (hfs.json sides.be.patterns) enables the kind (knowledge/patterns/be/realtime.yaml)
@@ -1449,9 +1485,8 @@ slots:
1449
1485
  tests: none
1450
1486
  lite:
1451
1487
  why: >-
1452
- a push channel: the server-sent-events door (be.feature.realtime.sse, the default for one-way push, owner decision 2026-10-24)
1453
- or the same socket.io gateway as full (owner ruling 2026-10-13), both run inside the be api process, one instance and no
1454
- Redis adapter; the front end listens through a use<Channel>Stream hook over modules/api/sse, or hooks/socketio (knowledge/patterns/be/realtime.yaml, liteAdapter)
1488
+ a push channel with the three doors of both editions: server-sent events (be.feature.realtime.sse, the default for one-way push, owner decision 2026-10-24), the socket.io gateway (two-way, owner ruling 2026-10-13) and the restricted GraphQL subscription (ruling:R212/case-7); all run inside the be api process, which scales horizontally and stateless
1489
+ on the hub of its declared infrastructure; the front end listens through a useSubscribe<Channel>Swr hook over modules/api/sse, or hooks/socketio (knowledge/patterns/be/realtime.yaml, liteAdapter)
1455
1490
  coverage: none
1456
1491
  rules: [BE_KIND_ISOLATION, BE_FEATURE_IMPORTS_FEATURE, BE_FEATURE_NOT_COMPOSED]
1457
1492
  since: 1.0.0
@@ -1459,14 +1494,11 @@ slots:
1459
1494
  - {path: packages/hfs/src/hfs-stacks-swarm.spec.mjs, edition: lite}
1460
1495
  - id: be.feature.realtime.graphql
1461
1496
  profiles: [be]
1462
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite pushes through the SSE door or the in-process socket.io gateway; the GraphQL subscription door remains full-only."
1463
1497
  path: "src/features/realtime/<channel>/transport/graphql/"
1464
- presence: opt-in # a GraphQL subscription over graphql-ws
1465
- litePresence: forbidden # ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite pushes through the api's SSE door or its in-process socket.io gateway
1498
+ presence: opt-in # a GraphQL subscription over graphql-ws, served in both editions (ruling:R212/case-7) and restricted: only when the client already uses GraphQL and needs the typed schema payload (knowledge/patterns/be/realtime.yaml)
1466
1499
  pattern: realtime
1467
1500
  tracked: tracked
1468
1501
  tier: feature
1469
- goesTo: "src/features/realtime/<channel>/transport/sse/ (be.feature.realtime.sse, the default for one-way push) or transport/websocket/ (be.feature.realtime.websocket): lite runs either door in the api process"
1470
1502
  requires: ["<channel>-graphql.module.ts", "<channel>.subscription.ts", "<channel>.subscription.spec.ts"]
1471
1503
  allows: ["<channel>-graphql.module.ts", "<channel>-graphql.module-definition.ts", "<channel>-graphql.options.ts", "<channel>.subscription.ts", "<channel>.subscription.spec.ts", dto/]
1472
1504
  tests: unit-beside # the door's own spec beside the door (a subscription has no service of its own)
@@ -1476,14 +1508,11 @@ slots:
1476
1508
  since: 1.0.0
1477
1509
  - id: be.feature.realtime.dto
1478
1510
  profiles: [be]
1479
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): GraphQL subscription DTOs exist only with the full subscription door; lite uses SSE or socket.io contracts."
1480
1511
  path: "src/features/realtime/<channel>/transport/graphql/dto/"
1481
1512
  presence: opt-in
1482
- litePresence: forbidden # the dto of the graphql door, which lite does not serve
1483
1513
  pattern: realtime
1484
1514
  tracked: tracked
1485
1515
  tier: feature
1486
- goesTo: "src/features/realtime/<channel>/transport/sse/ (be.feature.realtime.sse, the default for one-way push) or transport/websocket/ (be.feature.realtime.websocket): lite runs either door in the api process"
1487
1516
  allows: ["<name>.{input,type,args}.ts"]
1488
1517
  tests: none
1489
1518
  coverage: none
@@ -1647,13 +1676,28 @@ slots:
1647
1676
  # resolved through the RequestLocale provider (request: stored user preference, else Accept-Language, else
1648
1677
  # vi; non-request: recipient's stored preference, else vi - see knowledge/patterns/be/messages.yaml);
1649
1678
  # database when a connection exists.
1650
- requiredInstances: {capability: [config, logging, errors, primitives, clock, i18n]}
1679
+ requiredInstances: {capability: [config, errors, primitives, clock, i18n]}
1651
1680
  requires: [index.ts]
1652
1681
  forbids: [common/, utils/, shared/, testing/] # primitives live in platform/primitives
1653
1682
  tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
1654
1683
  coverage: required
1655
1684
  budget: {indexExports: 60}
1656
1685
  rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE]
1686
+ - id: be.platform.logging
1687
+ since: 3.5.0
1688
+ profiles: [be]
1689
+ path: "src/modules/platform/logging/"
1690
+ presence: required
1691
+ pattern: logging
1692
+ tracked: tracked
1693
+ tier: platform
1694
+ owner: true
1695
+ requires: [index.ts]
1696
+ forbids: [common/, utils/, shared/, testing/]
1697
+ tests: unit-beside
1698
+ coverage: required
1699
+ budget: {indexExports: 60}
1700
+ rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE]
1657
1701
  # ----- BE: pattern slots (opt-in; enabled by hfs.json sides.be.patterns). A path under one is HFS_SLOT_NOT_ENABLED unless its pattern is declared.
1658
1702
  - id: be.platform.event-bus
1659
1703
  since: 3.1.0
@@ -1670,7 +1714,7 @@ slots:
1670
1714
  the same EventPublisher and EventConsumer ports on Supabase Queues (pgmq): the outbox relay sends each committed row to the
1671
1715
  pgmq queue of every consumer group of its topic through the shared EntityManager, a consumer reads with a visibility
1672
1716
  timeout, retries by letting it lapse and archives the message on dead letter (read_ct over the budget); a single api may
1673
- deliver in process. No Kafka (knowledge/patterns/be/supabase-queues.yaml)
1717
+ deliver in process. No Kafka (knowledge/patterns/be/event-bus.yaml liteAdapter)
1674
1718
  requires: [index.ts, event-bus.module.ts, event-bus.port.ts, event-bus.contracts.ts, event-bus.service.ts, event-bus.service.spec.ts]
1675
1719
  tests: unit-beside
1676
1720
  coverage: required
@@ -1681,7 +1725,7 @@ slots:
1681
1725
  - id: be.platform.queue
1682
1726
  since: 3.1.0
1683
1727
  profiles: [be]
1684
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite implements queue ports with pgmq and pg_cron; full uses BullMQ and Redis."
1728
+ editionWhy: "ruling:R212/case-10: both editions implement the queue ports with BullMQ on Redis; lite declares its Redis in hfs.json sides.be.backing and keeps the outbox table in a supabase migration."
1685
1729
  path: "src/modules/platform/queue/"
1686
1730
  presence: opt-in
1687
1731
  pattern: queue # the ONE BullMQ owner: the outbox relay into queues (jobId = outbox id) and job schedulers (knowledge/patterns/be/queues.yaml)
@@ -1690,10 +1734,10 @@ slots:
1690
1734
  owner: true
1691
1735
  lite:
1692
1736
  why: >-
1693
- the same QueueOutbox and QueueWorkerRegistry ports on Supabase Queues (pgmq): the relay sends each committed outbox row with
1694
- pgmq.send (the outbox id travels in the message, so a second send is a no-op at the processor's inbox), processors read
1695
- with a visibility timeout, back off by extending it, and pgmq.archive a message whose read_ct exceeds the budget (the dead
1696
- letter); a schedule is a pg_cron job that enqueues. No BullMQ or Redis (knowledge/patterns/be/supabase-queues.yaml)
1737
+ the same QueueOutbox and QueueWorkerRegistry ports and full's BullMQ adapter on the declared Redis (backing: [redis]): the
1738
+ relay adds each committed outbox row with the outbox id as the job id, workers retry with exponential backoff and keep a
1739
+ job that exhausts its attempts as failed (the dead letter); the outbox table comes from a supabase migration, and a
1740
+ schedule is a BullMQ job scheduler or a pg_cron tick that inserts one outbox row (knowledge/patterns/be/queues.yaml liteAdapter)
1697
1741
  requires: [index.ts, queue.module.ts, queue.port.ts, queue.contracts.ts, queue-outbox.service.ts, queue-outbox.service.spec.ts]
1698
1742
  tests: unit-beside
1699
1743
  coverage: required
@@ -1786,7 +1830,7 @@ slots:
1786
1830
  - id: be.platform.jobs
1787
1831
  since: 3.1.0
1788
1832
  profiles: [be]
1789
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite persists fenced claims in Supabase migrations and dispatches through pgmq; full uses TypeORM schema authority and its queue adapter."
1833
+ editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite persists fenced claims in Supabase migrations and dispatches through the BullMQ queue on its declared Redis (ruling:R212/case-10); full uses TypeORM schema authority and the same queue adapter."
1790
1834
  path: "src/modules/platform/jobs/"
1791
1835
  presence: opt-in
1792
1836
  pattern: fenced-job # the ONE fencing primitive: the job entity, the claim that bumps the token, guarded writes (knowledge/patterns/be/jobs.yaml)
@@ -2138,8 +2182,8 @@ slots:
2138
2182
  rules: [FE_HOOKS_ARE_HOOKS, FE_DATA_FRESHNESS]
2139
2183
  - id: fe.hooks.swr
2140
2184
  profiles: [fe]
2141
- # The data hooks (fe-stack data, FE-DATA-SWR): one useSWR / useSWRMutation wrapper per modules/api operation, keyed
2142
- # QUERY_<X>_SWR_KEY / MUTATE_<X>_SWR_KEY plus the identity of the request; SwrSideEffects.tsx runs the app-wide hooks once.
2185
+ # The data hooks (fe-stack data, FE-DATA-SWR): one useSWR / useSWRMutation / useSWRSubscription wrapper per modules/api operation, keyed
2186
+ # QUERY_<X>_SWR_KEY / MUTATE_<X>_SWR_KEY / SUBSCRIBE_<X>_SWR_KEY plus the identity of the request; SwrSideEffects.tsx runs the app-wide hooks once.
2143
2187
  path: "apps/<app>/src/hooks/swr/"
2144
2188
  presence: optional
2145
2189
  tracked: tracked
@@ -2148,7 +2192,9 @@ slots:
2148
2192
  requires: [index.ts]
2149
2193
  allows: [index.ts, SwrSideEffects.tsx, api/index.ts, "api/{graphql,rest}/index.ts", "api/{graphql,rest}/{queries,mutations}/index.ts",
2150
2194
  "api/graphql/queries/useQuery*Swr.ts", "api/graphql/mutations/useMutate*Swr.ts",
2151
- "api/rest/queries/useGet*Swr.ts", "api/rest/mutations/use{Post,Put,Patch,Delete}*Swr.ts"]
2195
+ "api/rest/queries/useGet*Swr.ts", "api/rest/mutations/use{Post,Put,Patch,Delete}*Swr.ts",
2196
+ "api/graphql/subscriptions/index.ts", "api/graphql/subscriptions/useSubscribe*Swr.ts",
2197
+ "api/sse/index.ts", "api/sse/subscriptions/index.ts", "api/sse/subscriptions/useSubscribe*Swr.ts"]
2152
2198
  roles: {entry: index.ts, effects: SwrSideEffects.tsx}
2153
2199
  tests: none
2154
2200
  budget: {file: 200, barrel: 400}
@@ -2271,18 +2317,19 @@ slots:
2271
2317
  tier: transport
2272
2318
  # The browser's calls to the back end (owner ruling 2026-10-13, fe-stack graphql/rest/codegen, FE-API-GRAPHQL): the auth and
2273
2319
  # no-auth Apollo clients with their link chain (graphql/clients), one file per operation (graphql/queries/query-<x>.ts,
2274
- # graphql/mutations/mutation-<x>.ts with its gql document map and typed request/response in types/), the generated wire types
2320
+ # graphql/mutations/mutation-<x>.ts and graphql/subscriptions/subscription-<x>.ts, each with its gql document map and typed request/response in types/), the generated wire types
2275
2321
  # (graphql/__generated__), the axios calls (rest/<area>/*.ts) for uploads, presigned URLs and auth endpoints and the fetch readers of
2276
- # server-sent-events streams (sse/<area>/*.ts, one file per stream, the bearer token from modules/auth). A repository has
2322
+ # server-sent-events streams (sse/<area>/stream-<channel>.ts, one operation per stream, run on the stream reader of sse/clients). A repository has
2277
2323
  # exactly ONE client folder and ONE GraphQLResponse union: the app's (fe.transport.client/outcome) when one app calls the back end,
2278
2324
  # or the api package's (fe.package.api.client/outcome) when several do (FE_TRANSPORT_OWNER, machine).
2279
2325
  requires: [index.ts]
2280
2326
  allows: [index.ts, graphql/index.ts, graphql/types.ts, "graphql/clients/**", "graphql/queries/query-*.ts", "graphql/queries/types/*.ts",
2281
- "graphql/mutations/mutation-*.ts", "graphql/mutations/types/*.ts", "graphql/__generated__/**", "rest/index.ts", "rest/<area>/*.ts",
2282
- "sse/index.ts", "sse/<area>/*.ts"]
2327
+ "graphql/mutations/mutation-*.ts", "graphql/mutations/types/*.ts", "graphql/subscriptions/subscription-*.ts", "graphql/subscriptions/types/*.ts",
2328
+ "graphql/__generated__/**", "rest/index.ts", "rest/<area>/*.ts",
2329
+ "sse/clients/**", "sse/*/stream-*.ts"]
2283
2330
  roles: {entry: index.ts, query: "query-*.ts", mutation: "mutation-*.ts"}
2284
2331
  # A hook imports its one operation file directly (one file per operation, FE-API-GRAPHQL), never through a barrel of every operation.
2285
- entries: ["graphql/queries/query-*.ts", "graphql/queries/types/*.ts", "graphql/mutations/mutation-*.ts", "graphql/mutations/types/*.ts", "sse/<area>/*.ts"]
2332
+ entries: ["graphql/queries/query-*.ts", "graphql/queries/types/*.ts", "graphql/mutations/mutation-*.ts", "graphql/mutations/types/*.ts", "graphql/subscriptions/subscription-*.ts", "graphql/subscriptions/types/*.ts", "sse/*/stream-*.ts"]
2286
2333
  tests: none
2287
2334
  rules: [FE_TRANSPORT_OWNER, FE_HTTP_STATUS_COLLAPSE, FE_WIRE_GENERATED]
2288
2335
  - id: fe.transport.generated
@@ -2307,6 +2354,19 @@ slots:
2307
2354
  tests: none
2308
2355
  why: the auth and no-auth Apollo clients and their link chain (retry, error, timeout, token refresh, attach access token, http) of the one app that calls the back end; when several apps do, the shared client is fe.package.api.client
2309
2356
  rules: [FE_TRANSPORT_OWNER]
2357
+ - id: fe.transport.stream
2358
+ since: 3.7.0
2359
+ profiles: [fe]
2360
+ path: "apps/<app>/src/modules/api/sse/clients/"
2361
+ presence: optional
2362
+ tracked: tracked
2363
+ tier: transport
2364
+ allows: ["*.ts"]
2365
+ tests: none
2366
+ why: "the fetch reader of server-sent-events streams of the one app that calls the back end, the stream half of the transport client layer (FE-REALTIME): it opens the stream with the bearer access token, parses the events and reopens it with Last-Event-ID after a capped, doubling wait; a stream operation (sse/<area>/stream-<channel>.ts) runs on it and a useSubscribe hook of hooks/swr reads that operation; a fetch here asks for text/event-stream and only a stream operation imports the reader (FE_TRANSPORT_OWNER); a repository where several apps stream keeps one reader in each of them"
2367
+ rules: [FE_TRANSPORT_OWNER]
2368
+ exemplifiedBy:
2369
+ - {path: packages/eslint/fe/r50-transport-owner-coverage.spec.mjs, editions: [lite, full]}
2310
2370
  - id: fe.transport.outcome
2311
2371
  since: 3.1.0
2312
2372
  profiles: [fe]
@@ -129,7 +129,7 @@ enforces:
129
129
  - {rule: R21, code: HFS_DUPLICATE_CODE, enforcer: duplicated-lines-density, condition: duplication}
130
130
  - {rule: R21, code: HFS_DUPLICATE_SYMBOL, enforcer: duplicated-lines-density, condition: duplication}
131
131
  # Ops that write product code: each runs sonar-local on its own change and cannot settle done while the gate is red.
132
- enforcedOps: [backend.implement, interface.implement, code.refactor]
132
+ enforcedOps: [backend.implement, interface.implement, code.refactor, code.fix]
133
133
  reasoning: |
134
134
  Chosen against the four projects the local server holds on 2026-09-29 (whole-project duplicated lines
135
135
  1.6 / 2.1 / 1.7 / 0.0 percent), so new code that meets them is at or below what main already carries.
@@ -0,0 +1,95 @@
1
+ # ============================================================================
2
+ # modules/goal/source-phrases.yaml
3
+ #
4
+ # The Vietnamese phrase lists runtime source matches against owner or product
5
+ # text (scripts/lib/source-phrases.mjs). This is DATA for matchers, never
6
+ # owner-visible messages - those live in modules/i18n/messages/ (i18n.mjs).
7
+ # Each list holds the Vietnamese alternatives one matcher adds to its English
8
+ # pattern (a regex fragment when the source pattern used regex syntax, a plain
9
+ # word otherwise); the consumer is named above each group. The leaf keys are
10
+ # declared Vietnamese fields (scripts/lib/language.mjs DECLARED_VIETNAMESE_FIELDS),
11
+ # like the signal phrase lists of archetypes.yaml.
12
+ # ============================================================================
13
+
14
+ schema: starci/module-goal-source-phrases@1
15
+
16
+ phrases:
17
+
18
+ # packages/eslint/fe/lib/app-shell-create.mjs - localized labels of create actions prohibited in the navbar.
19
+ appShell:
20
+ product: ['Tạo mới', 'tao moi']
21
+
22
+ # scripts/agent/bias.mjs extractRoutingBias - the routing markers an owner
23
+ # prompt types before a pool alias. Regex alternatives; syllables join with \s*.
24
+ bias:
25
+ prefer: ['ưu\s*tiên', 'uu\s*tien', 'u-u\s*tien', prefer]
26
+ avoid: ['không\s*dùng', 'khong\s*dung', 'tránh', 'tranh', avoid, 'don''t\s+use']
27
+ only: ['chỉ\s*dùng', 'chi\s*dung', 'only\s+use', only]
28
+
29
+ # scripts/checks/check-guidance-commands.mjs excusedBy - a negation or a
30
+ # refusal verb, in either language, excuses a refused command in guidance.
31
+ guidance:
32
+ negation: ['không', 'đừng', 'cấm', 'tránh']
33
+ refused: ['bị chặn', 'bị từ chối']
34
+
35
+ # scripts/kernel/ask-server.mjs drawReplyDecision - a Telegram reply made only
36
+ # of accept words (plus optionally "golden") accepts a drawing. accept is a
37
+ # word list; golden holds regex alternatives.
38
+ drawReply:
39
+ accept: ['duyệt', 'đồng', 'ý', 'chấp', 'nhận', 'được', 'đẹp', 'rồi', 'nhé', 'nha', 'ừ']
40
+ golden: ['(?:đặt làm |làm )?(?:mẫu|ảnh|hình) chuẩn', 'làm mẫu']
41
+
42
+ # scripts/kernel/progress-rca.mjs causesOf - the blocker/report phrases of
43
+ # each failure cause, whichever language the report was written in.
44
+ rca:
45
+ missingPaths: ['không tồn tại', 'vắng mặt', '0 tệp', 'quét 0']
46
+ grantTooNarrow: ['ngoài owned', 'ngoài allowlist', 'ngoài grant', 'ngoài own']
47
+ toolTimeout: ['30 giây', 'ngắt sau']
48
+ testGap: ['kiểm thử hồi quy']
49
+ checkerUnavailable: ['không khả dụng']
50
+
51
+ # scripts/supervisor/owed.mjs - notes addressed to the supervisor, conditions
52
+ # only the owner can meet, and the label heuristics.
53
+ owed:
54
+ supervisorVerbs: ['cho', 'cần', 'chờ', 'đợi', 'phản hồi(?: của)?', 'hỏi']
55
+ ownerWord: ['chủ sở hữu']
56
+ orWord: ['hoặc']
57
+ beyondAuthority: ['ngoài thẩm quyền']
58
+ ownerOnly: ['bàn giao', 'mật khẩu', 'thanh toán']
59
+ workerDied: ['không nộp report']
60
+ contractConflict: ['mâu thuẫn']
61
+ decision: ['ủy quyền', 'uỷ quyền', '\bquyết\b']
62
+
63
+ # scripts/work/draw-feedback.mjs - the owner-note classification words
64
+ # (GOLDEN_WORDS, product-direction, grammar, knowledge).
65
+ drawNote:
66
+ golden: ['mẫu chuẩn', 'ảnh chuẩn', 'hình chuẩn']
67
+ product: ['luôn', 'không bao giờ', 'mọi', 'tất cả', 'từ nay', 'màu', 'thương hiệu', 'phông', 'chữ', 'khoảng cách', 'bo góc', 'đổ bóng', 'biểu tượng']
68
+ grammar: ['thành phần mới', 'biến thể', 'thiếu thành phần', 'không có thành phần']
69
+ knowledge: ['kiến thức', 'quy tắc chung', 'nguyên tắc chung']
70
+
71
+ # scripts/work/draw/draw-quality.mjs - internal-copy jargon, actor and
72
+ # success words a render source's visible copy may carry in either language.
73
+ drawCopy:
74
+ sourceLabel: ['nguồn']
75
+ internalVocabulary: ['nguồn hiện tại', 'hệ thống lõi']
76
+ actor: ['người dùng', 'chủ']
77
+ success: ['đã cài', 'đang hoạt động', 'sẵn sàng', 'đã xác nhận', 'thành công']
78
+
79
+ # scripts/work/ui-archetype.mjs archetypeOf - surface title/intent words in
80
+ # either language that derive an archetype when ui.archetype is absent.
81
+ uiArchetype:
82
+ wizard: ['thiết lập', 'các bước']
83
+ form: ['chỉnh sửa', 'tạo mới', 'cài đặt', 'đăng nhập']
84
+ dashboard: ['tổng quan', 'trang chủ']
85
+
86
+ # scripts/work/grammar-proposal.mjs MD_FIELDS - the section markers of a
87
+ # markdown grammar proposal in either language.
88
+ proposal:
89
+ gap: ['không có', 'thiếu']
90
+ anatomy: ['giải phẫu']
91
+
92
+ # scripts/lib/redact.mjs - the verification-code label a secret value may sit
93
+ # behind (regex alternative; a separator between syllables is [ _-]?).
94
+ redact:
95
+ otpLabel: ['mã[ _-]?(?:otp|xác[ _-]?(?:thực|minh))']
@@ -8,6 +8,27 @@
8
8
  # a word to scan for).
9
9
  kinds: [config-key, bias-field, verb, flag, code, vocabulary, file]
10
10
  removed:
11
+ - id: cut-seam-command
12
+ kind: flag
13
+ name: cut-seam --command
14
+ match: 'cut-seam[^\n]*--command\b'
15
+ use: 'cut-seam --reconcile reads the commands and selection from --checks-file'
16
+ since: 1.0.0-alpha.31
17
+ - id: cut-set-closing-check
18
+ kind: vocabulary
19
+ name: closingCheck
20
+ use: 'modules/kernel/check-policy.yaml#regression owns integration checks after job merges; closingOrdinal identifies only the final open ordinal'
21
+ since: 1.0.0-alpha.31
22
+ - id: verb-guard-input
23
+ kind: verb
24
+ name: starci guard input
25
+ use: 'the reference-transaction hook reads prepared ref updates directly in its shell loop'
26
+ since: 1.0.0-alpha.31
27
+ - id: hook-input-timeout-ms
28
+ kind: config-key
29
+ name: hook-input.timeoutMs
30
+ use: 'modules/kernel/hook-input.yaml declares the input window as minInputWindowMs'
31
+ since: 1.0.0-alpha.31
11
32
  - id: bias-require
12
33
  kind: bias-field
13
34
  name: require
@@ -339,3 +360,44 @@ removed:
339
360
  use: 'the runtime has no specs of its own (owner ruling no-runtime-specs); specs.unit and specs.e2e switch the product workflow tests'
340
361
  since: 1.0.0-alpha.19
341
362
  match: '\bspecs\.harness\b'
363
+ - id: file-supabase-queues-topic
364
+ kind: file
365
+ name: supabase-queues.yaml
366
+ use: 'the queue is BullMQ on Redis in both editions (ruling:R212/case-10): knowledge/patterns/be/queues.yaml owns the adapter, and each pattern topic''s liteAdapter says what lite uses with and without its declared backing service'
367
+ since: 1.0.0-alpha.31
368
+ match: '(?<![\w-])supabase-queues(?:\.yaml)?\b'
369
+ - id: code-rt-gate-loosening
370
+ kind: code
371
+ name: RT_GATE_LOOSENING
372
+ use: 'the tree is judged as it stands, never by its history: RT_LOOSENING_STATE (the loosening-state self-check) with the design-truths manifest knowledge/hfs/design-truths.yaml'
373
+ since: 1.0.0-alpha.31
374
+ - id: vocabulary-gate-loosening
375
+ kind: vocabulary
376
+ name: gate-loosening
377
+ use: 'the diff-based detector and its table are gone: the loosening-state self-check (scripts/checks/check-loosening-state.mjs) reads the tree as it stands against knowledge/hfs/design-truths.yaml'
378
+ since: 1.0.0-alpha.31
379
+ match: 'gate-loosening|gateLoosening|loosensReleased|looseningsOf|check-code-loosening'
380
+ - id: verb-runtime-benchmark-snapshot
381
+ kind: verb
382
+ name: starci runtime benchmark-snapshot
383
+ use: 'routing evidence is read live from the ledgers: `starci machine op-metrics --by op` and the ledger view v_model_scorecard (modules/models/registry.yaml costPolicy)'
384
+ since: 1.0.0-alpha.31
385
+ match: '(?<![\w-])benchmark-snapshot(?![\w-])'
386
+ - id: vocab-benchmark-scorecard
387
+ kind: vocabulary
388
+ name: the benchmark/ model-pool scorecard
389
+ use: 'routing evidence is read live from the ledgers: `starci machine op-metrics --by op` and the ledger view v_model_scorecard (modules/models/registry.yaml costPolicy)'
390
+ since: 1.0.0-alpha.31
391
+ match: '(?<![\w./-])benchmark/(?:README\.md|expectations\.yaml|snapshots\b|findings\b)|benchmark-expectations@1'
392
+ - id: slot-runtime-benchmark
393
+ kind: vocabulary
394
+ name: runtime.benchmark
395
+ use: 'the runtime tree has no benchmark slot; routing evidence is read live from the ledgers (modules/models/registry.yaml costPolicy)'
396
+ since: 1.0.0-alpha.31
397
+ match: '\bruntime\.benchmark\b'
398
+ - id: config-release-tagmatchesversion
399
+ kind: config-key
400
+ name: release.tagMatchesVersion
401
+ use: 'the release tag is always <release.tagPrefix><package.json version>; delete the key'
402
+ since: 1.0.0-alpha.31
403
+ match: '\btagMatchesVersion\b'
@@ -192,7 +192,9 @@ costPolicy: |-
192
192
  imagegen call tier (tiers.yaml calls, tierUse) and holds one provider slot while it runs. Every automatic launch contains only worker-start candidates and advances only
193
193
  after a verified no-effect failure. Agents have explicit turn, wall-time and tool-call ceilings; a ceiling stop is a
194
194
  failed attempt and never proof of completion. Never infer price or quality superiority from a subscription label;
195
- benchmark bounded representative operations before proposing another tier change. A native worker is released
195
+ a tier change is proposed only on live ledger evidence: `starci machine op-metrics --by op` (success rate, retry
196
+ chains and repeated failures per op) and the ledger view `v_model_scorecard` (pass rate and tokens per op, agent
197
+ and model). A native worker is released
196
198
  after its exact worker_done result is accepted; the workflow child remains until integration is complete.
197
199
  assetFallback: When image generation is unavailable, reuse verified assets first; Claude may leave
198
200
  decorative image slots blank with a concrete deferred brief. Never mark missing artwork generated
@@ -442,6 +442,7 @@ allocation:
442
442
  interface.audit: {mb: 3000, class: heavy}
443
443
  brand.decide: {mb: 3000, class: heavy}
444
444
  code.refactor: {mb: 2000, class: heavy}
445
+ code.fix: {mb: 2000, class: heavy}
445
446
  test.author: {mb: 2500, class: heavy}
446
447
  task.execute: {mb: 2000, class: heavy}
447
448
  backend.scaffold: {mb: 2000, class: heavy}
@@ -513,6 +514,7 @@ roleOfKind:
513
514
  package.scaffold: {role: implement, work: hands-on, floor: easy}
514
515
  scope.define: {role: decide, work: think, floor: hard}
515
516
  code.refactor: {role: implement, work: hands-on, floor: easy}
517
+ code.fix: {role: implement, work: hands-on, floor: medium}
516
518
  test.author: {role: implement, work: hands-on, floor: easy}
517
519
  grammar.update: {role: implement, work: hands-on, floor: hard}
518
520
  architecture.revise: {role: decide, work: think, floor: hard}