@starci/hfs 4.7.45 → 4.7.46

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 (38) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/lint/run.mjs +1 -1
  3. package/package.json +1 -1
  4. package/release/candidate-ci.mjs +7 -7
  5. package/release/push.mjs +3 -3
  6. package/runtime/config.example.yaml +9 -10
  7. package/runtime/engine/config.mjs +3 -3
  8. package/runtime/engine/release-config.mjs +5 -5
  9. package/runtime/knowledge/grammars/offset-pop/playbook.yaml +1 -1
  10. package/runtime/knowledge/grammars/starci/playbook.yaml +16 -2
  11. package/runtime/knowledge/hfs/canon-pins.yaml +7 -7
  12. package/runtime/knowledge/hfs/ci-standard.yaml +1 -1
  13. package/runtime/knowledge/hfs/rules.yaml +36 -5
  14. package/runtime/knowledge/patterns/be/realtime.yaml +1 -0
  15. package/runtime/modules/kernel/command-policy.yaml +5 -5
  16. package/runtime/modules/kernel/failure-codes.yaml +32 -20
  17. package/runtime/modules/kernel/removed-vocabulary.yaml +33 -7
  18. package/runtime/packages/cli/src/catalog.generated.mjs +90 -37
  19. package/runtime/scripts/guards/command-policy.mjs +4 -4
  20. package/runtime/scripts/guards/push-scan.mjs +128 -0
  21. package/runtime/scripts/guards/release-push-gate.mjs +31 -29
  22. package/runtime/scripts/hfs/example-secrets.mjs +4 -4
  23. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +5 -5
  24. package/runtime/scripts/hfs/runtime-rules/git-triggers.mjs +61 -0
  25. package/runtime/scripts/hfs/runtime-rules/hook-shape.mjs +4 -6
  26. package/runtime/scripts/lib/release-version.mjs +134 -134
  27. package/runtime/scripts/machine/home.mjs +2 -2
  28. package/templates/app/ci-workflows/github/workflows/ci.yml +2 -2
  29. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +2 -2
  30. package/templates/app/hooks/husky/pre-push +3 -56
  31. package/release/supabase-bucket.mjs +0 -8
  32. package/runtime/scripts/api/fs/write-json-file.mjs +0 -10
  33. package/runtime/scripts/api/git/merge-base-query.mjs +0 -5
  34. package/runtime/scripts/gates/commit-proof.mjs +0 -53
  35. package/runtime/scripts/guards/release-definition.mjs +0 -204
  36. package/runtime/scripts/guards/release-record.mjs +0 -58
  37. package/runtime/scripts/guards/release-suite-mode.mjs +0 -15
  38. package/runtime/scripts/hfs/runtime-rules/release-notes.mjs +0 -44
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.7.46 - 2026-10-26
4
+
5
+ - Changed: owner ruling push-main-after-scoped-check reaches product apps: the generated pre-push hook passes a fast-forward main push when the hooks pass and the secret scan is clean, a v* tag must be annotated, and app CI runs on pushes to main.
6
+ - Changed: candidate-ci keeps only catalogued failure codes.
7
+
3
8
  ## 4.7.45 - 2026-10-26
4
9
 
5
10
  - Changed: the alpha.31.1 round folded into alpha.31; the bundled canon pins and runtime copies follow the final package versions. No rule changes.
package/lint/run.mjs CHANGED
@@ -191,7 +191,7 @@ function exportsTarget(entry) {
191
191
  }
192
192
 
193
193
  /** The import entry of a package found by the app's own node_modules lookup (the folders require.resolve.paths lists from cwd). */
194
- export function importEntry(require, packageName) {
194
+ function importEntry(require, packageName) {
195
195
  for (const dir of require.resolve.paths(packageName) ?? []) {
196
196
  const manifest = path.join(dir, packageName, 'package.json');
197
197
  if (!fs.existsSync(manifest)) continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starci/hfs",
3
- "version": "4.7.45",
3
+ "version": "4.7.46",
4
4
  "description": "The StarCi app command implementation for scaffolding, linting, checking and synchronizing one product repository.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -2,9 +2,9 @@
2
2
  // red, with the owner confirming afterwards"). The push sends the exact release sha to refs/heads/release/candidate-<version> first (the managed ci.yml triggers on
3
3
  // release/candidate-*), waits for the ci.yml run of THAT sha, and moves the default branch and the tag only when it is green:
4
4
  // green the newest non-cancelled run of the sha concluded success and none of its jobs was skipped
5
- // red RELEASE_CANDIDATE_CI_RED (a run or a job that concluded skipped is red, never a pass: owner ruling 2026-10-22, a skip is a loosening) with the run URL and the failing jobs and steps; nothing reaches the default branch. The candidate ref stays for diagnosis
6
- // timeout RELEASE_CANDIDATE_CI_TIMEOUT: no run listed within the grace (a queued run is listed), or it did not end within the wait
7
- // unknown RELEASE_CI_UNKNOWN: GitHub could not be read to the end; a release never proceeds blind
5
+ // red "candidate CI red" (a run or a job that concluded skipped is red, never a pass: owner ruling 2026-10-22, a skip is a loosening) with the run URL and the failing jobs and steps; nothing reaches the default branch. The candidate ref stays for diagnosis
6
+ // timeout "candidate CI timeout": no run listed within the grace (a queued run is listed), or it did not end within the wait
7
+ // unknown candidate CI unknown: GitHub could not be read to the end; a release never proceeds blind
8
8
  // There is no override: a red candidate never passes (owner ruling 2026-10-22: a loosening is always red).
9
9
  // Every process goes through ctx.spawn (git, gh); the wait through ctx.sleep and ctx.now.
10
10
 
@@ -95,9 +95,9 @@ export async function candidateStep(ctx, { head, tag, prefix, remote, wait = CAN
95
95
  if (seen.state === 'red') {
96
96
  const failing = failingJobs(ctx, seen.run);
97
97
  const detail = failing.length ? failing.join('; ') : 'the failing jobs could not be read';
98
- return { ok: false, lines: [...lines, `RELEASE_CANDIDATE_CI_RED: the ci.yml run of ${head.slice(0, 10)} on ${ref} is red${url}; failing: ${detail}. Nothing reached the default branch; the candidate ref stays for diagnosis`], data: { ref, state: 'red', run: seen.run, failing } };
98
+ return { ok: false, lines: [...lines, `candidate CI red: the ci.yml run of ${head.slice(0, 10)} on ${ref} is red${url}; failing: ${detail}. Nothing reached the default branch; the candidate ref stays for diagnosis`], data: { ref, state: 'red', run: seen.run, failing } };
99
99
  }
100
- if (seen.state === 'unstarted') return { ok: false, lines: [...lines, `RELEASE_CANDIDATE_CI_TIMEOUT: no ci.yml run started for ${head.slice(0, 10)} on ${ref} within ${wait.graceMinutes} minutes (does .github/workflows/ci.yml trigger on release/candidate-*? run starci app sync)`], data: { ref, state: 'unstarted' } };
101
- if (seen.state === 'timeout') return { ok: false, lines: [...lines, `RELEASE_CANDIDATE_CI_TIMEOUT: the ci.yml run of ${head.slice(0, 10)} did not end within ${wait.waitMinutes} minutes${url}`], data: { ref, state: 'timeout', run: seen.run } };
102
- return { ok: false, lines: [...lines, `RELEASE_CI_UNKNOWN: GitHub could not be read for the candidate ${head.slice(0, 10)} on ${ref}; a release never proceeds blind`], data: { ref, state: 'unknown' } };
100
+ if (seen.state === 'unstarted') return { ok: false, lines: [...lines, `candidate CI timeout: no ci.yml run started for ${head.slice(0, 10)} on ${ref} within ${wait.graceMinutes} minutes (does .github/workflows/ci.yml trigger on release/candidate-*? run starci app sync)`], data: { ref, state: 'unstarted' } };
101
+ if (seen.state === 'timeout') return { ok: false, lines: [...lines, `candidate CI timeout: the ci.yml run of ${head.slice(0, 10)} did not end within ${wait.waitMinutes} minutes${url}`], data: { ref, state: 'timeout', run: seen.run } };
102
+ return { ok: false, lines: [...lines, `candidate CI unknown: GitHub could not be read for the candidate ${head.slice(0, 10)} on ${ref}; a release never proceeds blind`], data: { ref, state: 'unknown' } };
103
103
  }
package/release/push.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  // push.mjs - `starci app release push`: sends the default branch and the release tag to the remote in ONE atomic push through the repository's own
2
- // pre-push hook (never --no-verify). It requires what the hook requires: an annotated release tag on HEAD and the L4 record of the gate for that
2
+ // pre-push hook (never --no-verify). The explicit release command requires an annotated release tag on HEAD and the L4 record of the gate for that
3
3
  // head and tag. `git push --dry-run --atomic` always runs first (the hook's verdict is the proof) and any failure refuses the push.
4
4
  // With --execute the exact sha first goes to its candidate ref refs/heads/release/candidate-<version> and the push waits for the ci.yml run of that sha
5
- // (candidate-ci.mjs, OWNER RULING candidate-ci 2026-10-18): only a green candidate (or the owner's sha-bound override record) moves the branch and the tag.
5
+ // (candidate-ci.mjs, OWNER RULING candidate-ci 2026-10-18): only a green candidate moves the branch and the tag.
6
6
  // That wait runs only for an app that sets release.requireCi: true; the default false (owner ruling release-require-ci-per-project) pushes after the
7
7
  // local gate and the dry run alone and says so. The tag, the L4 record and the dry run are never skipped.
8
8
  import fs from 'node:fs';
@@ -21,7 +21,7 @@ function readRecord(file) {
21
21
  const greenRecord = (record, head, tag) => record?.head === head && record?.tag === tag
22
22
  && Array.isArray(record.gates) && record.gates.length > 0 && record.gates.every((gate) => gate?.pass === true);
23
23
 
24
- /** The release gate as the hook reads it: the annotated tag on HEAD whose L4 record names head and tag. Returns { tag } or { reason }. */
24
+ /** The explicit release command gate: the annotated tag on HEAD whose L4 record names head and tag. Returns { tag } or { reason }. */
25
25
  function gateState(ctx, head, prefix, expected) {
26
26
  const tags = annotatedOnHead(ctx, prefix);
27
27
  if (!tags.includes(expected)) return { reason: `no ANNOTATED tag ${expected} (the package.json version) on HEAD${tags.length > 0 ? `, which carries ${tags.join(', ')}` : ''}: run starci app release gate --execute first` };
@@ -38,8 +38,9 @@ parallel: {gear: 1}
38
38
  # kernel (docs/supervisor.md) also takes, optional, defaults in home.mjs DEFAULTS:
39
39
  # kernel: {agent, model, effort} its agent pin (default: the kernel pin below)
40
40
  # workers: {base, max} the adaptive [Worker] cap, 0 to 10 (0 = the Supervisor runs no fix worker)
41
- # landGate: {mode: shared} shared lets running lanes commit directly; exclusive = gate only. A land
42
- # fast-forwards LOCAL main only: the remote main moves with a release (starci release cut)
41
+ # landGate: {mode: shared, push: true} shared lets running lanes commit directly; exclusive = gate only. A land
42
+ # fast-forwards LOCAL main; main reaches the remote right after a green scoped check through
43
+ # `starci release push` (push: true, the default; false locks the remote). CI is the critic.
43
44
  supervisor:
44
45
  # mode: chat (default) - the owner's desktop chat session is the Supervisor: it owns channel 'main'
45
46
  # and reads Decision Items; nothing starts a [Supervisor] kernel. kernel - the optional [Supervisor]
@@ -49,7 +50,7 @@ supervisor:
49
50
  pollIntervalMs: null
50
51
  repos: []
51
52
  # frozenMinutes: <n> # Host seat check: a busy frame unchanged this long is frozen, not busy
52
- # landGate: {mode: shared|exclusive}
53
+ # landGate: {mode: shared|exclusive, push: true|false}
53
54
  # delegation — while the owner is away, a named delegate (e.g. the supervisor chat) may
54
55
  # answer owner asks until the given time; excluded classes stay owner-only. null = none.
55
56
  # delegation: {asks: supervisor, until: 2026-09-23T09:00:00+07:00,
@@ -185,11 +186,9 @@ sonar: {organization: null}
185
186
  # minFreeRamPct free-RAM percentage below which no new heavy op starts
186
187
  # set them only in the gitignored config.yaml copy, e.g.
187
188
  # resources: {minFreeDiskGb: 5, minFreeDiskPct: 1, minFreeRamPct: 10}
188
- # release - where the Linux parity of a release is judged (scripts/guards/release-suite-mode.mjs; docs/releasing.md "Where the suite runs"). One key:
189
- # suite: local the shipped default: `starci release cut` runs the root rows (npm run test:packages, npm run test:e2e, npm run check) and the Linux parity container before it pushes,
190
- # and the pre-push gate asks for the green root rows
191
- # suite: ci the owner's recorded choice (2026-10-09): no local Linux container; the GitHub ci workflow runs the Linux parity (and SonarCloud) AFTER the push.
192
- # The cut still runs the same root rows and the example apps; the record lists linux-parity as delegated, never green.
193
- # A release can reach GitHub and its tag before the Linux parity has run; a red CI is fixed forward with the next pre-release (starci release ci-status).
194
- # "none" is refused: the Linux parity does not vanish, it moves to CI. Set it only in the gitignored config.yaml copy: release: {suite: ci}
189
+ # release - where the full suite of a release is judged (scripts/guards/release-suite-mode.mjs; docs/releasing.md "Where the suite runs"). One key:
190
+ # suite: local the shipped default: `starci release cut` also runs the L4 rows on this host (root rows, example apps, Sonar proofs, Linux parity container) before it tags; it never gates a push
191
+ # suite: ci the owner's recorded choice (2026-10-09, widened 2026-10-26 by push-main-after-scoped-check): EVERY row is judged by the GitHub workflows (ci, examples, sonar) on each push to main;
192
+ # the cut runs none and lists each row as delegated, never green. A red CI opens the next alpha.N.M and never blocks the next push (starci release ci-status).
193
+ # "none" is refused: the suite does not vanish, it moves to CI. Set it only in the gitignored config.yaml copy: release: {suite: ci}
195
194
  release: {suite: local}
@@ -294,13 +294,13 @@ const validateSupervisorBlock=(supervisor,profile)=>{
294
294
  throw new Error('Invalid config.yaml: supervisor.mode must be chat or kernel, or null.');
295
295
  // The [Supervisor] kernel seat (scripts/machine/home.mjs supervisorSettings): kernel {agent?, model?, effort?}
296
296
  // pins its agent (default: the kernel pin), workers {base?, max?} its adaptive [Worker] cap (0 to 10; the shipped default is 0 fix workers),
297
- // landGate {mode?: shared|exclusive} the land gate (scripts/supervisor/land.mjs).
297
+ // landGate {mode?: shared|exclusive, push?: boolean} the land gate (scripts/supervisor/land.mjs); push (default true, owner ruling push-main-after-scoped-check) lets `starci release push` send main to the remote after a green scoped check, false is the owner's lock on the remote.
298
298
  const seat=supervisor.kernel,workers=supervisor.workers,gate=supervisor.landGate;
299
299
  if(seat!==undefined&&seat!==null)validateAgentSeat(seat,'supervisor.kernel',profile);
300
300
  if(!(workers===undefined||workers===null||(plain(workers)&&Object.keys(workers).every(key=>['base','max'].includes(key)&&Number.isInteger(workers[key])&&workers[key]>=0&&workers[key]<=10))))
301
301
  throw new Error('Invalid config.yaml: supervisor.workers must be {base?, max?} integers from 0 to 10, or null.');
302
- if(!(gate===undefined||gate===null||(plain(gate)&&Object.keys(gate).every(key=>key==='mode'&&['shared','exclusive'].includes(gate.mode)))))
303
- throw new Error('Invalid config.yaml: supervisor.landGate must be {mode?: shared|exclusive}, or null.');
302
+ if(!(gate===undefined||gate===null||(plain(gate)&&Object.keys(gate).every(key=>(key==='mode'&&['shared','exclusive'].includes(gate.mode))||(key==='push'&&typeof gate.push==='boolean')))))
303
+ throw new Error('Invalid config.yaml: supervisor.landGate must be {mode?: shared|exclusive, push?: boolean}, or null.');
304
304
  // stallMinutes: scripts/supervisor/stall.mjs calls a running workflow STALLED after this many minutes with no progress.
305
305
  // frozenMinutes: scripts/supervisor/supervisor-watchdog.mjs reads a busy seat frame with no turn progress for this long as frozen.
306
306
  const frozen=supervisor.frozenMinutes;
@@ -3,14 +3,14 @@ import {invalid} from './invalid-config.mjs';
3
3
 
4
4
  /** The keys of the release block. */
5
5
  export const RELEASE_KEYS=Object.freeze(['suite']);
6
- /** Where the full test suite of a release is judged: `local` (the release cut runs it, the shipped default) or `ci` (the GitHub workflow runs it after the push; the cut runs only the affected specs of the release and the rows CI cannot run). */
6
+ /** Where the full test suite of a release is judged: `local` (the release cut also runs the L4 rows before it tags, the shipped default) or `ci` (the GitHub workflows judge EVERY row on each push to main; the cut runs none). */
7
7
  export const SUITE_MODES=Object.freeze(['local','ci']);
8
- export const RELEASE_DEFAULTS=Object.freeze({suite:'local'});
8
+ const RELEASE_DEFAULTS=Object.freeze({suite:'local'});
9
9
 
10
10
  /**
11
- * config.yaml `release` - {suite?: local | ci}: who judges the full suite of a release (docs/releasing.md "Where the suite runs"). `local` is the shipped
12
- * default: `starci release cut` runs the root suite and the Linux parity container before it pushes. `ci` is the owner's recorded choice (2026-10-09) that
13
- * the full suite runs on GitHub only: the cut runs no root suite and no Linux container, and the CI workflow's verdict is read afterwards (`starci release ci-status`).
11
+ * config.yaml `release` - {suite?: local | ci}: who judges the full suite of a release (docs/releasing.md "Where the suite runs"). `local` is the shipped default: `starci release cut` also runs the
12
+ * L4 rows on this host before it tags. `ci` is the owner's recorded choice (2026-10-09, widened 2026-10-26 by push-main-after-scoped-check): the full suite runs on GitHub only, on every push
13
+ * to main; the cut runs no row and the verdict is read with `starci release ci-status`. The push of main needs neither mode: it needs a green scoped check.
14
14
  * `none` is refused: the suite does not vanish, it moves to CI, and the value says so.
15
15
  */
16
16
  export function validateRelease(release){
@@ -253,7 +253,7 @@ guidance:
253
253
  a family where selection, current and focus share the same pink.
254
254
  - id: big-touch-targets
255
255
  requirement: |
256
- Keep the published sizes: Common's 44px floor for controls, the Subnav toggle and the TopBar
256
+ Keep the published sizes: Common's 40px control height for controls, the Subnav toggle and the TopBar
257
257
  trigger, and `size="control"` throughout the navbar. Do not shrink them with app classes,
258
258
  and put answer choices in RadioGroup/CheckboxGroup/SegmentedControl rather than small
259
259
  custom chips.
@@ -185,7 +185,7 @@ shapes:
185
185
  - id: navbar-controls
186
186
  implements: [FE-APP-SHELL-2]
187
187
  compose: |
188
- Every bar control uses `size="control"`, the 2.75rem shared height. Icon uses the family
188
+ Every bar control uses `size="control"`, the 2.5rem (40px) shared height. Icon uses the family
189
189
  size-5 token (20px) throughout the region. SearchField or its trigger shows Ctrl K and opens
190
190
  on Ctrl/Cmd+K; inside a surface its variant resolves through the nesting context. A compact
191
191
  language switch in a bar is SegmentedControl size="control" (the HeroUI lg group held to
@@ -250,7 +250,7 @@ shapes:
250
250
  scrolls independently. Groups use translated headings such as Main, Operations and Settings
251
251
  from modules/i18n/messages/app-shell.yaml. The expanded item has a size-5 icon, label and
252
252
  Grammar current-page treatment. A keyboard-accessible toggle collapses it into icon-only
253
- destinations with size-5 icons in 2.75rem targets, current-page highlighting, count badges
253
+ destinations with size-5 icons in 2.5rem targets, current-page highlighting, count badges
254
254
  and hover/focus tooltips. Group order is stable, spacing replaces group headings and
255
255
  settings-type groups stay at the end. The application persists collapse state per user.
256
256
  - id: conversation-panel
@@ -333,6 +333,20 @@ imageryClarification:
333
333
  library-edit scope. Historical image notes are design references, not decisions for the current
334
334
  product.
335
335
  guidance:
336
+ - id: owner-ui-rulings-alpha-31
337
+ ownership: FE-RULE-OWNERSHIP
338
+ implements: [COLLECTION-1, LAYOUT-1, SURFACE-1, ACTION-3, CTA-6]
339
+ requirement: |
340
+ The canonical cases own the rule bodies; this entry only points to them. COLLECTION-1 case-9
341
+ (owner:selection-fill-no-check): navigation-like single selection uses ListBox
342
+ selectionIndicator="none", with the selectedKeys and accessibility contract unchanged.
343
+ LAYOUT-1 case-10 (owner:joined-multi-pane): the selected family realizes one touching pane
344
+ surface with vertical Dividers. SURFACE-1 case-4 (owner:scroll-region-background-tone):
345
+ VerticalScrollRegion tone="background" under messages and log entries, with the header and
346
+ composer on the surface. ACTION-3 case-5 (owner:hover-actions-floating-toolbar): ChatBubble's
347
+ public floating toolbar owns the IconButtons and tooltips beside the text; apps do not target
348
+ starci-core internals. CTA-6 case-4 (owner:button-variant-follows-intent): ordinary repeated
349
+ item commands are neutral and render ghost.
336
350
  - id: nested-controls-and-panel-lists
337
351
  ownership: FE-RULE-OWNERSHIP
338
352
  implements: [ANATOMY-2, PADDING-0]
@@ -30,35 +30,35 @@ pins:
30
30
  source: packages/event-stream/package.json
31
31
  why: 'the one server-sent-events client of every front end (fe-stack event-stream, owner:event-stream-package): the transport client layer (modules/api/sse/clients) creates one instance for the app with the bearer token source and the stream operations follow on it; it carries Last-Event-ID resume, the capped doubling backoff, the heartbeat timeout, the single 401 renewal and the useSWRSubscription adapter, so no app keeps a hand-written reader.'
32
32
  '@starci/grammar':
33
- version: 0.12.12
33
+ version: 0.12.13
34
34
  group: starci
35
35
  install: registry
36
36
  side: fe
37
37
  source: packages/grammar/package.json
38
38
  why: 'the grammar front ends read: the brand layer sets `--font-sans` and `--font-mono` and the grammar reads them; it carries the Input tel kind, the copyable and read-only Input, the IconButton disclosure props and count badge, ChatBubble, FillViewport, Textarea mentions and Enter-to-send, the leading PrimaryRailLayout rail, the plate-less Image, ListRow, the soft Alert, Progress steps, the floating primary IconButton, FlowGraph, the STATE-8 loading views and the bordered selectable TagGroup chips. The runtime source version wins for a @starci package.'
39
39
  '@starci/eslint-canon-be':
40
- version: 3.4.54
40
+ version: 3.4.55
41
41
  group: starci
42
42
  install: registry
43
43
  side: be
44
44
  source: packages/eslint/be/package.json
45
45
  why: 'the back-end ESLint canon: `loadHfs(import.meta.url)` of be/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the be side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; `starciBeConfig({ hfs: loadHfs(import.meta.url) })` is the typed factory and the BE-CONVENTION laws are its rules.'
46
46
  '@starci/eslint-canon-fe':
47
- version: 9.1.8
47
+ version: 9.1.9
48
48
  group: starci
49
49
  install: registry
50
50
  side: fe
51
51
  source: packages/eslint/fe/package.json
52
52
  why: 'the front-end ESLint canon: `loadHfs(import.meta.url)` of fe/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the fe side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; the front end has no tests (FE_NO_TESTS R97 in hfs) and no-vietnamese-in-source (R91) applies.'
53
53
  '@starci/eslint-grammar-starci':
54
- version: 0.12.12
54
+ version: 0.12.13
55
55
  group: starci
56
56
  install: registry
57
57
  side: fe
58
58
  source: packages/eslint/grammar-starci/package.json
59
59
  why: 'the StarCi Grammar component API lint, versioned with @starci/grammar; front ends compose this family package with the universal FE canon.'
60
60
  '@starci/eslint-grammar-offset-pop':
61
- version: 0.12.12
61
+ version: 0.12.13
62
62
  group: starci
63
63
  install: registry
64
64
  side: fe
@@ -98,14 +98,14 @@ pins:
98
98
  source: packages/test-world/package.json
99
99
  why: 'the shared e2e library of every back end (R47, R48), a devDependency of every back end: the warm stack behind toxiproxy, the network-edge fakes, the Nest boot, the typed useTestWorld handle, useSandbox for contract specs and the outage lock. Kafka is real own infra (the one digest-pinned apache/kafka KRaft image, a broker listener and proxy per slot, slot-prefixed topics, consumer groups and client ids) and Postgres is schema-per-context (a schema and a login role per context in a shared database). Each worker has its own data slot (<ns>_w<k>) with its own databases, Keycloak realm, leased Redis DB, bucket/collection/topic prefixes, fakes host, toxiproxy proxies and outage lock; a run provisions min(jest workers, workers cap, default 2) slots, and state file protocol 2 pairs exactly with @starci/jest-preset (TEST_WORLD_PAIR_MISMATCH otherwise). A modules world boots real peer apps beside its modules, world.apps.<name>.during(fn) is the outage of a peer app, w.keycloak.clientSecret(client) answers the run-generated secret of a confidential realm client, world.keycloak.events/sessions read the realm user events and live sessions, world.infra.postgresql.connection(name) takes one database connection down while the others serve, and world.resolve and scope.resolve take any Nest token. Every path a declaration names (`stack`, seeds, the realm, Dockerfiles) resolves from the app root (the directory of hfs.json); the globalSetup registers the path aliases as TypeScript resolves them. Its CLI main is exported at ./cli for starci app stack.'
100
100
  '@starci/cli':
101
- version: 1.7.45
101
+ version: 1.7.46
102
102
  group: starci
103
103
  install: registry
104
104
  side: both
105
105
  source: packages/cli/package.json
106
106
  why: 'the only StarCi command package; product repositories install it for starci app, while @starci/hfs remains its pinned transitive implementation.'
107
107
  '@starci/hfs':
108
- version: 4.7.45
108
+ version: 4.7.46
109
109
  group: starci
110
110
  install: registry
111
111
  side: both
@@ -34,7 +34,7 @@ heap:
34
34
  items:
35
35
  - id: candidate-trigger
36
36
  title: the release CI runs on the release candidate ref
37
- requires: "ci.yml runs on push of branches ['release/candidate-*'] and tags ['v*'] (and workflow_dispatch); the release flows wait for the run of the release sha there"
37
+ requires: "ci.yml runs on push of branches ['main', 'release/candidate-*'] and tags ['v*'] (and workflow_dispatch); the release flows wait for the run of the release sha there"
38
38
  covers: [app-ci, runtime]
39
39
  by: [R221 CI_TRIGGERS_RELEASE_ONLY, R251 HFS_APP_WORKFLOW_TRIGGER]
40
40
  - id: job-shape
@@ -20,7 +20,7 @@ schema: starci/hfs-rules@3
20
20
  version: 3.1.0
21
21
  gates:
22
22
  pre-commit: the managed L0 hook runs app hygiene, managed-file drift, ESLint per side and stylelint on staged source, and prettier on staged files; typecheck and targeted specs belong to the op gate under R225
23
- pre-push: the managed app hook validates an annotated release tag and the recorded L4 evidence for HEAD, permitting the release refs and backup namespace under R225; it starts no source check, build or test
23
+ pre-push: the managed app hook invokes the bundled push policy under R225; it starts no source check, build or test
24
24
  settle: the op gate `starci gate run` over the op's changed files (merge guard, starci app lint --changed, codegen, tsc, the slice's specs; only findings new against the workflow's previous checkpoint block), re-read at starci kernel settle, which refuses a red done; a green settle commits the op's checkpoint on wf-<workflowId>
25
25
  land: the workflow's finish, the only time main is touched - gate.mjs over the whole workflow branch against its merge-base with main, the merge guard, review.verify of the exact head that lands, the branch rebased onto main, then main fast-forwarded and pushed
26
26
  ci: GitHub Actions runs the same pinned starci app lint, format:check, typecheck, unit with the per-file coverage threshold, build:be and build:fe, then the Sonar scan and gate
@@ -3850,7 +3850,7 @@ rules:
3850
3850
  - {path: packages/hfs/src/hfs-supabase-secrets.spec.mjs, edition: lite}
3851
3851
  - id: R221
3852
3852
  code: "CI_TRIGGERS_RELEASE_ONLY"
3853
- law: "The only workflow triggers are a push of release tags, a push of release candidate branches `release/candidate-*`, a person dispatching it and, in the runtime's own workflows (`.github/workflows/` at the root), a push to `main`: every tracked workflow declares an `on` that holds only `push` filtered to `tags: ['v*']` and/or `branches` naming only `release/candidate-*` (and, in the runtime's own workflows, `main`; no paths or ignore filter beside them) and `workflow_dispatch`. Every `ci.yml`, and the runtime's `examples.yml`, must name `release/candidate-*`: `starci release cut` and `starci app release push` push the exact release sha there and move main and the tag only when that run is green (owner ruling candidate-ci, 2026-10-18). A push to any other branch, a pull_request, a schedule, a workflow_call or any other event refuses the workflow."
3853
+ law: "Workflow triggers are push filtered to v* tags or release/candidate-* branches, and workflow_dispatch. Runtime workflows and app ci.yml also admit main. App ci.yml includes the candidate branch filter for starci app release push. Other branches, events, path filters and ignore filters are refused."
3854
3854
  scope: runtime
3855
3855
  editions: [lite, full]
3856
3856
  docs: [docs/git-governance.md#r221]
@@ -3894,7 +3894,7 @@ rules:
3894
3894
  - {kind: runtime, id: protected-zone, at: scripts/hfs/runtime-rules/rights-policy.mjs}
3895
3895
  - id: R225
3896
3896
  code: "RT_HOOK_SHAPE"
3897
- law: "The app hook templates keep the gate model: pre-commit is L0 only (no typecheck, no test run); pre-push checks the release gate (the starci-release L4 record, refs/backup/, v[0-9] tags; the runtime's own hook is scripts/guards/release-push-gate.mjs, which judges main and v* tags against the release definition and lets every other ref through) and runs no npm test, jest, typecheck or lint."
3897
+ law: "The app hook templates keep the gate model: pre-commit is L0 only (no typecheck or test run); pre-push invokes the bundled scripts/guards/release-push-gate.mjs policy for fast-forward main, a clean outgoing secret scan and annotated v* tags. It runs no build, test, typecheck or lint."
3898
3898
  scope: runtime
3899
3899
  kinds: [check]
3900
3900
  gates: [land, runtime]
@@ -4269,7 +4269,7 @@ rules:
4269
4269
  editions: [lite, full]
4270
4270
  docs:
4271
4271
  - {path: docs/application-stacks.md#r251-deploy-workflow, editions: [lite, full]}
4272
- law: "In lite and full apps, deployment jobs declare a protected environment, explicit permissions and concurrency, materialize sealed secrets through SOPS_AGE_KEY or starci app secret materialize, and reference only the age key and transport SSH key/known_hosts secrets. They never embed a dotenv blob or write a dotenv file. Every remote workflow action uses a 40-hex commit SHA (local actions are allowed and container actions require a full sha256 digest); supply-chain reports each unpinned uses reference at its source line, including disabled jobs and steps; installed tools are checksum or digest verified, or supplied by a pinned action. App workflows start only on release tags [v*], the release candidate branches release/candidate-* (required in ci.yml: starci app release push waits for that run, owner ruling candidate-ci 2026-10-18) or workflow_dispatch, never on another branch push."
4272
+ law: "In lite and full apps, deployment jobs declare a protected environment, explicit permissions and concurrency, materialize sealed secrets through SOPS_AGE_KEY or starci app secret materialize, and reference only the age key and transport SSH key/known_hosts secrets. They never embed a dotenv blob or write a dotenv file. Every remote workflow action uses a 40-hex commit SHA (local actions are allowed and container actions require a full sha256 digest); supply-chain reports each unpinned uses reference at its source line, including disabled jobs and steps; installed tools are checksum or digest verified, or supplied by a pinned action. App workflows start only on release tags [v*], the release candidate branches release/candidate-* (required in ci.yml: starci app release push waits for that run, owner ruling candidate-ci 2026-10-18) or workflow_dispatch; ci.yml also runs on main, and other branch pushes are refused."
4273
4273
  kinds: [check]
4274
4274
  gates: [settle, land, ci]
4275
4275
  failureCodes: ["HFS_DEPLOY_WORKFLOW", "HFS_WORKFLOW_PIN", "HFS_CI_TOOL_INSTALL", "HFS_APP_WORKFLOW_TRIGGER"]
@@ -5576,7 +5576,7 @@ rules:
5576
5576
  templateNa: "The app composes its shell from product destinations and identity; full and lite example shells provide the reference."
5577
5577
  - id: R318
5578
5578
  code: "FE_NESTED_VARIANT"
5579
- law: "Apps compose Grammar with layout-only CSS; paint utilities, arbitrary paint properties and inline style paint belong to Grammar. Grammar context owns all layer-aware field variants, nested Tabs defaults and the content edge of panel lists; apps omit static field variants, do not force primary Tabs inside a painted surface, do not paint raw surfaces, and add no horizontal list-wrapper padding within the nearest painted Grammar surface. BOUNDARY-1 case-5 owns Divider full-bleed composition."
5579
+ law: "Apps compose Grammar with layout-only CSS; paint utilities, arbitrary paint properties and inline style paint belong to Grammar. Grammar context owns all layer-aware field variants, nested Tabs defaults and the content edge of panel lists; apps omit static field variants, do not force primary Tabs inside a painted surface, do not paint raw surfaces, and add no horizontal list-wrapper padding within the nearest painted Grammar surface. BOUNDARY-1 case-5 owns Divider full-bleed composition. Navigation-like single-select lists use fill without checks (COLLECTION-1 case-9); application styles never target Grammar internal classes (ACTION-3 case-5)."
5580
5580
  kinds: [lint, design]
5581
5581
  gates: [pre-commit, settle, land, ci]
5582
5582
  failureCodes: ["FE_NESTED_VARIANT", "FE_LIST_PANEL_PADDING", "FE_GRAMMAR_LAYOUT_ONLY", "FE_DIVIDER_FULL_BLEED"]
@@ -5585,18 +5585,23 @@ rules:
5585
5585
  - {kind: eslint-fe, id: no-list-panel-padding}
5586
5586
  - {kind: eslint-fe, id: no-app-surface-paint}
5587
5587
  - {kind: eslint-fe, id: divider-full-bleed}
5588
+ - {kind: eslint-fe, id: selection-fill-no-check}
5589
+ - {kind: eslint-fe, id: no-grammar-internal-selector}
5588
5590
  editions: [lite, full]
5589
5591
  docs: ["docs/code-pattern-enforcement.md#r318-app-grammar-composition"]
5590
5592
  templateNa: "Product composition is proved by the FE canon's Grammar import and surface fixtures; a template cannot choose product slots or lists."
5591
5593
  proves:
5592
5594
  - {path: packages/eslint/fe/divider-full-bleed.spec.mjs, editions: [lite, full], codes: [FE_DIVIDER_FULL_BLEED]}
5593
5595
  - {path: packages/eslint/fe/surface-rules.spec.mjs, editions: [lite, full], codes: [FE_NESTED_VARIANT, FE_LIST_PANEL_PADDING, FE_GRAMMAR_LAYOUT_ONLY]}
5596
+ - {path: packages/eslint/fe/ui-rulings.spec.mjs, editions: [lite, full], codes: [FE_NESTED_VARIANT, FE_GRAMMAR_LAYOUT_ONLY]}
5594
5597
  exemplifiedBy:
5595
5598
  - {path: packages/eslint/fe/divider-full-bleed.spec.mjs, editions: [lite, full], codes: [FE_DIVIDER_FULL_BLEED]}
5596
5599
  - {path: packages/eslint/fe/surface-rules.spec.mjs, editions: [lite, full], codes: [FE_NESTED_VARIANT, FE_LIST_PANEL_PADDING, FE_GRAMMAR_LAYOUT_ONLY]}
5600
+ - {path: packages/eslint/fe/ui-rulings.spec.mjs, editions: [lite, full], codes: [FE_NESTED_VARIANT, FE_GRAMMAR_LAYOUT_ONLY]}
5597
5601
  witness:
5598
5602
  - {path: packages/eslint/fe/divider-full-bleed.spec.mjs, editions: [lite, full], codes: [FE_DIVIDER_FULL_BLEED]}
5599
5603
  - {path: packages/eslint/fe/surface-rules.spec.mjs, editions: [lite, full], codes: [FE_NESTED_VARIANT, FE_LIST_PANEL_PADDING, FE_GRAMMAR_LAYOUT_ONLY]}
5604
+ - {path: packages/eslint/fe/ui-rulings.spec.mjs, editions: [lite, full], codes: [FE_NESTED_VARIANT, FE_GRAMMAR_LAYOUT_ONLY]}
5600
5605
  cases:
5601
5606
  - id: case-1
5602
5607
  date: "2026-10-25"
@@ -5633,3 +5638,29 @@ rules:
5633
5638
  source: owner
5634
5639
  quote: "Critique this carefully so the button choice is a system and not a feeling, understand? Update .claude."
5635
5640
  ruling: "The button variant follows the declared intent (owner ruling button-variant-follows-intent): the app passes intent and step, Grammar derives the variant from them and from the placement it reads from React context. The semantics are HeroUI's, so the lint is universal in the FE canon."
5641
+
5642
+ - id: R320
5643
+ code: "FE_CONTROL_HEIGHT"
5644
+ law: "One 40px control height is the default of every standalone control (CONTROL-HEIGHT-1): an app never picks a larger size than the default outside a landing hero call to action, and controls of one row share one size."
5645
+ kinds: [lint, design]
5646
+ gates: [pre-commit, settle, land, ci]
5647
+ failureCodes: ["FE_CONTROL_HEIGHT"]
5648
+ enforcers:
5649
+ - {kind: eslint-fe, id: control-height}
5650
+ editions: [lite, full]
5651
+ docs:
5652
+ - packages/eslint/fe/docs/control-height.md
5653
+ - docs/code-pattern-enforcement.md#r320-control-height
5654
+ proves:
5655
+ - {path: packages/eslint/fe/control-height.spec.mjs, editions: [lite, full], codes: [FE_CONTROL_HEIGHT]}
5656
+ exemplifiedBy:
5657
+ - {path: packages/eslint/fe/control-height.spec.mjs, editions: [lite, full], codes: [FE_CONTROL_HEIGHT]}
5658
+ witness:
5659
+ - {path: packages/eslint/fe/control-height.spec.mjs, editions: [lite, full], codes: [FE_CONTROL_HEIGHT]}
5660
+ templateNa: "The control height is Grammar's own value; a template states no size."
5661
+ cases:
5662
+ - id: case-1
5663
+ date: "2026-10-26"
5664
+ source: owner
5665
+ quote: "The size of the buttons and controls is uneven; the teacher thinks height = 40px (h-10) should be fixed for every button, switch, input, tabs and so on, what do you think? (translated from Vietnamese)"
5666
+ ruling: "One 40px control height is the default of every standalone control (owner ruling control-height-40): Grammar draws it, an app omits size, sm is for a nested item or dense toolbar row, lg only for a landing hero call to action, and controls of one row share one size."
@@ -188,6 +188,7 @@ rules:
188
188
  - BE_REALTIME_TOPIC_SCOPE
189
189
  - BE_DEFAULT_DENY
190
190
  - BE_SOURCE_FORM
191
+ - BE_PUBLIC_CONTRACT_FORM
191
192
  - HFS_COVERAGE_SCOPE_DRIFT
192
193
  manual:
193
194
  - Review that the pure topic policy is exported by the owning domain and used by both the publisher and door.
@@ -80,7 +80,7 @@ op:
80
80
  release:
81
81
  verbs: [cut, publish]
82
82
  scripts: [release:cut, release:publish]
83
- use: starci release cut is owner-approved and moves main plus its tag; starci release publish publishes packages from that release; starci release notes prints or writes a tag CHANGELOG section and is not owner-gated
83
+ use: starci release cut is owner-approved and tags a commit already pushed; starci release publish publishes a finished package at once; starci release push sends main and is not owner-gated; starci release notes prints or writes a tag CHANGELOG section and is not owner-gated
84
84
  # Programs that cannot change state, with any arguments.
85
85
  read: [ls, dir, cat, type, head, tail, wc, uniq, cut, tr, diff, cmp, comm, rg, grep, egrep, fgrep, echo, printf, pwd, cd, pushd, popd, which, where, whoami, hostname,
86
86
  stat, file, realpath, readlink, dirname, basename, date, true, false, test, '[', expr, sleep, seq, basename, md5sum, sha1sum, sha256sum, shasum, cksum, od, xxd, hexdump,
@@ -123,7 +123,7 @@ git:
123
123
  add: {code: RIGHTS_GIT_COMMIT, use: starci git commit}
124
124
  rm: {code: RIGHTS_GIT_COMMIT, use: starci git commit}
125
125
  mv: {code: RIGHTS_GIT_COMMIT, use: starci git commit}
126
- push: {code: RIGHTS_GIT_PUSH, use: 'starci git backup (refs/backup/* only); main and its tag move only through starci release cut'}
126
+ push: {code: RIGHTS_GIT_PUSH, use: 'starci git backup (refs/backup/* only); main moves only through starci release push and a release tag through starci release cut'}
127
127
  tag: {code: RIGHTS_GIT_TAG, use: starci release cut (the only tag maker; housekeeping marks are refs/backup/* or refs/salvage/*, never tags)}
128
128
  merge: {code: RIGHTS_GIT_SYNC, use: starci git sync (merge main into the lane) or starci git land (coordinator)}
129
129
  rebase: {code: RIGHTS_GIT_SYNC, use: starci git sync}
@@ -154,7 +154,7 @@ npm:
154
154
  programs: [npm, pnpm, yarn, bun]
155
155
  read: [view, v, info, show, ls, list, la, ll, outdated, explain, why, root, prefix, bin, help, search, whoami, ping, '-v', '--version', 'config get', 'config list', pack-dry]
156
156
  deny:
157
- publish: {code: RIGHTS_NPM_PUBLISH, use: starci release cut authorizes the release; starci release publish publishes its packages once}
157
+ publish: {code: RIGHTS_NPM_PUBLISH, use: starci release publish is the package-publication path and runs as soon as a package branch is done}
158
158
  unpublish: {code: RIGHTS_NPM_PUBLISH, use: starci release publish is the only package-publication path; unpublishing is owner-only}
159
159
  deprecate: {code: RIGHTS_NPM_PUBLISH, use: starci release publish is the only package-publication path; deprecation is owner-only}
160
160
  dist-tag: {code: RIGHTS_NPM_PUBLISH, use: starci release publish is the only package-publication path; dist-tag mutation is owner-only}
@@ -165,8 +165,8 @@ npm:
165
165
  add: {code: RIGHTS_RAW_TOOL, use: starci npm install adds exact-pinned dependencies and is limited to lead or owner}
166
166
  update: {code: RIGHTS_RAW_TOOL, use: starci npm install is the owner-controlled dependency-change path using canon pins}
167
167
  uninstall: {code: RIGHTS_RAW_TOOL, use: starci npm install is the owner-controlled dependency-change path using canon pins}
168
- test: {code: RIGHTS_RAW_TOOL, use: starci runtime verify in the runtime (the check, package clean-test and scaffold e2e; the full proof is the release cut) or starci gate unit --root <app> in an app}
169
- t: {code: RIGHTS_RAW_TOOL, use: starci runtime verify in the runtime (the check, package clean-test and scaffold e2e; the full proof is the release cut) or starci gate unit --root <app> in an app}
168
+ test: {code: RIGHTS_RAW_TOOL, use: starci runtime verify in the runtime (the check, package clean-test and scaffold e2e; the full proof is CI) or starci gate unit --root <app> in an app}
169
+ t: {code: RIGHTS_RAW_TOOL, use: starci runtime verify in the runtime (the check, package clean-test and scaffold e2e; the full proof is CI) or starci gate unit --root <app> in an app}
170
170
  run: {code: RIGHTS_RAW_TOOL, use: 'starci runtime verify proves a runtime change; starci work layout-render serves the app for a layout capture; use starci release sync-runtime for package runtime copies and grammar preparation, or starci check run, starci lint run, starci typecheck run, starci app stack, or starci gate run for other scripts'}
171
171
  run-script: {code: RIGHTS_RAW_TOOL, use: 'starci runtime verify proves a runtime change; use starci check run, starci lint run, starci typecheck run, starci app stack, or starci gate run for other scripts'}
172
172
  start: {code: RIGHTS_RAW_TOOL, use: 'starci app stack'}
@@ -1770,13 +1770,13 @@ CI_STANDARD:
1770
1770
  kind: check-finding
1771
1771
 
1772
1772
  CI_TRIGGERS_RELEASE_ONLY:
1773
- title: "Workflow trigger other than a release tag, a person or the runtime's main"
1774
- title_vi: "Workflow có trigger không phải tag phát hành, người chạy tay hay main của runtime"
1775
- meaning_vi: "Một workflow CI (gốc, ví dụ hoặc template của packages/hfs) khai báo trigger ngoài các loại được phép: push của tag v*, workflow_dispatch và, chỉ ở workflow gốc của runtime, push của nhánh main (không kèm paths hay bộ lọc ignore). Push nhánh khác, push main trong ví dụ hoặc template, pull_request, schedule hay workflow_call làm CI chạy sai thời điểm, và mọi app scaffold từ template sẽ thừa hưởng lỗi đó."
1773
+ title: "Workflow trigger outside R221"
1774
+ title_vi: "Workflow có trigger ngoài R221"
1775
+ meaning_vi: "Workflow khai báo trigger ngoài R221: main được phép trong workflow runtime và ci.yml của app; các nhánh, sự kiện và bộ lọc khác phải theo R221."
1776
1776
  causes_vi:
1777
- - "Giữ trigger push hoặc pull_request của mô hình CI cũ"
1778
- - "Thêm bộ lọc branches khác [main], hoặc paths, vào push; hoặc thêm push main vào ví dụ hoặc template của app"
1779
- nextStep_vi: "Người giám sát đặt on của workflow gốc thành push với tags: ['v*'] và/hoặc branches: [main] cùng workflow_dispatch; ví dụ và template trong packages/hfs/templates/app/ci-workflows chỉ giữ tags: ['v*'] và workflow_dispatch (render lại ví dụ bằng starci app sync --write); không thêm trigger khác (docs/git-governance.md)."
1777
+ - "Khai báo nhánh, sự kiện hoặc bộ lọc không thuộc R221"
1778
+ - "Khai báo nhánh, sự kiện hoặc bộ lọc không thuộc R221"
1779
+ nextStep_vi: "Sửa on theo R221 rồi chạy starci app sync --write để tạo lại workflow ứng dụng."
1780
1780
  owner: supervisor
1781
1781
  kind: check-finding
1782
1782
 
@@ -2284,6 +2284,17 @@ FE_CONSOLE_CALL:
2284
2284
  owner: op-retry
2285
2285
  kind: check-finding
2286
2286
 
2287
+ FE_CONTROL_HEIGHT:
2288
+ title: "A control picks a larger size or mixes sizes in one row"
2289
+ title_vi: "Điều khiển chọn cỡ lớn hơn hoặc trộn nhiều cỡ trong một hàng"
2290
+ meaning_vi: "Điều khiển Grammar tại <file> truyền size lớn hơn chiều cao chuẩn 40px, hoặc đứng cạnh điều khiển khác cỡ trong cùng một hàng."
2291
+ causes_vi:
2292
+ - "Truyền size lg hoặc xl cho nút, công tắc, ô nhập ngoài khu hero của trang landing."
2293
+ - "Trộn size sm với cỡ mặc định trong cùng một thanh công cụ hoặc hàng."
2294
+ nextStep_vi: "Bỏ size để Grammar vẽ đúng một chiều cao điều khiển 40px (quy định control-height-40)."
2295
+ owner: op-retry
2296
+ kind: check-finding
2297
+
2287
2298
  FE_COOKIE_ATTRIBUTES:
2288
2299
  title: "A written cookie leaves its attributes to the defaults"
2289
2300
  title_vi: "Cookie được ghi mà không nêu rõ thuộc tính"
@@ -3219,12 +3230,12 @@ HFS_APP_LEXICON:
3219
3230
  kind: check-finding
3220
3231
 
3221
3232
  HFS_APP_WORKFLOW_TRIGGER:
3222
- title: "App workflows start on release tags or manual dispatch"
3233
+ title: "App workflow trigger outside R251"
3223
3234
  title_vi: "Workflow ứng dụng có sự kiện khởi chạy không hợp lệ"
3224
- meaning_vi: "Workflow tại <path> chạy theo push nhánh hoặc sự kiện khác; ứng dụng chỉ chạy CI theo release tag v* hoặc workflow_dispatch."
3235
+ meaning_vi: "Workflow ứng dụng khai báo trigger ngoài R251; ci.yml được chạy trên main và nhánh release candidate, các workflow khác giữ phạm vi trigger của R251."
3225
3236
  causes_vi:
3226
- - "Vi phạm R251: áp dụng ngoại lệ push main của runtime cho workflow ứng dụng."
3227
- nextStep_vi: "Đặt on.push.tags thành [v*] và dùng workflow_dispatch khi cần chạy thủ công; bỏ nhánh và sự kiện khác."
3237
+ - "Khai báo trigger không thuộc R251"
3238
+ nextStep_vi: "Sửa trigger theo R251 và chạy starci app sync --write."
3228
3239
  owner: op-retry
3229
3240
  kind: check-finding
3230
3241
 
@@ -4532,7 +4543,7 @@ RIGHTS_GIT_PUSH:
4532
4543
  causes_vi:
4533
4544
  - "Agent định đẩy main, một nhánh thường, mọi thẻ hoặc một refspec quá rộng"
4534
4545
  - "Coordinator hoặc Supervisor đẩy bản sao lưu nhưng đích không nằm trong refs/backup/* hoặc không có refspec tường minh"
4535
- nextStep_vi: "Lệnh bị từ chối; để thay đổi đi qua starci release cut, hoặc nếu là coordinator hay Supervisor thì dùng starci git backup với đích refs/backup/* tường minh."
4546
+ nextStep_vi: "Lệnh bị từ chối; để thay đổi đi qua starci release push, hoặc nếu là coordinator hay Supervisor thì dùng starci git backup với đích refs/backup/* tường minh."
4536
4547
  owner: op-retry
4537
4548
  kind: verb-refusal
4538
4549
 
@@ -4605,12 +4616,13 @@ RIGHTS_PROTECTED_ZONE:
4605
4616
  RIGHTS_PUSH_NOT_RELEASE:
4606
4617
  title: "Pre-push gate refused the push"
4607
4618
  title_vi: "Cổng pre-push từ chối lần đẩy"
4608
- meaning_vi: "Hook pre-push đã chặn một lần đẩy main hoặc thẻ v* của runtime vì commit được đẩy chưa phải commit phát hành: phiên bản chưa vượt phiên bản ở main trên remote, thiếu thẻ annotated v<version>, thiếu tiêu đề CHANGELOG có ngày, hoặc thiếu bản ghi L4 của đúng commit đó với suite gốc, suite các package và check đều xanh."
4619
+ meaning_vi: "Hook pre-push đã chặn một lần đẩy main hoặc thẻ v* của runtime hoặc app: main không phải fast-forward hoặc vùng đẩy có ứng viên bí mật; thẻ v* không phải thẻ annotated."
4609
4620
  causes_vi:
4610
4621
  - "Thao tác xóa main hoặc xóa thẻ phát hành"
4611
4622
  - "Đẩy main không phải fast-forward của main trên remote (force push, rewind hoặc nhánh rẽ)"
4612
- - "Commit được đẩy không phải commit phát hành: version trùng version ở main remote, thiếu thẻ annotated v<version>, CHANGELOG chưa có tiêu đề có ngày, hoặc bản ghi starci-release của đúng commit đó thiếu hay có dòng đỏ"
4613
- nextStep_vi: "Không đẩy main giữa hai bản phát hành; chạy starci release cut --tag v<version> (thêm --plan để xem trước) đã được owner duyệt, lệnh này ghi bản ghi L4 rồi đẩy main và thẻ v* cùng nhau."
4623
+ - "Vùng đẩy chứa ứng viên bí mật (tên tệp, dòng và mẫu, không bao giờ giá trị)"
4624
+ - "Đẩy thẻ v* không phải thẻ annotated"
4625
+ nextStep_vi: "Gỡ hoặc mã hóa ứng viên bí mật, fetch main rồi đẩy lại bằng starci release push; thẻ thì tạo bằng starci release cut --tag v<version> (thêm --plan để xem trước)."
4614
4626
  owner: supervisor
4615
4627
  kind: verb-refusal
4616
4628
 
@@ -4628,11 +4640,11 @@ RIGHTS_RAW_TOOL:
4628
4640
  RIGHTS_RELEASE_CUT:
4629
4641
  title: "Release cut refused"
4630
4642
  title_vi: "Từ chối cắt bản phát hành"
4631
- meaning_vi: "Release cut là quyết định phát hành của owner và chạy toàn bộ cổng L4 một lần; worker, lead, Supervisor và coordinator không được tự khởi động nó."
4643
+ meaning_vi: "Release cut gắn thẻ bản phát hành cho một commit đã đẩy và publish gói là quyết định phát hành của owner; worker, lead, Supervisor và coordinator không được tự khởi động chúng."
4632
4644
  causes_vi:
4633
4645
  - "Agent gọi starci release cut hoặc đường script tương đương"
4634
4646
  - "Agent định publish bản phát hành mà chưa có phê duyệt của owner"
4635
- nextStep_vi: "Lệnh bị từ chối; báo lane đã sẵn sàng để owner phê duyệt và khởi động starci release cut."
4647
+ nextStep_vi: "Lệnh bị từ chối; đẩy main bằng starci release push, và báo để owner phê duyệt starci release cut hoặc publish."
4636
4648
  owner: op-retry
4637
4649
  kind: verb-refusal
4638
4650
 
@@ -4653,7 +4665,7 @@ RIGHTS_SUITE_RUN:
4653
4665
  meaning_vi: "Toàn bộ suite chỉ chạy một lần trong mỗi release hoặc theo yêu cầu trong unit.verify hay e2e.verify; một worker chỉ chạy tệp spec vừa thay đổi hoặc để op gate chọn phạm vi."
4654
4666
  causes_vi:
4655
4667
  - "Agent gọi npm test, một script test toàn bộ hoặc node --test trên cả cây tests"
4656
- - "Agent chạy suite ngoài release cut và ngoài op verify được yêu cầu"
4668
+ - "Agent chạy suite ngoài CI và ngoài op verify được yêu cầu"
4657
4669
  nextStep_vi: "Lệnh bị từ chối; chạy riêng tệp spec đã thay đổi, để op gate chọn spec liên quan, hoặc dùng unit.verify/e2e.verify khi owner hay mục tiêu yêu cầu."
4658
4670
  owner: op-retry
4659
4671
  kind: verb-refusal
@@ -5018,11 +5030,11 @@ RT_HELD_UNSURFACED:
5018
5030
  RT_HOOK_SHAPE:
5019
5031
  title: "App hook template breaks the gate model"
5020
5032
  title_vi: "Template hook của app phá vỡ mô hình cổng"
5021
- meaning_vi: "Template hook husky của app (packages/hfs/templates/app/hooks/husky) không đúng mô hình: pre-commit chỉ được chạy mức L0 (work-hygiene, eslint, stylelint, prettier trên tệp staged), không typecheck và không chạy test; pre-push chỉ kiểm cổng phát hành (bản ghi L4 trong starci-release, không gian refs/backup/, mẫu thẻ v[0-9]*), không chạy npm test, jest, typecheck hay lint."
5033
+ meaning_vi: "Template hook của app không đúng R225: pre-commit chạy L0; pre-push gọi chính sách push dùng chung trong gói, không chạy build, test, typecheck hay lint."
5022
5034
  causes_vi:
5023
5035
  - "Thêm lại npm run typecheck hoặc test:affected vào pre-commit"
5024
- - "Đưa typecheck, lint hay test vào pre-push, hoặc xóa các dấu mốc cổng phát hành khỏi pre-push"
5025
- nextStep_vi: "Người giám sát trả template về đúng mô hình (pre-commit L0, pre-push chỉ kiểm cổng phát hành) rồi render lại hook các ví dụ bằng starci app sync --write, qua một lane đã land."
5036
+ - "Đưa build, typecheck, lint hay test vào pre-push hoặc bỏ lời gọi chính sách push dùng chung"
5037
+ nextStep_vi: "Khôi phục template theo R225 rồi chạy starci app sync --write để tạo lại hook."
5026
5038
  owner: supervisor
5027
5039
  kind: check-finding
5028
5040