@starci/hfs 4.12.2 → 4.13.2
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 +50 -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 +20 -8
- package/runtime/modules/kernel/failure-codes.yaml +46 -4
- package/runtime/modules/schemas/application-stacks.schema.yaml +48 -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 +18 -0
- package/runtime/scripts/hfs/stack-deploy-config.mjs +53 -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 +33 -0
- package/stack/ci-key.d.mts +11 -0
- package/stack/ci-key.mjs +44 -0
- package/stack/cli.d.mts +7 -1
- package/stack/cli.mjs +22 -13
- package/stack/config.d.mts +15 -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 +22 -5
- package/stack/deploy.mjs +242 -72
- package/stack/journal.d.mts +146 -0
- package/stack/journal.mjs +191 -0
- package/stack/manager-backup.d.mts +54 -0
- package/stack/manager-backup.mjs +136 -0
- package/stack/notify.d.mts +9 -0
- package/stack/notify.mjs +32 -0
- package/stack/release-store.d.mts +49 -1
- package/stack/release-store.mjs +102 -16
- package/stack/remote.d.mts +2 -0
- package/stack/remote.mjs +32 -11
- package/stack/rollback-guard.d.mts +10 -0
- package/stack/rollback-guard.mjs +41 -0
- package/stack/server-key.d.mts +2 -0
- package/stack/server-key.mjs +1 -1
- package/stack/ssh-gate.d.mts +35 -0
- package/stack/ssh-gate.mjs +145 -0
- package/stack/status.d.mts +51 -0
- package/stack/status.mjs +115 -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.5
|
|
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.
|
|
41
|
+
version: 3.10.0
|
|
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.5
|
|
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.5
|
|
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.5
|
|
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.2
|
|
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:
|
|
@@ -5979,3 +5979,93 @@ rules:
|
|
|
5979
5979
|
source: owner
|
|
5980
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
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)."
|
|
6049
|
+
- id: R344
|
|
6050
|
+
code: "BE_AI_VIRTUAL_KEY"
|
|
6051
|
+
law: "An app's ai-router gateway has one client key, STARCI_AI_KEY (sk-starci-<app>-<48 hex>, sealed in the ai member, used by the integrations/ai LangChain client and the agent runtime alike), and no admin token: integrations/ai reads exactly that secret name with env.secret, as a literal, and the custody owner integrations/ai-gateway passes the gateway no adminToken."
|
|
6052
|
+
kinds: [lint]
|
|
6053
|
+
gates: [pre-commit, settle, land, ci]
|
|
6054
|
+
failureCodes: ["BE_AI_VIRTUAL_KEY"]
|
|
6055
|
+
enforcers:
|
|
6056
|
+
- {kind: eslint-be, id: ai-virtual-key-name}
|
|
6057
|
+
editions: [lite, full]
|
|
6058
|
+
docs: [contexts/code/be/ai.md]
|
|
6059
|
+
templateNa: "The skeleton scaffolds no model call; an app adds the ai and ai-gateway integrations and the ai member when it calls a model."
|
|
6060
|
+
proves:
|
|
6061
|
+
- {path: packages/eslint-canon-be/src/ai-gateway.spec.ts, editions: [lite, full], codes: [BE_AI_VIRTUAL_KEY]}
|
|
6062
|
+
exemplifiedBy:
|
|
6063
|
+
- {path: packages/eslint-canon-be/src/ai-gateway.spec.ts, editions: [lite, full], codes: [BE_AI_VIRTUAL_KEY]}
|
|
6064
|
+
witness:
|
|
6065
|
+
- {path: packages/eslint-canon-be/src/ai-gateway.spec.ts, editions: [lite, full], codes: [BE_AI_VIRTUAL_KEY]}
|
|
6066
|
+
cases:
|
|
6067
|
+
- id: case-1
|
|
6068
|
+
date: "2026-10-31"
|
|
6069
|
+
source: owner
|
|
6070
|
+
quote: "Xóa ai api key và openclaw ai key chỉnh thành starci ai key là key ảo"
|
|
6071
|
+
ruling: "Per app and environment the ai member holds the upstream provider keys and one random virtual key STARCI_AI_KEY that the gateway mounts and every client uses; the gateway has no admin API (ruling be-ai-one-virtual-key)."
|
|
@@ -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]
|
|
@@ -1987,7 +1999,7 @@ slots:
|
|
|
1987
1999
|
tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts; plus src/tests/integration/<provider>/ (R112)
|
|
1988
2000
|
coverage: required
|
|
1989
2001
|
budget: {indexExports: 80} # a provider's port surface includes clients, config, options, tokens and errors
|
|
1990
|
-
rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT, BE_INTEGRATION_SPEC_MISSING, BE_AI_KEY_CUSTODY, BE_AI_GATEWAY_CLIENT]
|
|
2002
|
+
rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT, BE_INTEGRATION_SPEC_MISSING, BE_AI_KEY_CUSTODY, BE_AI_GATEWAY_CLIENT, BE_AI_VIRTUAL_KEY]
|
|
1991
2003
|
- id: be.integrations.model
|
|
1992
2004
|
since: 3.1.0
|
|
1993
2005
|
profiles: [be]
|