@starci/hfs 4.12.0 → 4.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/README.md +8 -9
- package/cache/app-families.d.mts +30 -0
- package/cache/app-families.mjs +94 -0
- package/cache/content-index.d.mts +13 -0
- package/cache/content-index.mjs +37 -0
- package/cache/db-family.d.mts +13 -0
- package/cache/db-family.mjs +37 -0
- package/cache/fake-session.d.mts +42 -0
- package/cache/fake-session.mjs +57 -0
- package/cache/fingerprint.d.mts +28 -0
- package/cache/fingerprint.mjs +120 -0
- package/cache/format-family.d.mts +13 -0
- package/cache/format-family.mjs +49 -0
- package/cache/git.d.mts +11 -0
- package/cache/git.mjs +13 -0
- package/cache/hash.d.mts +15 -0
- package/cache/hash.mjs +58 -0
- package/cache/hfs-family.d.mts +35 -0
- package/cache/hfs-family.mjs +164 -0
- package/cache/import-graph.d.mts +75 -0
- package/cache/import-graph.mjs +204 -0
- package/cache/jsonl.d.mts +31 -0
- package/cache/jsonl.mjs +115 -0
- package/cache/lint-family.d.mts +162 -0
- package/cache/lint-family.mjs +433 -0
- package/cache/order.d.mts +1 -0
- package/cache/order.mjs +2 -0
- package/cache/progress.d.mts +19 -0
- package/cache/progress.mjs +68 -0
- package/cache/quality-baselines.d.mts +109 -0
- package/cache/quality-baselines.mjs +235 -0
- package/cache/records.d.mts +33 -0
- package/cache/records.mjs +54 -0
- package/cache/report.d.mts +19 -0
- package/cache/report.mjs +32 -0
- package/cache/run-log.d.mts +17 -0
- package/cache/run-log.mjs +37 -0
- package/cache/run.d.mts +33 -0
- package/cache/run.mjs +65 -0
- package/cache/session.d.mts +29 -0
- package/cache/session.mjs +197 -0
- package/cache/side-key.d.mts +10 -0
- package/cache/side-key.mjs +47 -0
- package/cache/test-family.d.mts +130 -0
- package/cache/test-family.mjs +258 -0
- package/cache/trust.d.mts +53 -0
- package/cache/trust.mjs +157 -0
- package/cache/types-family.d.mts +74 -0
- package/cache/types-family.mjs +154 -0
- package/cache/types.d.mts +99 -0
- package/cache/types.mjs +8 -0
- package/cache/wiring-family.d.mts +14 -0
- package/cache/wiring-family.mjs +43 -0
- package/coverage/cli.d.mts +4 -2
- package/coverage/cli.mjs +44 -5
- package/db/migrate.d.mts +16 -0
- package/db/migrate.mjs +21 -4
- package/db/postgres.d.mts +23 -0
- package/db/postgres.mjs +32 -1
- package/lint/run.d.mts +47 -1
- package/lint/run.mjs +157 -5
- package/package.json +2 -1
- package/release/gate.d.mts +2 -0
- package/release/gate.mjs +4 -2
- package/release/records.d.mts +15 -0
- package/release/records.mjs +25 -0
- package/release/urgent.mjs +23 -4
- package/runtime/knowledge/grammars/core/playbook.yaml +12 -18
- package/runtime/knowledge/hfs/canon-pins.yaml +8 -8
- package/runtime/knowledge/hfs/ci-standard.yaml +29 -6
- package/runtime/knowledge/hfs/rules.yaml +97 -7
- package/runtime/knowledge/hfs/slots.yaml +19 -7
- package/runtime/modules/kernel/failure-codes.yaml +46 -4
- package/runtime/modules/schemas/application-stacks.schema.yaml +26 -0
- package/runtime/scripts/hfs/architecture/hfs.d.mts +6 -6
- package/runtime/scripts/hfs/check.mjs +1 -1
- package/runtime/scripts/hfs/gate-threshold.mjs +2 -1
- package/runtime/scripts/hfs/rules/boot-proof.d.mts +5 -0
- package/runtime/scripts/hfs/rules/boot-proof.mjs +15 -1
- package/runtime/scripts/hfs/rules/ci-custody.mjs +3 -1
- package/runtime/scripts/hfs/rules/ci-standard.mjs +32 -3
- package/runtime/scripts/hfs/rules/database-migrations.d.mts +14 -0
- package/runtime/scripts/hfs/rules/database-migrations.mjs +55 -0
- package/runtime/scripts/hfs/rules/database.mjs +2 -1
- package/runtime/scripts/hfs/rules/deploy-workflow.mjs +53 -6
- package/runtime/scripts/hfs/rules/release-provenance.mjs +32 -4
- package/runtime/scripts/hfs/rules/stacks.mjs +5 -1
- package/runtime/scripts/hfs/rules/supply-chain.mjs +4 -1
- package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +98 -7
- package/runtime/scripts/hfs/rules/unused-symbol.d.mts +9 -2
- package/runtime/scripts/hfs/rules/unused-symbol.mjs +15 -2
- package/runtime/scripts/hfs/runtime-rules/git-triggers.mjs +21 -5
- package/runtime/scripts/hfs/stack-deploy-config.d.mts +6 -0
- package/runtime/scripts/hfs/stack-deploy-config.mjs +32 -1
- package/runtime/scripts/hfs/stacks-gate.mjs +2 -0
- package/runtime/scripts/hfs/tree-findings.mjs +3 -1
- package/scaffold/add-inbox-lite.mjs +2 -4
- package/scaffold/add-queue-lite.mjs +2 -3
- package/scaffold/add-table.mjs +2 -4
- package/scaffold/migration-stamp.d.mts +11 -0
- package/scaffold/migration-stamp.mjs +32 -0
- package/src/helpers/hfs-arch-fixture.d.mts +1 -1
- package/src/main.d.mts +4 -1
- package/src/main.mjs +189 -74
- package/src/stack-records-double.d.mts +31 -0
- package/stack/cli.d.mts +3 -1
- package/stack/cli.mjs +17 -11
- package/stack/config.d.mts +6 -0
- package/stack/converge.d.mts +2 -0
- package/stack/converge.mjs +1 -1
- package/stack/db-phases.d.mts +185 -0
- package/stack/db-phases.mjs +346 -0
- package/stack/deploy.d.mts +18 -4
- package/stack/deploy.mjs +235 -70
- package/stack/journal.d.mts +127 -0
- package/stack/journal.mjs +182 -0
- package/stack/manager-backup.d.mts +54 -0
- package/stack/manager-backup.mjs +136 -0
- package/stack/release-store.d.mts +44 -0
- package/stack/release-store.mjs +89 -6
- package/stack/remote.d.mts +2 -0
- package/stack/remote.mjs +31 -11
- package/stack/server-key.d.mts +2 -0
- package/stack/server-key.mjs +1 -1
- package/stack/status.d.mts +49 -0
- package/stack/status.mjs +114 -0
- package/stack/urgent-floor.d.mts +10 -61
- package/stack/urgent-floor.mjs +28 -123
- package/stack/verify.mjs +26 -0
- package/sync/ci-commands.mjs +10 -5
- package/sync/format-worker.d.mts +1 -0
- package/sync/format-worker.mjs +17 -0
- package/sync/format.d.mts +21 -3
- package/sync/format.mjs +99 -7
- package/sync/index.d.mts +3 -6
- package/sync/index.mjs +12 -12
- package/sync/stack-images.d.mts +25 -10
- package/sync/stack-images.mjs +215 -54
- package/templates/app/ci-workflows/github/workflows/ci.yml +114 -7
- package/templates/app/ci-workflows/github/workflows/images.yml +21 -65
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +130 -37
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +114 -7
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +21 -65
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +130 -37
- package/templates/app/gitignore +1 -0
- package/templates/app/gitignore-lite +1 -0
- package/templates/app/package-scripts/db.json +1 -1
- package/templates/app/skeleton/.gitattributes +2 -0
- package/templates/app/skeleton-lite/.gitattributes +2 -0
- package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +2 -1
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +0 -63
package/release/records.d.mts
CHANGED
|
@@ -15,4 +15,19 @@ export declare function recordRow(dir: string, head: string, row: {
|
|
|
15
15
|
}): void;
|
|
16
16
|
/** The recorded green row of `head` whose name and command match and whose log still has the recorded sha256, or null. */
|
|
17
17
|
export declare function greenRow(dir: string, head: string, name: string, command: string): Json;
|
|
18
|
+
/** The trailer keys an urgent release writes into its tag annotation (WP3 of contexts/project/cicd.md): the tag's CI run reads them to back-fill the record. */
|
|
19
|
+
export declare const URGENT_TRAILER = "Starci-Urgent";
|
|
20
|
+
export declare const URGENT_REASON_TRAILER = "Starci-Urgent-Reason";
|
|
21
|
+
/** What the trailers of an urgent tag say: who released it and why. */
|
|
22
|
+
export interface UrgentTrailers {
|
|
23
|
+
role: string;
|
|
24
|
+
reason: string;
|
|
25
|
+
}
|
|
26
|
+
/** The trailer block of an urgent tag annotation: one `Starci-Urgent: <role>` and one `Starci-Urgent-Reason: <text>` line (the reason is one line, so it cannot forge a trailer). */
|
|
27
|
+
export declare function urgentTrailerBlock(role: string, reason: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* The urgent trailers of a tag annotation, read from its last paragraph (the place `git interpret-trailers` reads), or null when it has none.
|
|
30
|
+
* Both keys must be there; the last occurrence of a key wins, like git's own parse.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseUrgentTrailers(message: string): UrgentTrailers | null;
|
|
18
33
|
export {};
|
package/release/records.mjs
CHANGED
|
@@ -53,3 +53,28 @@ export function greenRow(dir, head, name, command) {
|
|
|
53
53
|
const row = rows.find((known) => known?.name === name && known.command === command && known.pass === true);
|
|
54
54
|
return row && row.log && fileSha256(row.log) === row.logSha256 ? row : null;
|
|
55
55
|
}
|
|
56
|
+
/** The trailer keys an urgent release writes into its tag annotation (WP3 of contexts/project/cicd.md): the tag's CI run reads them to back-fill the record. */
|
|
57
|
+
export const URGENT_TRAILER = 'Starci-Urgent';
|
|
58
|
+
export const URGENT_REASON_TRAILER = 'Starci-Urgent-Reason';
|
|
59
|
+
/** The role as a trailer value: the caller's role name (letters, digits, dot, dash, underscore); anything else becomes a dash. */
|
|
60
|
+
const trailerRole = (role) => role.replaceAll(/[^\w.-]/gu, '-');
|
|
61
|
+
/** The trailer block of an urgent tag annotation: one `Starci-Urgent: <role>` and one `Starci-Urgent-Reason: <text>` line (the reason is one line, so it cannot forge a trailer). */
|
|
62
|
+
export function urgentTrailerBlock(role, reason) {
|
|
63
|
+
return `${URGENT_TRAILER}: ${trailerRole(role)}\n${URGENT_REASON_TRAILER}: ${reason.replaceAll(/\s+/gu, ' ').trim()}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The urgent trailers of a tag annotation, read from its last paragraph (the place `git interpret-trailers` reads), or null when it has none.
|
|
67
|
+
* Both keys must be there; the last occurrence of a key wins, like git's own parse.
|
|
68
|
+
*/
|
|
69
|
+
export function parseUrgentTrailers(message) {
|
|
70
|
+
const last = message.replaceAll('\r\n', '\n').trim().split(/\n\s*\n/u).at(-1) ?? '';
|
|
71
|
+
const found = new Map();
|
|
72
|
+
for (const line of last.split('\n')) {
|
|
73
|
+
const match = /^([A-Za-z][A-Za-z0-9-]*):[ \t]*(.*)$/u.exec(line.trimEnd());
|
|
74
|
+
if (match)
|
|
75
|
+
found.set(match[1], match[2].trim());
|
|
76
|
+
}
|
|
77
|
+
const role = found.get(URGENT_TRAILER);
|
|
78
|
+
const reason = found.get(URGENT_REASON_TRAILER);
|
|
79
|
+
return role && reason ? { role, reason } : null;
|
|
80
|
+
}
|
package/release/urgent.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { firstLine } from '../stack/context.mjs';
|
|
|
11
11
|
import { failure, refusal } from '../stack/errors.mjs';
|
|
12
12
|
import { assertUnmoved, git, inspect, releaseDir, runGate, summaryOf } from './gate.mjs';
|
|
13
13
|
import { assertNames, branchRefs, probe, tagRefs } from './push.mjs';
|
|
14
|
-
import { URGENT_RULING, URGENT_SCHEMA, greenRow, recordRow, writeUrgent } from './records.mjs';
|
|
14
|
+
import { URGENT_RULING, URGENT_SCHEMA, greenRow, parseUrgentTrailers, recordRow, urgentTrailerBlock, writeUrgent } from './records.mjs';
|
|
15
15
|
/**
|
|
16
16
|
* The fast local checks of an urgent release, by gate name, in the order they run: what can break production, the types and the build
|
|
17
17
|
* (owner ruling urgent-typecheck-build, 2026-10-30). App check, lint and the tests wait for the back-fill (`release gate --backfill`).
|
|
@@ -61,6 +61,25 @@ function planOf(checks) {
|
|
|
61
61
|
return ['urgent checks (each must pass; a green row recorded for HEAD with the same command and log is reused):',
|
|
62
62
|
...checks.map((check) => ` ${check.name.padEnd(10)} ${check.gate ? check.gate.command.join(' ') : '(nothing to run)'}${check.note ? ` [${check.note}]` : ''}`)];
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Writes the urgent trailers (`Starci-Urgent`, `Starci-Urgent-Reason`) into the annotation of the release tag, so the tag's own CI run knows the release was
|
|
66
|
+
* urgent and back-fills its record instead of deploying it again. A new tag carries them from the start; a tag the gate already put on HEAD and nobody has
|
|
67
|
+
* published yet is re-annotated (its message kept, the trailers appended); a tag already on the remote is never moved, so it stays without them and the line says so.
|
|
68
|
+
*/
|
|
69
|
+
function annotateTag(ctx, { tag, head, role, reason, onHead, unpublished }) {
|
|
70
|
+
const trailers = urgentTrailerBlock(role, reason);
|
|
71
|
+
if (!onHead) {
|
|
72
|
+
git(ctx, ['tag', '-a', tag, '-m', `URGENT release ${tag} on ${head.slice(0, 12)} by ${role}: ${reason} (fast local checks only; back-fill: starci app release gate --backfill ${tag})`, '-m', trailers, head]);
|
|
73
|
+
return { written: true, line: `tag trailers ${tag}: Starci-Urgent, Starci-Urgent-Reason` };
|
|
74
|
+
}
|
|
75
|
+
const message = git(ctx, ['for-each-ref', '--format=%(contents)', `refs/tags/${tag}`]).stdout;
|
|
76
|
+
if (parseUrgentTrailers(message))
|
|
77
|
+
return { written: true, line: `tag trailers ${tag}: already there` };
|
|
78
|
+
if (!unpublished)
|
|
79
|
+
return { written: false, line: `tag trailers NONE: ${tag} is already published without them and a published tag is never moved; the deploy record still names the urgent role and reason` };
|
|
80
|
+
git(ctx, ['tag', '-a', '-f', tag, '-m', message.trim() === '' ? `release ${tag}` : message.trim(), '-m', trailers, head]);
|
|
81
|
+
return { written: true, line: `tag trailers ${tag}: re-annotated (not yet published): Starci-Urgent, Starci-Urgent-Reason` };
|
|
82
|
+
}
|
|
64
83
|
/**
|
|
65
84
|
* `starci app release push --urgent`. options: { execute, remote, reason, role, platform }. Dry run prints the checks, the tag and the push it would
|
|
66
85
|
* make; --execute runs them, tags, pushes atomically and writes the urgent record. A red check is RELEASE_URGENT_CHECK_RED: nothing tagged, nothing pushed.
|
|
@@ -94,8 +113,8 @@ export async function releaseUrgentPush(ctx, config, options = {}) {
|
|
|
94
113
|
if (failed)
|
|
95
114
|
throw failure('RELEASE_URGENT_CHECK_RED', `${failed.name} is red (${failed.detail}; log ${failed.log}): nothing tagged, nothing pushed`);
|
|
96
115
|
assertUnmoved(ctx, head);
|
|
97
|
-
|
|
98
|
-
|
|
116
|
+
const trailers = annotateTag(ctx, { tag: chosen.tag, head, role, reason, onHead: chosen.state === 'on-head', unpublished: refs.includes(`refs/tags/${chosen.tag}`) });
|
|
117
|
+
out.push(trailers.line);
|
|
99
118
|
if (refs.length > 0) {
|
|
100
119
|
const hook = probe(ctx, remote, refs, red);
|
|
101
120
|
out.push(...hook.lines);
|
|
@@ -107,7 +126,7 @@ export async function releaseUrgentPush(ctx, config, options = {}) {
|
|
|
107
126
|
}
|
|
108
127
|
const record = {
|
|
109
128
|
schema: URGENT_SCHEMA, head, tag: chosen.tag, createdAt: new Date(ctx.now()).toISOString(), producedBy: 'starci app release push --urgent',
|
|
110
|
-
role, reason, ruling: URGENT_RULING, remote, branch, refs, pushed: true, checks: rows, backfill: null,
|
|
129
|
+
role, reason, trailers: trailers.written, ruling: URGENT_RULING, remote, branch, refs, pushed: true, checks: rows, backfill: null,
|
|
111
130
|
};
|
|
112
131
|
writeUrgent(common, record);
|
|
113
132
|
return { ok: true, lines: [...out, `pushed to ${remote}: ${refs.join(' ') || '(already there)'}`, `URGENT record written: ${path.join(common, `${head}.urgent.json`)}`, `back-fill later: starci app release gate --backfill ${chosen.tag} --execute (in a clean checkout of ${chosen.tag})`], data: { ...data, pushed: true, record: path.join(common, `${head}.urgent.json`) } };
|
|
@@ -357,8 +357,8 @@ guidance:
|
|
|
357
357
|
NumberField, DateField, TimeField, Select, ComboBox, DatePicker, DateRangePicker, Checkbox,
|
|
358
358
|
CheckboxGroup, RadioGroup, OtpInput and PressableField resolve variant="secondary" inside it and
|
|
359
359
|
variant="primary" outside. Compose the surface and omit the field variant.
|
|
360
|
-
Switch and Slider have no layer variant; a Radio follows its RadioGroup.
|
|
361
|
-
|
|
360
|
+
Switch and Slider have no layer variant; a Radio follows its RadioGroup. PaneGrid's
|
|
361
|
+
collapsed-pane drawer also establishes the context. SurfaceCard and bounded SurfaceAccordionCard derive
|
|
362
362
|
data-grammar-surface-depth from the parent context. The top layer is raised and a nested layer is
|
|
363
363
|
subordinate; never raise both. The family DNA owns their surface paint.
|
|
364
364
|
FE_NESTED_VARIANT rejects a static field variant
|
|
@@ -597,23 +597,17 @@ presentation:
|
|
|
597
597
|
Retained ownership reference, not proof of the current package/API. Verify actual installed source/props/tokens before use; correct outdated rows within selected maintenance scope using [source and maintenance](index.yaml#source-and-maintenance).
|
|
598
598
|
A part that carries one rule per side or per property names that CSS property after the part (`item padding-inline`); one part and property has one rule.
|
|
599
599
|
rows:
|
|
600
|
-
- component:
|
|
601
|
-
element: drawer body,
|
|
600
|
+
- component: PaneGrid
|
|
601
|
+
element: drawer body, collapsing pane
|
|
602
602
|
rule: PADDING-0
|
|
603
|
-
- component:
|
|
604
|
-
element:
|
|
605
|
-
rule: PADDING-4
|
|
606
|
-
- component: ChatWorkspace
|
|
607
|
-
element: overlay rail, hasRail, not isCompactRail
|
|
608
|
-
rule: PADDING-4
|
|
609
|
-
- component: ChatWorkspace
|
|
610
|
-
element: rail trigger boundary padding-block, hasRail, isCompactRail
|
|
603
|
+
- component: PaneGrid
|
|
604
|
+
element: trigger bar padding-block, collapsing pane
|
|
611
605
|
rule: PADDING-2
|
|
612
|
-
- component:
|
|
613
|
-
element:
|
|
606
|
+
- component: PaneGrid
|
|
607
|
+
element: trigger bar padding-inline, collapsing pane
|
|
614
608
|
rule: PADDING-4
|
|
615
|
-
- component:
|
|
616
|
-
element:
|
|
609
|
+
- component: PaneGrid
|
|
610
|
+
element: drawer trigger, collapsing pane
|
|
617
611
|
rule: PADDING-3
|
|
618
612
|
- component: EmptyNotice
|
|
619
613
|
element: root
|
|
@@ -959,8 +953,8 @@ presentation:
|
|
|
959
953
|
source/props/tokens before use; correct outdated rows within selected maintenance scope using [source
|
|
960
954
|
and maintenance](index.yaml#source-and-maintenance).
|
|
961
955
|
rows:
|
|
962
|
-
- component:
|
|
963
|
-
element:
|
|
956
|
+
- component: PaneGrid
|
|
957
|
+
element: drawer trigger, collapsing pane
|
|
964
958
|
rule: FONT-2
|
|
965
959
|
- component: Heading
|
|
966
960
|
element: root, scale!="display", level=1
|
|
@@ -31,42 +31,42 @@ pins:
|
|
|
31
31
|
source: packages/event-stream/package.json
|
|
32
32
|
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.'
|
|
33
33
|
'@starci/grammar':
|
|
34
|
-
version: 0.16.
|
|
34
|
+
version: 0.16.3
|
|
35
35
|
group: starci
|
|
36
36
|
install: registry
|
|
37
37
|
side: fe
|
|
38
38
|
source: packages/grammar/package.json
|
|
39
39
|
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.'
|
|
40
40
|
'@starci/eslint-canon-be':
|
|
41
|
-
version: 3.9.
|
|
41
|
+
version: 3.9.3
|
|
42
42
|
group: starci
|
|
43
43
|
install: registry
|
|
44
44
|
side: be
|
|
45
45
|
source: packages/eslint-canon-be/package.json
|
|
46
46
|
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.'
|
|
47
47
|
'@starci/eslint-canon-fe':
|
|
48
|
-
version: 9.6.
|
|
48
|
+
version: 9.6.3
|
|
49
49
|
group: starci
|
|
50
50
|
install: registry
|
|
51
51
|
side: fe
|
|
52
52
|
source: packages/eslint-canon-fe/package.json
|
|
53
53
|
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.'
|
|
54
54
|
'@starci/eslint-grammar-core':
|
|
55
|
-
version: 0.16.
|
|
55
|
+
version: 0.16.3
|
|
56
56
|
group: starci
|
|
57
57
|
install: registry
|
|
58
58
|
side: fe
|
|
59
59
|
source: packages/eslint-grammar-core/package.json
|
|
60
60
|
why: 'the StarCi Grammar component API lint, versioned with @starci/grammar; front ends compose this family package with the universal FE canon.'
|
|
61
61
|
'@starci/eslint-grammar-offset-pop':
|
|
62
|
-
version: 0.16.
|
|
62
|
+
version: 0.16.3
|
|
63
63
|
group: starci
|
|
64
64
|
install: registry
|
|
65
65
|
side: fe
|
|
66
66
|
source: packages/eslint-grammar-offset-pop/package.json
|
|
67
67
|
why: 'the Offset Pop Grammar component API lint, versioned with @starci/grammar; its shared component contract comes from the StarCi family lint without copying rules.'
|
|
68
68
|
'@starci/stylelint-canon':
|
|
69
|
-
version: 2.
|
|
69
|
+
version: 2.3.0
|
|
70
70
|
group: starci
|
|
71
71
|
install: registry
|
|
72
72
|
side: fe
|
|
@@ -85,7 +85,7 @@ pins:
|
|
|
85
85
|
side: both
|
|
86
86
|
source: packages/prettier-config/package.json
|
|
87
87
|
'@starci/jest-preset':
|
|
88
|
-
version: 2.
|
|
88
|
+
version: 2.6.0
|
|
89
89
|
group: starci
|
|
90
90
|
install: registry
|
|
91
91
|
side: be
|
|
@@ -106,7 +106,7 @@ pins:
|
|
|
106
106
|
source: packages/ai-router/package.json
|
|
107
107
|
why: 'the AI pool router of a back end: a NestJS module that routes OpenAI-compatible chat, stream and embeddings calls across keys, models and providers, with @starci/ai-router/testing for its specs; @starci/jest-preset compiles it as an ESM-only dependency.'
|
|
108
108
|
'@starci/hfs':
|
|
109
|
-
version: 4.
|
|
109
|
+
version: 4.13.0
|
|
110
110
|
group: starci
|
|
111
111
|
install: registry
|
|
112
112
|
side: both
|
|
@@ -11,10 +11,18 @@ title: The one StarCi CI standard
|
|
|
11
11
|
# over the runtime's own workflows) checks every item marked `check: ci-standard`; the other items are owned by the rule or render named in `by`.
|
|
12
12
|
# Apps get the standard only through `starci app sync`: the managed CI files are sync targets, and `starci app sync` (the dry run) / the app check refuse drift.
|
|
13
13
|
|
|
14
|
-
# The jobs of the managed app CI (job ids, in this order), per edition; `sonar` exists when the Sonar service is enabled.
|
|
14
|
+
# The jobs of the managed app CI (job ids, in this order), per edition; `sonar` exists when the Sonar service is enabled. `plan` decides what a run does,
|
|
15
|
+
# `images` builds the images once per commit beside the checks (a call of the reusable images.yml, so GitHub names its jobs `images / ...`), `verdict` is
|
|
16
|
+
# the one job branch protection requires and the deploy waits for.
|
|
17
|
+
# required the jobs a green run has concluded success. The CLI's `stack deploy --ci-run` reads this list from here and asks GitHub for each of them
|
|
18
|
+
# in the run it is given, so the list names the jobs GitHub reports under their own id; the verdict needs `images`, so it proves them.
|
|
19
|
+
# pipeline the jobs the workflow must hold besides those, judged through the verdict
|
|
15
20
|
jobs:
|
|
16
|
-
full: {required: [fast, lint, test, coverage, build], optional: [sonar]}
|
|
17
|
-
lite: {required: [fast, lint, test, coverage, build], optional: [db-types, sonar]}
|
|
21
|
+
full: {required: [plan, fast, lint, test, coverage, build, verdict], pipeline: [images], optional: [sonar]}
|
|
22
|
+
lite: {required: [plan, fast, lint, test, coverage, build, verdict], pipeline: [images], optional: [db-types, sonar]}
|
|
23
|
+
|
|
24
|
+
# One job per environment with deploy.auto follows the verdict: its id starts with this prefix and it calls the reusable stack-deploy.yml.
|
|
25
|
+
deployJobPrefix: deploy-
|
|
18
26
|
|
|
19
27
|
# The per-job settings the managed CI renders when .starcistacks/application-stacks.yaml ci.jobs leaves them out (ci.jobs.<job>.timeoutMinutes,
|
|
20
28
|
# ci.jobs.test.shards). Sized from a real run (NIVO v0.2.3 on the 2-vCPU GitHub runner, 2026-10-18): the be lint with the Sonar import took 11.5 minutes and the
|
|
@@ -34,14 +42,29 @@ heap:
|
|
|
34
42
|
items:
|
|
35
43
|
- id: candidate-trigger
|
|
36
44
|
title: the release CI runs on the release candidate ref
|
|
37
|
-
requires: "ci.yml runs on push of branches ['main', 'release/candidate-*'] and tags ['v*']
|
|
45
|
+
requires: "ci.yml runs on push of branches ['main', 'release/candidate-*'] and tags ['v*'], on pull_request to main and on workflow_dispatch; the release flows wait for the run of the release sha there; pull_request_target, workflow_run and schedules are never triggers"
|
|
38
46
|
covers: [app-ci, runtime]
|
|
39
47
|
by: [R221 CI_TRIGGERS_RELEASE_ONLY, R251 HFS_APP_WORKFLOW_TRIGGER]
|
|
40
48
|
- id: job-shape
|
|
41
|
-
title: the job shape fast / lint / test / coverage / build / sonar
|
|
42
|
-
requires: "the managed CI holds exactly the jobs of `jobs` for its edition; the unit run is a step of `test`; the merge, the coverage proof and the Codecov upload are steps of `coverage`, which alone holds the OIDC id-token"
|
|
49
|
+
title: the job shape plan / fast / lint / test / coverage / build / images / verdict / sonar and the deploy jobs
|
|
50
|
+
requires: "the managed CI holds exactly the jobs of `jobs` for its edition and one `deploy-<env>` job per environment with deploy.auto; the unit run is a step of `test`; the merge, the coverage proof and the Codecov upload are steps of `coverage`, which alone holds the OIDC id-token; the verdict needs every other job and runs always; a deploy job needs the verdict and calls the reusable stack-deploy.yml, and no other job deploys"
|
|
43
51
|
covers: [app-ci]
|
|
44
52
|
check: ci-standard
|
|
53
|
+
- id: pull-request-ci
|
|
54
|
+
title: a pull request runs the checks with no secret and no environment
|
|
55
|
+
requires: "a workflow that runs on pull_request has read-only workflow permissions, binds no job to an environment and reads no secret outside the jobs the plan skips for a pull request (sonar); it never checks out or runs the code of a pull request under pull_request_target"
|
|
56
|
+
covers: [app-ci]
|
|
57
|
+
by: [R221 CI_TRIGGERS_RELEASE_ONLY, R251 HFS_APP_WORKFLOW_TRIGGER]
|
|
58
|
+
- id: images-once
|
|
59
|
+
title: the images are built once per commit, in parallel with the checks, and pushed by digest
|
|
60
|
+
requires: "images.yml is a reusable workflow (workflow_call) that ci.yml calls beside the checks; every pushed image carries the immutable tag sha-<sha> (the release tag v<version> is a retag of that digest), an SBOM and provenance; the deploy takes digests only; no mutable environment tag is deployed; build arguments are public values"
|
|
61
|
+
covers: [app-ci]
|
|
62
|
+
by: [R271 HFS_RELEASE_TAG_MISMATCH, R269 HFS_SBOM_MISSING]
|
|
63
|
+
- id: deploy-brake
|
|
64
|
+
title: a green release tag deploys by itself, and STARCI_DEPLOY_PAUSED stops it
|
|
65
|
+
requires: "the deploy of an environment with deploy.auto starts only on a release tag run after the verdict, bound to that GitHub Environment (deployment refs restricted to v* tags); its variable STARCI_DEPLOY_PAUSED=true skips the automatic deploy without a code change; a manual dispatch of stack-deploy.yml ignores the brake"
|
|
66
|
+
covers: [app-ci]
|
|
67
|
+
by: [R251 HFS_DEPLOY_WORKFLOW]
|
|
45
68
|
- id: full-history-base-ref
|
|
46
69
|
title: full history and the lint base STARCI_BASE_REF
|
|
47
70
|
requires: "every actions/checkout of the managed CI fetches the full history (fetch-depth: 0), and the lint job writes STARCI_BASE_REF to GITHUB_ENV before it lints"
|
|
@@ -3776,7 +3776,7 @@ rules:
|
|
|
3776
3776
|
- {path: packages/hfs/src/hfs-lite-secrets.spec.mjs, edition: lite}
|
|
3777
3777
|
- id: R221
|
|
3778
3778
|
code: "CI_TRIGGERS_RELEASE_ONLY"
|
|
3779
|
-
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.
|
|
3779
|
+
law: "Workflow triggers are push filtered to v* tags or release/candidate-* branches, workflow_call (a reusable workflow has no trigger of its own) and workflow_dispatch. Runtime workflows and app ci.yml also admit main, and app ci.yml admits pull_request to main (branches ['main'] only: a pull request run has no secret and no environment). App ci.yml includes the candidate branch filter for starci app release push. pull_request_target, workflow_run, schedules, other branches, events, path filters and ignore filters are refused."
|
|
3780
3780
|
scope: runtime
|
|
3781
3781
|
editions: [lite, full]
|
|
3782
3782
|
docs: [docs/git-governance.md#r221]
|
|
@@ -4195,7 +4195,7 @@ rules:
|
|
|
4195
4195
|
editions: [lite, full]
|
|
4196
4196
|
docs:
|
|
4197
4197
|
- {path: docs/application-stacks.md#r251-deploy-workflow, editions: [lite, full]}
|
|
4198
|
-
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; every job that reads secrets.SOPS_AGE_KEY, a deployment job or not, is bound to the GitHub Environment whose members it opens, so it reads that environment's server key and never a repository secret of that name. Deployment jobs 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."
|
|
4198
|
+
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; every job that reads secrets.SOPS_AGE_KEY, a deployment job or not, is bound to the GitHub Environment whose members it opens, so it reads that environment's server key and never a repository secret of that name. Deployment jobs 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), workflow_call or workflow_dispatch; ci.yml also runs on main and on pull_request to main (read-only workflow permissions, no environment bound in the file), and other branch pushes are refused. A release deploys through the reusable stack-deploy.yml (ruling app-cicd-auto-deploy): the job of ci.yml that calls it (deploy-<env>, after the verdict job) holds permissions, concurrency and with.environment, the called deploy job binds the GitHub Environment (deployment refs restricted to v* tags) and reads SOPS_AGE_KEY there, and the transport is the sealed member vps-ci opened with that key, so the CI deploy key, the host and the pinned host keys are never GitHub secrets, workflow text or log lines; STARCI_DEPLOY_PAUSED on the environment stops the automatic deploy. The secrets allowlist and the dotenv ban are judged on the deployment jobs, not on the check jobs beside them."
|
|
4199
4199
|
kinds: [check]
|
|
4200
4200
|
gates: [settle, land, ci]
|
|
4201
4201
|
failureCodes: ["HFS_DEPLOY_WORKFLOW", "HFS_WORKFLOW_PIN", "HFS_CI_TOOL_INSTALL", "HFS_APP_WORKFLOW_TRIGGER"]
|
|
@@ -4205,7 +4205,7 @@ rules:
|
|
|
4205
4205
|
proves:
|
|
4206
4206
|
- {path: packages/hfs/src/hfs-deploy-workflow.spec.mjs, editions: [lite, full]}
|
|
4207
4207
|
exemplifiedBy:
|
|
4208
|
-
- {path: packages/hfs/templates/app/ci-workflows-lite/github/workflows/
|
|
4208
|
+
- {path: packages/hfs/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml, edition: lite}
|
|
4209
4209
|
- {path: packages/hfs/templates/app/ci-workflows/github/workflows/ci.yml, edition: full}
|
|
4210
4210
|
- {path: packages/hfs/src/hfs-deploy-workflow.spec.mjs, editions: [lite, full]}
|
|
4211
4211
|
witness:
|
|
@@ -4224,7 +4224,7 @@ rules:
|
|
|
4224
4224
|
proves:
|
|
4225
4225
|
- {path: packages/hfs/src/hfs-script-hygiene.spec.mjs, editions: [lite, full]}
|
|
4226
4226
|
exemplifiedBy:
|
|
4227
|
-
- {path: packages/hfs/templates/app/ci-workflows-lite/github/workflows/
|
|
4227
|
+
- {path: packages/hfs/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml, edition: lite}
|
|
4228
4228
|
- {path: packages/hfs/templates/app/ci-workflows/github/workflows/ci.yml, edition: full}
|
|
4229
4229
|
- {path: packages/hfs/src/hfs-script-hygiene.spec.mjs, editions: [lite, full]}
|
|
4230
4230
|
witness:
|
|
@@ -4331,7 +4331,7 @@ rules:
|
|
|
4331
4331
|
editionWhy: "owner:lite-backing-services makes the db service lite's database; database configuration, extension, migration and seed requirements apply to that backing."
|
|
4332
4332
|
docs: [docs/microservices.md#r259]
|
|
4333
4333
|
templateNa: "Extension use, contract migrations, vault operations and seed literals are product-authored db content, rather than a scaffolded SQL surface."
|
|
4334
|
-
law: "DB_CONFIG_EXT: extensions belong to the database allowlist, vault writes and migration role passwords are refused, and seed data holds no real email or phone. Destructive DDL belongs only to *_contract_* migrations whose targets are no longer referenced by back-end SQL or entities. Seeds contain no real email or phone literals."
|
|
4334
|
+
law: "DB_CONFIG_EXT: extensions belong to the database allowlist, vault writes and migration role passwords are refused, and seed data holds no real email or phone. Destructive DDL belongs only to *_contract_* migrations whose targets are no longer referenced by back-end SQL or entities, and a contract migration that is new since the previous release tag (the newest v* tag HEAD contains) never destroys an object that the back-end sources of that tag still reference: the deploy applies it right after the accept, while that release is the rollback target, so the code change goes in one release and its contract migration in the next. Seeds contain no real email or phone literals."
|
|
4335
4335
|
kinds: [check]
|
|
4336
4336
|
gates: [settle, land, ci]
|
|
4337
4337
|
failureCodes: ["DB_EXTENSION_ALLOWED", "DB_MIGRATION_DESTRUCTIVE", "HFS_SEED_PII", "DB_VAULT_WRITE"]
|
|
@@ -4600,7 +4600,7 @@ rules:
|
|
|
4600
4600
|
- {path: packages/hfs/src/hfs-doc-fresh.spec.mjs, edition: full}
|
|
4601
4601
|
- id: R271
|
|
4602
4602
|
code: "HFS_RELEASE_TAG_MISMATCH"
|
|
4603
|
-
law: "A lite or full product release uses a v-prefixed semantic version tag equal to package.json version; images.yml
|
|
4603
|
+
law: "A lite or full product release uses a v-prefixed semantic version tag equal to package.json version; images.yml pushes each image under the immutable tag sha-<github.sha> and adds the release name v<version> only as a retag of that digest on a release tag run, under a guard that rejects any other ref; and every database deployment refuses a non-tag ref before migration (a lite app migrates inside its stack deploy, which stack-deploy.yml starts only from a release tag, so its db-deploy.yml is retired and starci app sync removes it)."
|
|
4604
4604
|
kinds: [check]
|
|
4605
4605
|
gates: ["settle","land","ci"]
|
|
4606
4606
|
failureCodes: ["HFS_RELEASE_TAG_MISMATCH", "HFS_DB_DEPLOY_UNGUARDED"]
|
|
@@ -4614,7 +4614,7 @@ rules:
|
|
|
4614
4614
|
exemplifiedBy:
|
|
4615
4615
|
- {"path":"packages/hfs/templates/app/ci-workflows-lite/github/workflows/images.yml","edition":"lite"}
|
|
4616
4616
|
- {"path":"packages/hfs/templates/app/ci-workflows/github/workflows/images.yml","edition":"full"}
|
|
4617
|
-
- {"path":"packages/hfs/templates/app/ci-workflows-lite/github/workflows/
|
|
4617
|
+
- {"path":"packages/hfs/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml","edition":"lite"}
|
|
4618
4618
|
- {path: packages/hfs/src/hfs-release-provenance.spec.mjs, edition: lite}
|
|
4619
4619
|
- {path: packages/hfs/src/hfs-release-provenance.spec.mjs, edition: full}
|
|
4620
4620
|
witness:
|
|
@@ -5956,3 +5956,93 @@ rules:
|
|
|
5956
5956
|
source: owner
|
|
5957
5957
|
quote: "A Mascot reuses the provenance props of Image and MediaFrame; a Mascot without an assetSlot is a finding (relayed by the nivo-lite app)."
|
|
5958
5958
|
ruling: "Mascot takes AssetSlotProps (assetSlot, assetSha256, assetPrompt) and emits them as data-asset-* on its root; the lint requires assetSlot on every Mascot."
|
|
5959
|
+
- id: R340
|
|
5960
|
+
code: "FE_RAISED_SURFACE_IN_SURFACE"
|
|
5961
|
+
law: "A Grammar surface that accepts a depth prop (SurfaceListCard) does not pass depth=\"top\" when a SurfaceCard, SurfaceListCard or SurfaceAccordionCard of the same file encloses it: a nested surface is subordinate (outlined, BOUNDARY-5), never raised again (BOUNDARY-6, DNA-COMMON-NO-RAISED-SURFACE-IN-SURFACE); the Grammar resolves the depth from the surface context. A depth computed at run time is not judged."
|
|
5962
|
+
kinds: [lint]
|
|
5963
|
+
gates: [pre-commit, settle, land, ci]
|
|
5964
|
+
failureCodes: ["FE_RAISED_SURFACE_IN_SURFACE"]
|
|
5965
|
+
enforcers:
|
|
5966
|
+
- {kind: eslint-grammar-core, id: no-raised-surface-in-surface}
|
|
5967
|
+
editions: [lite, full]
|
|
5968
|
+
docs: [knowledge/patterns/fe/ui.yaml#FE-UI]
|
|
5969
|
+
templateNa: "A template forces no surface depth; the app composes its surfaces."
|
|
5970
|
+
proves:
|
|
5971
|
+
- packages/eslint-grammar-core/src/surface-depth.spec.ts
|
|
5972
|
+
exemplifiedBy:
|
|
5973
|
+
- {path: packages/eslint-grammar-core/src/surface-depth.spec.ts, editions: [lite, full], codes: [FE_RAISED_SURFACE_IN_SURFACE]}
|
|
5974
|
+
witness:
|
|
5975
|
+
- {path: packages/eslint-grammar-core/src/surface-depth.spec.ts, editions: [lite, full], codes: [FE_RAISED_SURFACE_IN_SURFACE]}
|
|
5976
|
+
cases:
|
|
5977
|
+
- id: case-1
|
|
5978
|
+
date: "2026-10-31"
|
|
5979
|
+
source: owner
|
|
5980
|
+
quote: "A list inside another surface is outlined (BOUNDARY-5, DNA-COMMON-NO-RAISED-SURFACE-IN-SURFACE); SurfaceListCard takes its depth from the surface context (grammar 0.16.2)."
|
|
5981
|
+
ruling: "SurfaceListCard resolves its depth from the surface context like SurfaceCard and SurfaceAccordionCard; an app that forces depth=\"top\" on a surface nested in another surface raises a surface inside a surface, so the lint refuses it."
|
|
5982
|
+
- id: R341
|
|
5983
|
+
code: "FE_FREESTYLE_REASON_MISSING"
|
|
5984
|
+
law: "A Grammar Freestyle region, the one sanctioned place where an app writes its own plain elements and CSS because no Grammar component fits, names its reason: a non-empty string literal saying why. The rules that judge hand-written elements, classes, styles and generated art leave the raw elements of a Freestyle subtree alone; a Grammar component inside it is judged as before. An element with a spread attribute is not judged."
|
|
5985
|
+
kinds: [lint]
|
|
5986
|
+
gates: [pre-commit, settle, land, ci]
|
|
5987
|
+
failureCodes: ["FE_FREESTYLE_REASON_MISSING"]
|
|
5988
|
+
enforcers:
|
|
5989
|
+
- {kind: eslint-grammar-core, id: freestyle-has-reason}
|
|
5990
|
+
editions: [lite, full]
|
|
5991
|
+
docs: [knowledge/patterns/fe/ui.yaml#FE-UI]
|
|
5992
|
+
templateNa: "A template writes no Freestyle region; the app adds one only when no Grammar component fits."
|
|
5993
|
+
proves:
|
|
5994
|
+
- packages/eslint-grammar-core/src/freestyle.spec.ts
|
|
5995
|
+
exemplifiedBy:
|
|
5996
|
+
- {path: packages/eslint-grammar-core/src/freestyle.spec.ts, editions: [lite, full], codes: [FE_FREESTYLE_REASON_MISSING]}
|
|
5997
|
+
witness:
|
|
5998
|
+
- {path: packages/eslint-grammar-core/src/freestyle.spec.ts, editions: [lite, full], codes: [FE_FREESTYLE_REASON_MISSING]}
|
|
5999
|
+
cases:
|
|
6000
|
+
- id: case-1
|
|
6001
|
+
date: "2026-10-31"
|
|
6002
|
+
source: owner
|
|
6003
|
+
quote: "cái trang này free style đi trò div và làm bth thôi, thêm 1 component Freestyle hoặc tên gì đó tương đương đi trò, ý là cho custom nhưng vẫm bám grammar trừ bất đắc dĩ ntn"
|
|
6004
|
+
ruling: "The Grammar has one sanctioned escape hatch, Freestyle: a region where the app writes its own plain divs and CSS because no Grammar component fits; it names its reason (the lint requires it), and the rest of the page still follows the Grammar."
|
|
6005
|
+
- id: R342
|
|
6006
|
+
code: "BE_ENTITY_EXTENDS_BASE"
|
|
6007
|
+
law: "An @Entity class does not repeat the columns every table shares: a database-set created_at or updated_at (CreateDateColumn, UpdateDateColumn, or a Column on those names, or on createdAt / updatedAt, with insert false, update false or a default) and, beside both, the generated uuid id are inherited from AbstractEntity or UuidAbstractEntity of @modules/platform/database (BE-PERSISTENCE-3, ruling entity-base). The finding names the base to extend: UuidAbstractEntity with the uuid key, AbstractEntity without it. A table whose timestamps the application sets (a plain Column), or that lacks one of them, extends nothing and maps its own; the base file platform/database/abstract.entity.ts is exempt."
|
|
6008
|
+
kinds: [lint]
|
|
6009
|
+
gates: [pre-commit, settle, land, ci]
|
|
6010
|
+
failureCodes: ["BE_ENTITY_EXTENDS_BASE"]
|
|
6011
|
+
enforcers:
|
|
6012
|
+
- {kind: eslint-be, id: entity-extends-base}
|
|
6013
|
+
editions: [lite, full]
|
|
6014
|
+
docs: [contexts/code/be/persistence.md]
|
|
6015
|
+
proves:
|
|
6016
|
+
- packages/eslint-canon-be/src/entity-base.spec.ts
|
|
6017
|
+
exemplifiedBy:
|
|
6018
|
+
- {path: packages/hfs/templates/be/table/entity.ts.tpl, editions: [lite, full]}
|
|
6019
|
+
- {path: packages/eslint-canon-be/src/entity-base.spec.ts, editions: [lite, full], codes: [BE_ENTITY_EXTENDS_BASE]}
|
|
6020
|
+
witness:
|
|
6021
|
+
- {path: packages/eslint-canon-be/src/entity-base.spec.ts, editions: [lite, full], codes: [BE_ENTITY_EXTENDS_BASE]}
|
|
6022
|
+
cases:
|
|
6023
|
+
- id: case-1
|
|
6024
|
+
date: "2026-10-31"
|
|
6025
|
+
source: owner
|
|
6026
|
+
quote: "Entities do not repeat the columns every table shares; the owner approved a lint so they never repeat them again (ruling entity-base, 2026-10-31)."
|
|
6027
|
+
ruling: "A table whose migration has created_at, updated_at (set by the database) and the generated uuid id maps them by extending AbstractEntity or UuidAbstractEntity from @modules/platform/database; an entity that declares them itself is a finding. A table without them extends nothing."
|
|
6028
|
+
- id: R343
|
|
6029
|
+
code: "HFS_CHECK_CACHE_FORGED"
|
|
6030
|
+
editions: ["lite", "full"]
|
|
6031
|
+
law: "An app's committed check cache (.starcistacks/cache/) is trusted in CI only as it stands at the trusted base; a record whose key matches the tree and whose findings differ from the inherited or computed result is HFS_CHECK_CACHE_FORGED (a hard blocker at any count), a touched key without a current record is HFS_CHECK_CACHE_STALE and a missing cache is HFS_CHECK_CACHE_MISSING (both report-only warnings)."
|
|
6032
|
+
kinds: [codemod]
|
|
6033
|
+
gates: [ci]
|
|
6034
|
+
failureCodes: ["HFS_CHECK_CACHE_FORGED", "HFS_CHECK_CACHE_STALE", "HFS_CHECK_CACHE_MISSING"]
|
|
6035
|
+
enforcers:
|
|
6036
|
+
- {kind: hfs, id: check-cache, at: packages/hfs/cache/trust.mjs}
|
|
6037
|
+
proves:
|
|
6038
|
+
- {path: packages/hfs/src/hfs-check-cache.spec.mjs, editions: [lite, full]}
|
|
6039
|
+
exemplifiedBy:
|
|
6040
|
+
- {path: packages/hfs/src/hfs-check-cache.spec.mjs, editions: [lite, full]}
|
|
6041
|
+
witness:
|
|
6042
|
+
- {path: packages/hfs/src/hfs-check-cache.spec.mjs, editions: [lite, full], codes: [HFS_CHECK_CACHE_FORGED, HFS_CHECK_CACHE_STALE, HFS_CHECK_CACHE_MISSING]}
|
|
6043
|
+
cases:
|
|
6044
|
+
- id: case-1
|
|
6045
|
+
date: "2026-10-31"
|
|
6046
|
+
source: owner
|
|
6047
|
+
quote: "app check cache lại vào file dc không? sau chỉ check diff; chốt starcistacks lưu hết nhé và check diff và triển cho ci và local lun."
|
|
6048
|
+
ruling: "The app commits its check results in .starcistacks/cache/; CI trusts only the cache at the trusted base, a forged record blocks at any count, stale and missing only warn (ruling app-check-cache)."
|
|
@@ -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.
|
|
10
|
+
version: 3.15.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).
|
|
@@ -58,6 +58,7 @@ versioning:
|
|
|
58
58
|
# 3.13.0 (rulings lite-pgmq-bus-bullmq-jobs and lite-db-image-in-stacks, 2026-10-29) is a change for lite only: ruleParams.db.extensions
|
|
59
59
|
# admits pgmq, the event bus, ruleParams.db.queueOutbox is retired with the pg_cron half of R258, the lite event-bus functions replace
|
|
60
60
|
# event_bus_write in ruleParams.be.dbFunctions, and app.db.image moves to .starcistacks/images/db/Dockerfile (db/ keeps schema and data).
|
|
61
|
+
# 3.15.0 (ruling app-check-cache, 2026-10-31) adds the optional app.starcistacks.cache, the committed check cache of the app (.starcistacks/cache/, both editions).
|
|
61
62
|
# 3.14.0 (ruling app-secrets-one-standard, 2026-10-29) adds the optional app.ci-stack-key-restore, the managed workflow that writes the
|
|
62
63
|
# deploy server's age identity back from its GitHub Environment backup.
|
|
63
64
|
|
|
@@ -645,7 +646,7 @@ slots:
|
|
|
645
646
|
since: 3.1.0
|
|
646
647
|
profiles: [app]
|
|
647
648
|
editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite renders its image workflow; full renders images for its separately deployed services."
|
|
648
|
-
#
|
|
649
|
+
# The reusable workflow ci.yml calls: builds every image of the app once per commit beside the checks, pushes the deployed ones to the registry by sha-<sha> with SBOM and provenance. Rendered by starci app sync.
|
|
649
650
|
path: ".github/workflows/images.yml"
|
|
650
651
|
presence: required
|
|
651
652
|
tracked: tracked
|
|
@@ -657,23 +658,23 @@ slots:
|
|
|
657
658
|
- id: app.ci-db
|
|
658
659
|
profiles: [app]
|
|
659
660
|
exemplifiedBy:
|
|
660
|
-
- packages/hfs/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml
|
|
661
661
|
- {path: packages/hfs/src/hfs-release-provenance.spec.mjs, edition: full}
|
|
662
|
-
editionWhy: "ruling:
|
|
663
|
-
# The database deploy workflow: workflow_dispatch only (like app.ci-e2e)
|
|
662
|
+
editionWhy: "ruling:app-cicd-auto-deploy: a lite app migrates in the migrate phase of its stack deploy, so its database deploy workflow is retired (starci app sync --execute removes it); full owns an optional provider-specific workflow."
|
|
663
|
+
# The database deploy workflow: workflow_dispatch only (like app.ci-e2e). A lite app has none: the stack deploy backs up and migrates inside the database container.
|
|
664
664
|
path: ".github/workflows/db-deploy.yml"
|
|
665
665
|
presence: optional
|
|
666
|
+
litePresence: forbidden # ruling:app-cicd-auto-deploy: retired in lite; migrations are the deploy's migrate phase
|
|
666
667
|
tracked: tracked
|
|
667
668
|
tier: none
|
|
668
669
|
tests: none
|
|
669
|
-
|
|
670
|
+
goesTo: "the stack deploy: starci app stack deploy takes a verified backup, applies the expand migrations before the services update and the contract migrations after the accept (a full app keeps its own optional workflow)"
|
|
670
671
|
rules: [HFS_MANAGED_FILE_DRIFT]
|
|
671
672
|
since: 2.1.0
|
|
672
673
|
- id: app.ci-stack-deploy
|
|
673
674
|
since: 3.3.0
|
|
674
675
|
profiles: [app]
|
|
675
676
|
editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): both editions deploy their images to a Docker Swarm stack through the same starci stack verbs; the workflow lists the images the app declares."
|
|
676
|
-
# The Swarm stack deploy workflow:
|
|
677
|
+
# The Swarm stack deploy workflow: reusable (ci.yml calls it after the verdict for every environment with deploy.auto) and workflow_dispatch for the owner's manual operations; a thin file that calls `starci app stack`, rendered by starci app sync.
|
|
677
678
|
path: ".github/workflows/stack-deploy.yml"
|
|
678
679
|
presence: optional
|
|
679
680
|
tracked: tracked
|
|
@@ -1023,6 +1024,17 @@ slots:
|
|
|
1023
1024
|
tests: none
|
|
1024
1025
|
since: 3.11.0
|
|
1025
1026
|
rules: [HFS_MANAGED_FILE_DRIFT]
|
|
1027
|
+
- id: app.starcistacks.cache
|
|
1028
|
+
profiles: [app]
|
|
1029
|
+
# The committed result cache of `starci app check` and `starci app lint` (ruling app-check-cache, contexts/project/check-cache.md): JSON Lines records keyed by
|
|
1030
|
+
# content, one folder per family. App-owned and written only by the CLI; sync never renders, overwrites or judges it. Both editions.
|
|
1031
|
+
path: ".starcistacks/cache/"
|
|
1032
|
+
presence: optional
|
|
1033
|
+
tracked: tracked
|
|
1034
|
+
tier: none
|
|
1035
|
+
tests: none
|
|
1036
|
+
since: 3.15.0
|
|
1037
|
+
allows: ["fingerprint.jsonl", "check/**", "lint/**", "test-coverage/**", "sonar-baseline/**", "codecov-baseline/**"]
|
|
1026
1038
|
- id: app.db.bootstrap
|
|
1027
1039
|
profiles: [app]
|
|
1028
1040
|
editions: [lite]
|
|
@@ -1764,7 +1764,7 @@ CI_STANDARD:
|
|
|
1764
1764
|
CI_TRIGGERS_RELEASE_ONLY:
|
|
1765
1765
|
title: "Workflow trigger outside R221"
|
|
1766
1766
|
title_vi: "Workflow có trigger ngoài R221"
|
|
1767
|
-
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
|
|
1767
|
+
meaning_vi: "Workflow khai báo trigger ngoài R221: main được phép trong workflow runtime và ci.yml của app; pull_request tới main chỉ được phép trong ci.yml, workflow_call dành cho workflow dùng lại; pull_request_target, workflow_run, lịch chạy và các nhánh, sự kiện, bộ lọc khác phải theo R221."
|
|
1768
1768
|
causes_vi:
|
|
1769
1769
|
- "Khai báo nhánh, sự kiện hoặc bộ lọc không thuộc R221"
|
|
1770
1770
|
- "Khai báo nhánh, sự kiện hoặc bộ lọc không thuộc R221"
|
|
@@ -3180,7 +3180,7 @@ HFS_APP_LEXICON:
|
|
|
3180
3180
|
HFS_APP_WORKFLOW_TRIGGER:
|
|
3181
3181
|
title: "App workflow trigger outside R251"
|
|
3182
3182
|
title_vi: "Workflow ứng dụng có sự kiện khởi chạy không hợp lệ"
|
|
3183
|
-
meaning_vi: "Workflow ứng dụng khai báo trigger ngoài R251; ci.yml được chạy trên main
|
|
3183
|
+
meaning_vi: "Workflow ứng dụng khai báo trigger ngoài R251; ci.yml được chạy trên main, nhánh release candidate và pull_request tới main (quyền chỉ đọc, không gắn environment), images.yml và stack-deploy.yml là workflow dùng lại (workflow_call), các workflow khác giữ phạm vi trigger của R251."
|
|
3184
3184
|
causes_vi:
|
|
3185
3185
|
- "Khai báo trigger không thuộc R251"
|
|
3186
3186
|
nextStep_vi: "Sửa trigger theo R251 và chạy starci app sync --execute."
|
|
@@ -3351,7 +3351,7 @@ HFS_DB_DEPLOY_UNGUARDED:
|
|
|
3351
3351
|
title_vi: "Triển khai cơ sở dữ liệu chấp nhận ref không phải tag"
|
|
3352
3352
|
meaning_vi: "Mỗi đường chạy triển khai migration phải từ chối ref không phải tag phát hành trước khi tác động cơ sở dữ liệu."
|
|
3353
3353
|
causes_vi:
|
|
3354
|
-
- "Workflow db-deploy thiếu chốt kiểm tag hoặc chốt không chặn đường chạy migration theo luật R271."
|
|
3354
|
+
- "Workflow db-deploy thiếu chốt kiểm tag hoặc chốt không chặn đường chạy migration theo luật R271 (app lite không còn db-deploy: migration chạy trong bước migrate của stack deploy)."
|
|
3355
3355
|
nextStep_vi: "Thêm chốt kiểm ref tag trước mỗi bước triển khai migration, rồi chứng minh ref nhánh bị từ chối."
|
|
3356
3356
|
owner: op-retry
|
|
3357
3357
|
kind: check-finding
|
|
@@ -3653,6 +3653,37 @@ HFS_FORBIDDEN_PRESENT:
|
|
|
3653
3653
|
owner: op-retry
|
|
3654
3654
|
kind: check-finding
|
|
3655
3655
|
|
|
3656
|
+
HFS_CHECK_CACHE_FORGED:
|
|
3657
|
+
title: "A committed check cache record disagrees with what the check computes"
|
|
3658
|
+
title_vi: "Bản ghi cache kiểm tra đã commit không khớp kết quả kiểm tra thật"
|
|
3659
|
+
meaning_vi: "`<file>`: bản ghi trong `.starcistacks/cache/` có khóa khớp cây hiện tại nhưng phát hiện (findings) của nó khác với kết quả mà bộ kiểm tra tính ra từ cache tại base tin cậy. CI chỉ tin cache như nó đang có ở base, nên bản ghi này bị sửa tay hoặc một bước kiểm tra không xác định (khác nhau giữa các máy)."
|
|
3660
|
+
causes_vi:
|
|
3661
|
+
- "Sửa tay một tệp trong `.starcistacks/cache/` (xóa phát hiện để qua mặt kiểm tra)"
|
|
3662
|
+
- "Một bước kiểm tra cho kết quả khác nhau trên các máy khác nhau"
|
|
3663
|
+
nextStep_vi: "Chạy lại `starci app check` trên cây sạch để sinh lại cache rồi commit; không sửa tay thư mục cache. Lỗi này chặn ở mọi số lượng."
|
|
3664
|
+
owner: op-retry
|
|
3665
|
+
kind: check-finding
|
|
3666
|
+
|
|
3667
|
+
HFS_CHECK_CACHE_STALE:
|
|
3668
|
+
title: "A changed file has no current record in the committed check cache"
|
|
3669
|
+
title_vi: "Tệp đã đổi nhưng cache kiểm tra đã commit chưa có bản ghi hiện hành"
|
|
3670
|
+
meaning_vi: "`<file>` đổi so với base tin cậy mà `.starcistacks/cache/` đã commit không có bản ghi khớp nội dung hiện tại. CI vẫn kiểm tra đầy đủ tệp này nên kết quả đúng, chỉ tốn thời gian (cảnh báo)."
|
|
3671
|
+
causes_vi:
|
|
3672
|
+
- "Quên chạy `starci app check` và commit cache sau khi sửa"
|
|
3673
|
+
nextStep_vi: "Chạy `starci app check` rồi commit `.starcistacks/cache/` cùng thay đổi."
|
|
3674
|
+
owner: op-retry
|
|
3675
|
+
kind: check-finding
|
|
3676
|
+
|
|
3677
|
+
HFS_CHECK_CACHE_MISSING:
|
|
3678
|
+
title: "The app commits no check cache"
|
|
3679
|
+
title_vi: "Ứng dụng chưa commit cache kiểm tra"
|
|
3680
|
+
meaning_vi: "`.starcistacks/cache/` không có bản ghi nào nên mọi lần chạy đều kiểm tra đầy đủ (cảnh báo, kết quả vẫn đúng)."
|
|
3681
|
+
causes_vi:
|
|
3682
|
+
- "Ứng dụng mới hoặc vừa nâng cấp, chưa chạy `starci app check` lần đầu"
|
|
3683
|
+
nextStep_vi: "Chạy `starci app check` một lần rồi commit `.starcistacks/cache/` để các lần sau chỉ kiểm tra phần thay đổi."
|
|
3684
|
+
owner: op-retry
|
|
3685
|
+
kind: check-finding
|
|
3686
|
+
|
|
3656
3687
|
HFS_FORMAT:
|
|
3657
3688
|
title: "Prettier is the only formatter"
|
|
3658
3689
|
title_vi: "Chưa format bằng Prettier"
|
|
@@ -4026,7 +4057,7 @@ HFS_README_WORK_POINTER_MISSING:
|
|
|
4026
4057
|
HFS_RELEASE_TAG_MISMATCH:
|
|
4027
4058
|
title: "Product release tag does not match its version or image tag"
|
|
4028
4059
|
title_vi: "Tag phát hành sản phẩm không khớp phiên bản hoặc tag image"
|
|
4029
|
-
meaning_vi: "Tag phát hành phải có dạng v cộng phiên bản package.json; workflow images.yml
|
|
4060
|
+
meaning_vi: "Tag phát hành phải có dạng v cộng phiên bản package.json; workflow images.yml đẩy image với tag bất biến sha-<commit>, còn tên phát hành v<phiên bản> chỉ là retag của digest đó trên lần chạy tag phát hành, sau một chốt kiểm ref."
|
|
4030
4061
|
causes_vi:
|
|
4031
4062
|
- "Phiên bản package và tag khác nhau hoặc image dùng tag khác theo luật R271."
|
|
4032
4063
|
nextStep_vi: "Đồng bộ phiên bản package và tag phát hành, rồi dùng cùng tag trong workflow xây image."
|
|
@@ -5541,6 +5572,17 @@ BE_ENTITY_SCHEMA_DRIFT:
|
|
|
5541
5572
|
owner: op-retry
|
|
5542
5573
|
kind: check-finding
|
|
5543
5574
|
|
|
5575
|
+
BE_ENTITY_EXTENDS_BASE:
|
|
5576
|
+
title: "An entity repeats the columns every table shares instead of extending the base"
|
|
5577
|
+
title_vi: "Entity lặp lại các cột mọi bảng đều có thay vì kế thừa lớp nền"
|
|
5578
|
+
meaning_vi: "File `<path>` khai một lớp @Entity tự map cột created_at hoặc updated_at do cơ sở dữ liệu đặt (CreateDateColumn, UpdateDateColumn, hoặc Column với insert false, update false hay default) và có thể cả khóa uuid được sinh ra bên cạnh cả hai."
|
|
5579
|
+
causes_vi:
|
|
5580
|
+
- "Vi phạm R342 (BE-PERSISTENCE-3, phán quyết entity-base): các cột mọi bảng cùng có được kế thừa, không lặp lại trong từng entity."
|
|
5581
|
+
- "Entity được viết tay hoặc sao chép từ entity cũ trước khi có AbstractEntity và UuidAbstractEntity trong platform/database."
|
|
5582
|
+
nextStep_vi: "Xóa các thuộc tính đó và khai `extends UuidAbstractEntity` (có khóa uuid id) hoặc `extends AbstractEntity` (không có), import từ `@modules/platform/database`. Bảng có timestamp do ứng dụng đặt hoặc thiếu một trong hai cột thì không kế thừa gì và map cột đó bằng `@Column` thường."
|
|
5583
|
+
owner: op-retry
|
|
5584
|
+
kind: check-finding
|
|
5585
|
+
|
|
5544
5586
|
BE_AUTH_PASSPORT_OWNER:
|
|
5545
5587
|
title: "An identity library is imported outside integrations/passport"
|
|
5546
5588
|
title_vi: "Thư viện định danh bị import ngoài integrations/passport"
|