@starci/skills 1.1.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/INDEX.md +74 -0
- package/INDEX.vi.md +75 -0
- package/README.md +44 -0
- package/README.vi.md +43 -0
- package/SKILL.md +135 -0
- package/SKILL.vi.md +128 -0
- package/alias/INDEX.md +104 -0
- package/alias/INDEX.vi.md +104 -0
- package/alias/alias.json +297 -0
- package/bin/starci-skills.mjs +219 -0
- package/knowledge/INDEX.md +22 -0
- package/knowledge/INDEX.vi.md +22 -0
- package/knowledge/grammars/starci/DNA.md +146 -0
- package/knowledge/grammars/starci/DNA.vi.md +146 -0
- package/knowledge/grammars/starci/INDEX.md +25 -0
- package/knowledge/grammars/starci/INDEX.vi.md +25 -0
- package/knowledge/grammars/starci/family.md +50 -0
- package/knowledge/grammars/starci/family.vi.md +50 -0
- package/knowledge/grammars/starci/idioms.md +120 -0
- package/knowledge/grammars/starci/idioms.vi.md +121 -0
- package/knowledge/grammars/starci/playbook.md +36 -0
- package/knowledge/grammars/starci/playbook.vi.md +35 -0
- package/knowledge/patterns/be/INDEX.md +29 -0
- package/knowledge/patterns/be/INDEX.vi.md +29 -0
- package/knowledge/patterns/be/comment.md +80 -0
- package/knowledge/patterns/be/comment.vi.md +80 -0
- package/knowledge/patterns/be/error.md +86 -0
- package/knowledge/patterns/be/error.vi.md +86 -0
- package/knowledge/patterns/be/folder.md +87 -0
- package/knowledge/patterns/be/folder.vi.md +86 -0
- package/knowledge/patterns/be/function.md +80 -0
- package/knowledge/patterns/be/function.vi.md +80 -0
- package/knowledge/patterns/be/imports.md +79 -0
- package/knowledge/patterns/be/imports.vi.md +79 -0
- package/knowledge/patterns/be/naming.md +87 -0
- package/knowledge/patterns/be/naming.vi.md +87 -0
- package/knowledge/patterns/be/test.md +79 -0
- package/knowledge/patterns/be/test.vi.md +79 -0
- package/knowledge/patterns/be/typing.md +73 -0
- package/knowledge/patterns/be/typing.vi.md +73 -0
- package/knowledge/patterns/fe/INDEX.md +29 -0
- package/knowledge/patterns/fe/INDEX.vi.md +29 -0
- package/knowledge/patterns/fe/comment.md +70 -0
- package/knowledge/patterns/fe/comment.vi.md +70 -0
- package/knowledge/patterns/fe/error.md +61 -0
- package/knowledge/patterns/fe/error.vi.md +61 -0
- package/knowledge/patterns/fe/folder.md +98 -0
- package/knowledge/patterns/fe/folder.vi.md +95 -0
- package/knowledge/patterns/fe/function.md +70 -0
- package/knowledge/patterns/fe/function.vi.md +70 -0
- package/knowledge/patterns/fe/imports.md +87 -0
- package/knowledge/patterns/fe/imports.vi.md +87 -0
- package/knowledge/patterns/fe/naming.md +77 -0
- package/knowledge/patterns/fe/naming.vi.md +77 -0
- package/knowledge/patterns/fe/test.md +73 -0
- package/knowledge/patterns/fe/test.vi.md +73 -0
- package/knowledge/patterns/fe/typing.md +67 -0
- package/knowledge/patterns/fe/typing.vi.md +67 -0
- package/knowledge/ui/INDEX.md +101 -0
- package/knowledge/ui/INDEX.vi.md +101 -0
- package/knowledge/ui/composition/INDEX.md +60 -0
- package/knowledge/ui/composition/INDEX.vi.md +63 -0
- package/knowledge/ui/composition/accent.md +73 -0
- package/knowledge/ui/composition/accent.vi.md +74 -0
- package/knowledge/ui/composition/action.md +55 -0
- package/knowledge/ui/composition/action.vi.md +56 -0
- package/knowledge/ui/composition/coverage.md +37 -0
- package/knowledge/ui/composition/coverage.vi.md +37 -0
- package/knowledge/ui/composition/cta.md +79 -0
- package/knowledge/ui/composition/cta.vi.md +79 -0
- package/knowledge/ui/composition/feedback.md +55 -0
- package/knowledge/ui/composition/feedback.vi.md +56 -0
- package/knowledge/ui/composition/hierarchy.md +83 -0
- package/knowledge/ui/composition/hierarchy.vi.md +82 -0
- package/knowledge/ui/composition/layout.md +91 -0
- package/knowledge/ui/composition/layout.vi.md +93 -0
- package/knowledge/ui/composition/responsive.md +67 -0
- package/knowledge/ui/composition/responsive.vi.md +67 -0
- package/knowledge/ui/composition/state.md +105 -0
- package/knowledge/ui/composition/state.vi.md +106 -0
- package/knowledge/ui/presentation/INDEX.md +94 -0
- package/knowledge/ui/presentation/INDEX.vi.md +69 -0
- package/knowledge/ui/presentation/boundary.md +170 -0
- package/knowledge/ui/presentation/boundary.vi.md +169 -0
- package/knowledge/ui/presentation/font.md +155 -0
- package/knowledge/ui/presentation/font.vi.md +156 -0
- package/knowledge/ui/presentation/gap.md +181 -0
- package/knowledge/ui/presentation/gap.vi.md +181 -0
- package/knowledge/ui/presentation/margin.md +168 -0
- package/knowledge/ui/presentation/margin.vi.md +171 -0
- package/knowledge/ui/presentation/measure.md +174 -0
- package/knowledge/ui/presentation/measure.vi.md +178 -0
- package/knowledge/ui/presentation/overflow.md +142 -0
- package/knowledge/ui/presentation/overflow.vi.md +142 -0
- package/knowledge/ui/presentation/padding.md +293 -0
- package/knowledge/ui/presentation/padding.vi.md +292 -0
- package/knowledge/ui/presentation/surface.md +166 -0
- package/knowledge/ui/presentation/surface.vi.md +166 -0
- package/knowledge/ui/presentation/text-flow.md +123 -0
- package/knowledge/ui/presentation/text-flow.vi.md +123 -0
- package/knowledge/ui/presentation/tone.md +114 -0
- package/knowledge/ui/presentation/tone.vi.md +113 -0
- package/knowledge/ui/proof/INDEX.md +55 -0
- package/knowledge/ui/proof/INDEX.vi.md +55 -0
- package/knowledge/ui/proof/accessibility.md +67 -0
- package/knowledge/ui/proof/accessibility.vi.md +68 -0
- package/knowledge/ui/proof/contrast.md +58 -0
- package/knowledge/ui/proof/contrast.vi.md +57 -0
- package/knowledge/ui/proof/focus.md +76 -0
- package/knowledge/ui/proof/focus.vi.md +76 -0
- package/knowledge/ui/proof/motion.md +63 -0
- package/knowledge/ui/proof/motion.vi.md +64 -0
- package/knowledge/ui/proof/render-truth.md +63 -0
- package/knowledge/ui/proof/render-truth.vi.md +63 -0
- package/operators/INDEX.md +199 -0
- package/operators/INDEX.vi.md +199 -0
- package/operators/architecture-decide/errors.json +115 -0
- package/operators/architecture-decide/operator.json +20 -0
- package/operators/architecture-decide/operator.md +133 -0
- package/operators/architecture-decide/operator.vi.md +130 -0
- package/operators/architecture-decide/self-test.mjs +221 -0
- package/operators/architecture-decide/validate.mjs +112 -0
- package/operators/backend-source-apply/errors.json +66 -0
- package/operators/backend-source-apply/operator.json +20 -0
- package/operators/backend-source-apply/operator.md +189 -0
- package/operators/backend-source-apply/operator.vi.md +185 -0
- package/operators/backend-source-apply/self-test.mjs +257 -0
- package/operators/backend-source-apply/validate.mjs +240 -0
- package/operators/business-decide/errors.json +78 -0
- package/operators/business-decide/operator.json +20 -0
- package/operators/business-decide/operator.md +176 -0
- package/operators/business-decide/operator.vi.md +174 -0
- package/operators/business-decide/self-test.mjs +225 -0
- package/operators/business-decide/validate.mjs +277 -0
- package/operators/content-generate/errors.json +106 -0
- package/operators/content-generate/operator.json +21 -0
- package/operators/content-generate/operator.md +155 -0
- package/operators/content-generate/operator.vi.md +155 -0
- package/operators/content-generate/self-test.mjs +288 -0
- package/operators/content-generate/validate.mjs +196 -0
- package/operators/errors.json +178 -0
- package/operators/frontend-direction-decide/errors.json +135 -0
- package/operators/frontend-direction-decide/operator.json +21 -0
- package/operators/frontend-direction-decide/operator.md +167 -0
- package/operators/frontend-direction-decide/operator.vi.md +167 -0
- package/operators/frontend-direction-decide/self-test.mjs +226 -0
- package/operators/frontend-direction-decide/validate.mjs +146 -0
- package/operators/frontend-presentation-resolve/errors.json +42 -0
- package/operators/frontend-presentation-resolve/operator.json +19 -0
- package/operators/frontend-presentation-resolve/operator.md +142 -0
- package/operators/frontend-presentation-resolve/operator.vi.md +140 -0
- package/operators/frontend-presentation-resolve/self-test.mjs +163 -0
- package/operators/frontend-presentation-resolve/validate.mjs +143 -0
- package/operators/frontend-source-apply/errors.json +30 -0
- package/operators/frontend-source-apply/operator.json +20 -0
- package/operators/frontend-source-apply/operator.md +127 -0
- package/operators/frontend-source-apply/operator.vi.md +126 -0
- package/operators/frontend-source-apply/self-test.mjs +214 -0
- package/operators/frontend-source-apply/validate.mjs +133 -0
- package/operators/frontend-surface-audit/errors.json +5 -0
- package/operators/frontend-surface-audit/operator.json +22 -0
- package/operators/frontend-surface-audit/operator.md +121 -0
- package/operators/frontend-surface-audit/operator.vi.md +121 -0
- package/operators/frontend-surface-audit/self-test.mjs +185 -0
- package/operators/frontend-surface-audit/validate.mjs +127 -0
- package/operators/git-publish/errors.json +54 -0
- package/operators/git-publish/operator.json +20 -0
- package/operators/git-publish/operator.md +180 -0
- package/operators/git-publish/operator.vi.md +178 -0
- package/operators/git-publish/self-test.mjs +177 -0
- package/operators/git-publish/validate.mjs +106 -0
- package/operators/platform-operate/errors.json +90 -0
- package/operators/platform-operate/operator.json +22 -0
- package/operators/platform-operate/operator.md +162 -0
- package/operators/platform-operate/operator.vi.md +160 -0
- package/operators/platform-operate/self-test.mjs +202 -0
- package/operators/platform-operate/validate.mjs +193 -0
- package/operators/quality-verify/errors.json +54 -0
- package/operators/quality-verify/operator.json +20 -0
- package/operators/quality-verify/operator.md +185 -0
- package/operators/quality-verify/operator.vi.md +177 -0
- package/operators/quality-verify/self-test.mjs +210 -0
- package/operators/quality-verify/validate.mjs +197 -0
- package/operators/release-deploy/errors.json +158 -0
- package/operators/release-deploy/operator.json +23 -0
- package/operators/release-deploy/operator.md +183 -0
- package/operators/release-deploy/operator.vi.md +181 -0
- package/operators/release-deploy/self-test.mjs +235 -0
- package/operators/release-deploy/validate.mjs +129 -0
- package/operators/uat-verify/errors.json +78 -0
- package/operators/uat-verify/operator.json +25 -0
- package/operators/uat-verify/operator.md +162 -0
- package/operators/uat-verify/operator.vi.md +161 -0
- package/operators/uat-verify/self-test.mjs +270 -0
- package/operators/uat-verify/validate.mjs +202 -0
- package/operators/workspace-bind/errors.json +90 -0
- package/operators/workspace-bind/operator.json +21 -0
- package/operators/workspace-bind/operator.md +148 -0
- package/operators/workspace-bind/operator.vi.md +148 -0
- package/operators/workspace-bind/self-test.mjs +205 -0
- package/operators/workspace-bind/validate.mjs +152 -0
- package/package.json +50 -0
- package/readiness/initialization/workspaces/commit-policy.json +91 -0
- package/readiness/initialization/workspaces/config.schema.json +35 -0
- package/readiness/initialization/workspaces/device-state.schema.json +54 -0
- package/readiness/initialization/workspaces/local-route.schema.json +206 -0
- package/readiness/initialization/workspaces/portable-route.schema.json +200 -0
- package/resources/INDEX.md +96 -0
- package/resources/INDEX.vi.md +99 -0
- package/resources/agents/profiles/claude.json +131 -0
- package/resources/agents/profiles/openai.json +131 -0
- package/resources/orchestrator.json +71 -0
- package/resources/tools.json +85 -0
- package/routing.json +272 -0
- package/scripts/alias-registry.mjs +31 -0
- package/scripts/device-state.mjs +497 -0
- package/scripts/device-state.spec.mjs +18 -0
- package/scripts/errors-registry.mjs +68 -0
- package/scripts/generate-alias-doc.mjs +66 -0
- package/scripts/generate-grammar-dna.mjs +387 -0
- package/scripts/generate-operators-index.mjs +95 -0
- package/scripts/generate-presentation-owned.mjs +681 -0
- package/scripts/install-cli.spec.mjs +74 -0
- package/scripts/json-schema.mjs +94 -0
- package/scripts/operator-md.mjs +96 -0
- package/scripts/run-operator-self-tests.mjs +36 -0
- package/scripts/validate-alias.mjs +165 -0
- package/scripts/validate-defaults.mjs +72 -0
- package/scripts/validate-knowledge-citations.mjs +90 -0
- package/scripts/validate-operator.mjs +125 -0
- package/scripts/validate-request.mjs +80 -0
- package/scripts/validate-resources.mjs +117 -0
- package/scripts/validate-response.mjs +141 -0
- package/scripts/validate-routing.mjs +91 -0
- package/scripts/validate-step.mjs +50 -0
- package/scripts/validate-templates.mjs +226 -0
- package/scripts/validate-templates.spec.mjs +144 -0
- package/scripts/validate-workflows.mjs +106 -0
- package/scripts/workspace-portable.mjs +389 -0
- package/scripts/workspace-portable.spec.mjs +246 -0
- package/templates/README.md +47 -0
- package/templates/README.vi.md +45 -0
- package/templates/changes.example.md +27 -0
- package/templates/grammars.template.md +34 -0
- package/templates/kinds/architecture-decision.contract.json +14 -0
- package/templates/kinds/architecture-decision.skeleton.md +52 -0
- package/templates/kinds/backend-source-application.contract.json +10 -0
- package/templates/kinds/backend-source-application.skeleton.md +34 -0
- package/templates/kinds/business-promise-authority.contract.json +13 -0
- package/templates/kinds/business-promise-authority.skeleton.md +67 -0
- package/templates/kinds/capture.schema.json +33 -0
- package/templates/kinds/changes.contract.json +10 -0
- package/templates/kinds/changes.skeleton.md +26 -0
- package/templates/kinds/checks.schema.json +44 -0
- package/templates/kinds/claims.schema.json +165 -0
- package/templates/kinds/conformance.schema.json +15 -0
- package/templates/kinds/content-brief.contract.json +12 -0
- package/templates/kinds/content-brief.skeleton.md +45 -0
- package/templates/kinds/content-generation-receipt.contract.json +11 -0
- package/templates/kinds/content-generation-receipt.skeleton.md +40 -0
- package/templates/kinds/content-review.contract.json +11 -0
- package/templates/kinds/content-review.skeleton.md +45 -0
- package/templates/kinds/contract.schema.json +28 -0
- package/templates/kinds/coverage-matrix.schema.json +86 -0
- package/templates/kinds/coverage.schema.json +62 -0
- package/templates/kinds/current-state.schema.json +40 -0
- package/templates/kinds/delta.schema.json +95 -0
- package/templates/kinds/e2e.schema.json +63 -0
- package/templates/kinds/frontend-direction-decision.contract.json +93 -0
- package/templates/kinds/frontend-direction-decision.skeleton.md +68 -0
- package/templates/kinds/frontend-presentation-resolution.contract.json +11 -0
- package/templates/kinds/frontend-presentation-resolution.skeleton.md +32 -0
- package/templates/kinds/frontend-source-application.contract.json +10 -0
- package/templates/kinds/frontend-source-application.skeleton.md +30 -0
- package/templates/kinds/frontend-surface-audit.contract.json +52 -0
- package/templates/kinds/frontend-surface-audit.skeleton.md +32 -0
- package/templates/kinds/gate-result.schema.json +64 -0
- package/templates/kinds/git-publication.contract.json +82 -0
- package/templates/kinds/git-publication.skeleton.md +61 -0
- package/templates/kinds/independent-critique.contract.json +9 -0
- package/templates/kinds/independent-critique.skeleton.md +28 -0
- package/templates/kinds/inventory.schema.json +35 -0
- package/templates/kinds/model.schema.json +99 -0
- package/templates/kinds/mutations.schema.json +65 -0
- package/templates/kinds/platform-operation-receipt.contract.json +76 -0
- package/templates/kinds/platform-operation-receipt.skeleton.md +54 -0
- package/templates/kinds/probes.schema.json +130 -0
- package/templates/kinds/proof.schema.json +17 -0
- package/templates/kinds/quality-verification.contract.json +87 -0
- package/templates/kinds/quality-verification.skeleton.md +58 -0
- package/templates/kinds/release-deployment.contract.json +85 -0
- package/templates/kinds/release-deployment.skeleton.md +67 -0
- package/templates/kinds/route.schema.json +293 -0
- package/templates/kinds/stack-model.schema.json +90 -0
- package/templates/kinds/uat-capture.schema.json +35 -0
- package/templates/kinds/uat-flow-verification.contract.json +11 -0
- package/templates/kinds/uat-flow-verification.skeleton.md +47 -0
- package/templates/kinds/uat-snapshot.schema.json +96 -0
- package/templates/kinds/uat-verdicts.schema.json +41 -0
- package/templates/kinds/ui-coverage.schema.json +77 -0
- package/templates/kinds/verdicts.schema.json +39 -0
- package/templates/kinds/workspace-route-binding.contract.json +12 -0
- package/templates/kinds/workspace-route-binding.skeleton.md +60 -0
- package/templates/kinds/writes.schema.json +37 -0
- package/templates/operator.template.md +73 -0
- package/templates/patterns.template.md +31 -0
- package/templates/step/request.schema.json +42 -0
- package/templates/step/response.schema.json +144 -0
- package/templates/ui-composition.template.md +36 -0
- package/templates/ui-presentation.template.md +57 -0
- package/templates/ui-proof.template.md +34 -0
- package/workflows/README.md +37 -0
- package/workflows/README.vi.md +37 -0
- package/workflows/backend-feature.json +59 -0
- package/workflows/content-unit.json +19 -0
- package/workflows/frontend-new-surface.json +81 -0
- package/workflows/frontend-reconstruct.json +67 -0
- package/workflows/frontend-refine.json +67 -0
- package/workflows/frontend-with-uat.json +78 -0
- package/workflows/full-feature.json +104 -0
- package/workflows/release.json +29 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// Proves validate.mjs on a synthetic session branch: one conforming application on the session branch
|
|
2
|
+
// (one file modified, one application-owned leaf created, one path unchanged), one dry run that
|
|
3
|
+
// commits nothing, one blocked on WRITE_REJECTED with nothing written, and one mutation per law, each
|
|
4
|
+
// of which must fail with a line that names the defect.
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { validateApplicationStep } from './validate.mjs';
|
|
10
|
+
|
|
11
|
+
const PAGE = 'app/plans/page.tsx';
|
|
12
|
+
const CANVAS = 'app/plans/canvas.tsx';
|
|
13
|
+
const LEGEND = 'app/plans/legend.tsx';
|
|
14
|
+
const BASE = '0f1e2d3c4b5a69788796a5b4c3d2e1f009182736';
|
|
15
|
+
const COMMIT = '9a8b7c6d5e4f30211203344556677889900aabbc';
|
|
16
|
+
const SESSION = 's-test';
|
|
17
|
+
const fp = (c) => `sha256:${c.repeat(64)}`;
|
|
18
|
+
|
|
19
|
+
const inventory = () => ({
|
|
20
|
+
treeFingerprint: fp('a'),
|
|
21
|
+
classNames: ['gap-6', 'gap-4'],
|
|
22
|
+
ruleIds: ['GAP-5', 'GAP-4'],
|
|
23
|
+
gaps: [],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const plan = ({ mode = 'apply', commit = COMMIT } = {}) => ({
|
|
27
|
+
mode,
|
|
28
|
+
base: BASE,
|
|
29
|
+
branch: `session/${SESSION}`,
|
|
30
|
+
commit,
|
|
31
|
+
files: [
|
|
32
|
+
{ path: PAGE, change: 'modified', before: fp('b'), after: fp('c'), classes: ['gap-6'] },
|
|
33
|
+
{ path: CANVAS, change: 'created', before: null, after: fp('d'), classes: [] },
|
|
34
|
+
{ path: LEGEND, change: 'unchanged', before: fp('e'), after: fp('e'), classes: [] },
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const dryPlan = () => ({
|
|
39
|
+
mode: 'dry',
|
|
40
|
+
base: BASE,
|
|
41
|
+
branch: `session/${SESSION}`,
|
|
42
|
+
commit: null,
|
|
43
|
+
files: [
|
|
44
|
+
{ path: PAGE, change: 'modified', before: fp('b'), after: null, classes: ['gap-6'] },
|
|
45
|
+
{ path: CANVAS, change: 'created', before: null, after: null, classes: [] },
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const responseMd = ({ mode = 'apply', commit = COMMIT, files } = {}) => {
|
|
50
|
+
const rows = (files ?? plan().files).map(({ path: p, change, classes }) => `| \`${p}\` | ${change} | ${classes.length ? `\`${classes[0]}\`` : '—'} | ${classes.length ? '`GAP-5`' : '—'} | ${change === 'created' ? 'an application-owned leaf carrying its contract' : 'the resolved value for this node'} |`);
|
|
51
|
+
return `# frontend-source-application — plan-picker
|
|
52
|
+
|
|
53
|
+
## Binding
|
|
54
|
+
|
|
55
|
+
| Field | Value |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| Target | \`plan-picker\` |
|
|
58
|
+
| Mode | \`${mode}\` |
|
|
59
|
+
| Branch | \`session/${SESSION}\` |
|
|
60
|
+
| Base | \`${BASE}\` |
|
|
61
|
+
| Commit | ${commit ? `\`${commit}\`` : '—'} |
|
|
62
|
+
|
|
63
|
+
## Projection
|
|
64
|
+
|
|
65
|
+
| Path | Change | Classes | Claims | Why |
|
|
66
|
+
| --- | --- | --- | --- | --- |
|
|
67
|
+
${rows.join('\n')}
|
|
68
|
+
|
|
69
|
+
## Rejections
|
|
70
|
+
|
|
71
|
+
| Path | Value | Because |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
|
|
74
|
+
## Fallbacks taken
|
|
75
|
+
|
|
76
|
+
| Code | Action |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
`;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const changesMd = ({ files } = {}) => {
|
|
82
|
+
const rows = (files ?? plan().files).map(({ path: p, change, classes }) => `| \`${p}\` | ${change} | ${change === 'created' ? 'the application-owned leaf the direction marked' : 'the resolved value for this node'} | ${classes.length ? '`GAP-5`' : '—'} |`);
|
|
83
|
+
return `# changes — frontend.source.apply step-3/parallel-1
|
|
84
|
+
|
|
85
|
+
The plans page and its application-owned canvas carry the resolved spacing on the session branch.
|
|
86
|
+
|
|
87
|
+
## Binding
|
|
88
|
+
|
|
89
|
+
| Field | Value |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| Operator | \`frontend.source.apply\` |
|
|
92
|
+
| Step | \`step-3/parallel-1\` |
|
|
93
|
+
| Checkout | \`@workspaces/fe\` at \`${BASE}\` → \`${COMMIT}\` on \`session/${SESSION}\` |
|
|
94
|
+
| Predecessor | \`step-2/parallel-1/response/response.md\` |
|
|
95
|
+
|
|
96
|
+
## Files
|
|
97
|
+
|
|
98
|
+
| Path | Change | Why | Claims |
|
|
99
|
+
| --- | --- | --- | --- |
|
|
100
|
+
${rows.join('\n')}
|
|
101
|
+
|
|
102
|
+
## What the next step must know
|
|
103
|
+
|
|
104
|
+
- Gates to run: the lint and type gates the checkout pins for these paths.
|
|
105
|
+
- Surfaces to observe: the /plans route at both branches.
|
|
106
|
+
- Not changed on purpose: the legend, because the resolution decided nothing about it.
|
|
107
|
+
`;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const requestJson = ({ extra = {} } = {}) => ({
|
|
111
|
+
schemaVersion: 9,
|
|
112
|
+
operatorId: 'frontend.source.apply',
|
|
113
|
+
step: 3,
|
|
114
|
+
parallel: 1,
|
|
115
|
+
sessionId: SESSION,
|
|
116
|
+
contexts: [{ alias: '@workspaces/fe', head: BASE }],
|
|
117
|
+
requirements: { mode: 'apply', resume: null, ...extra },
|
|
118
|
+
inputs: {
|
|
119
|
+
'frontend-presentation-resolution': 'step-2/parallel-1/response/response.md',
|
|
120
|
+
'frontend-direction-decision': 'step-1/parallel-1/response/response.md',
|
|
121
|
+
},
|
|
122
|
+
resume: null,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const responseJson = ({ status = 'done', stop, fields, commits = [COMMIT], next = ['frontend.surface.audit'] } = {}) => ({
|
|
126
|
+
schemaVersion: 9,
|
|
127
|
+
operatorId: 'frontend.source.apply',
|
|
128
|
+
step: 3,
|
|
129
|
+
parallel: 1,
|
|
130
|
+
status,
|
|
131
|
+
...(stop ? { stop } : {}),
|
|
132
|
+
fallbacks: [],
|
|
133
|
+
fields: fields ?? { 'frontend-source-application': 'response/response.md', changes: 'response/changes.md', writes: 'response/data/writes.json' },
|
|
134
|
+
commits,
|
|
135
|
+
next,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
function writeBranch(files, { inventoryDoc = inventory(), withInventory = true } = {}) {
|
|
139
|
+
const session = mkdtempSync(path.join(tmpdir(), 'fe-apply-session-'));
|
|
140
|
+
const branch = path.join(session, 'step-3', 'parallel-1');
|
|
141
|
+
for (const d of ['request', 'response/data']) mkdirSync(path.join(branch, d), { recursive: true });
|
|
142
|
+
const upstream = path.join(session, 'step-2', 'parallel-1', 'response', 'data');
|
|
143
|
+
mkdirSync(upstream, { recursive: true });
|
|
144
|
+
mkdirSync(path.join(session, 'step-1', 'parallel-1', 'response'), { recursive: true });
|
|
145
|
+
writeFileSync(path.join(session, 'step-1', 'parallel-1', 'response', 'response.md'), '# frontend-direction-decision — plan-picker\n');
|
|
146
|
+
writeFileSync(path.join(session, 'step-2', 'parallel-1', 'response', 'response.md'), '# frontend-presentation-resolution — plan-picker\n');
|
|
147
|
+
if (withInventory) writeFileSync(path.join(upstream, 'inventory.json'), JSON.stringify(inventoryDoc, null, 2));
|
|
148
|
+
writeFileSync(path.join(session, 'state.json'), JSON.stringify({ id: SESSION, chain: [['2/1'], ['3/1']], steps: { '2/1': 'frontend.presentation.resolve', '3/1': 'frontend.source.apply' }, current: '3/1', status: 'running' }));
|
|
149
|
+
for (const [name, content] of Object.entries(files)) {
|
|
150
|
+
if (content === null) continue;
|
|
151
|
+
writeFileSync(path.join(branch, name), typeof content === 'string' ? content : JSON.stringify(content, null, 2));
|
|
152
|
+
}
|
|
153
|
+
return { branch, session };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const baseline = () => ({
|
|
157
|
+
'request/request.json': requestJson(),
|
|
158
|
+
'response/response.json': responseJson(),
|
|
159
|
+
'response/response.md': responseMd(),
|
|
160
|
+
'response/changes.md': changesMd(),
|
|
161
|
+
'response/data/writes.json': plan(),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const dryRun = () => ({
|
|
165
|
+
'request/request.json': requestJson({ extra: { mode: 'dry' } }),
|
|
166
|
+
'response/response.json': responseJson({ commits: [] }),
|
|
167
|
+
'response/response.md': responseMd({ mode: 'dry', commit: null, files: dryPlan().files }),
|
|
168
|
+
'response/changes.md': changesMd({ files: dryPlan().files }),
|
|
169
|
+
'response/data/writes.json': dryPlan(),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
async function expectValid(files, label, options) {
|
|
173
|
+
const { branch, session } = writeBranch(files, options);
|
|
174
|
+
const { errors } = await validateApplicationStep(branch);
|
|
175
|
+
rmSync(session, { recursive: true, force: true });
|
|
176
|
+
assert.deepEqual(errors, [], `${label} should be valid`);
|
|
177
|
+
}
|
|
178
|
+
async function expectError(files, needle, label, options) {
|
|
179
|
+
const { branch, session } = writeBranch(files, options);
|
|
180
|
+
const { errors } = await validateApplicationStep(branch);
|
|
181
|
+
rmSync(session, { recursive: true, force: true });
|
|
182
|
+
assert.ok(errors.some((e) => e.includes(needle)), `${label}: expected an error containing "${needle}", got:\n${errors.join('\n') || '(none)'}`);
|
|
183
|
+
}
|
|
184
|
+
const mutate = (change) => { const p = plan(); change(p); return { ...baseline(), 'response/data/writes.json': p }; };
|
|
185
|
+
|
|
186
|
+
await expectValid(baseline(), 'one modified path, one application-owned leaf created, one unchanged');
|
|
187
|
+
await expectValid(dryRun(), 'a dry run that commits nothing');
|
|
188
|
+
await expectValid({ 'request/request.json': requestJson(), 'response/response.json': responseJson({ status: 'blocked', stop: 'WRITE_REJECTED', commits: [], next: [], fields: {} }) }, 'blocked on WRITE_REJECTED with nothing written');
|
|
189
|
+
|
|
190
|
+
await expectError({ ...baseline(), 'request/request.json': requestJson({ extra: { mystery: 1 } }) }, 'requirements.mystery is not a field', 'undeclared requirement');
|
|
191
|
+
await expectError({ ...baseline(), 'request/request.json': requestJson({ extra: { mode: 'dry' } }) }, "mode apply differs from the request's dry", 'mode re-decided in the plan');
|
|
192
|
+
await expectError(mutate((p) => { p.files[0].classes = ['gap-8']; }), 'writes class gap-8, which the resolution never published', 'class the resolution never published');
|
|
193
|
+
await expectError(mutate((p) => { p.branch = 'mtp'; }), 'only session/<sessionId> may be committed to', 'write on the person\'s branch');
|
|
194
|
+
await expectError(mutate((p) => { p.branch = 'session/other'; }), 'is not the session branch of', 'write on another session branch');
|
|
195
|
+
await expectError(mutate((p) => { p.commit = null; }), 'commits the declared write set exactly once', 'applied run without a commit');
|
|
196
|
+
await expectError({ ...baseline(), 'response/response.json': responseJson({ commits: [COMMIT, BASE] }) }, 'records exactly one commit, found 2', 'two commits for one write set');
|
|
197
|
+
await expectError({ ...baseline(), 'response/response.json': responseJson({ commits: [BASE] }) }, 'is not the commit', 'response and plan disagree on the commit');
|
|
198
|
+
await expectError({ ...dryRun(), 'response/response.json': responseJson({ commits: [COMMIT] }) }, 'a dry run records no commit', 'dry run that committed');
|
|
199
|
+
await expectError({ ...dryRun(), 'response/data/writes.json': { ...dryPlan(), commit: COMMIT } }, 'a dry run commits nothing, so commit must be null', 'dry plan carrying a commit');
|
|
200
|
+
await expectError(mutate((p) => { p.files = p.files.map((f) => ({ ...f, change: 'unchanged', before: f.after ?? fp('e'), after: f.after ?? fp('e'), classes: [] })); }), 'moves at least one declared path', 'nothing moved');
|
|
201
|
+
await expectError(mutate((p) => { p.files[0].after = p.files[0].before; }), 'reports a modification with an unchanged hash', 'modification that did not move');
|
|
202
|
+
await expectError(mutate((p) => { p.files[1].before = fp('f'); }), 'was created but reports a prior hash', 'creation with a prior hash');
|
|
203
|
+
await expectError(mutate((p) => { p.files[2].classes = ['gap-6']; }), 'is reported unchanged while carrying classes', 'unchanged file carrying classes');
|
|
204
|
+
await expectError(mutate((p) => { p.files.push({ ...p.files[0] }); }), 'appears twice in the plan', 'one path planned twice');
|
|
205
|
+
await expectError({ ...baseline(), 'response/response.md': responseMd({ commit: BASE }) }, 'Commit', 'receipt and plan disagree on the commit');
|
|
206
|
+
await expectError({ ...baseline(), 'response/changes.md': changesMd().replace(`| \`${LEGEND}\` | unchanged | the resolved value for this node | — |\n`, '') }, 'Files lists 2 paths, the plan carries 3', 'changes.md hides a planned path');
|
|
207
|
+
await expectError({ ...baseline(), 'response/changes.md': changesMd().replace('| modified |', '| deleted |') }, 'is deleted here and modified in the plan', 'changes.md contradicts the plan');
|
|
208
|
+
await expectError({ ...baseline(), 'response/changes.md': changesMd().replace(` on \`session/${SESSION}\``, '') }, 'does not name the session branch', 'changes.md hides the session branch');
|
|
209
|
+
await expectError(baseline(), 'the resolution inventory could not be read', 'inventory missing beside the receipt', { withInventory: false });
|
|
210
|
+
await expectError({ ...baseline(), 'response/response.md': responseMd().replace('## Rejections', '## Rejected') }, 'missing section ^## Rejections$', 'receipt section renamed');
|
|
211
|
+
await expectError({ ...baseline(), 'response/data/writes.json': null, 'response/response.json': responseJson({ fields: { 'frontend-source-application': 'response/response.md', changes: 'response/changes.md' } }) }, 'required output writes is not in fields', 'missing required output');
|
|
212
|
+
await expectError({ ...baseline(), 'response/response.json': responseJson({ status: 'blocked', stop: 'MADE_UP_CODE', commits: [], next: [] }) }, 'not a registered code', 'unknown stop code');
|
|
213
|
+
|
|
214
|
+
process.stdout.write('frontend.source.apply self-test: 3 valid branches, 22 rejected mutations\n');
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// frontend.source.apply's own law over one branch, on top of the shared step check: every class in every
|
|
2
|
+
// written file comes from the bound resolution's inventory, which is read back beside the resolution
|
|
3
|
+
// receipt the request named; the write lands on the session branch and nowhere else; a dry run
|
|
4
|
+
// commits nothing and an applied run commits exactly once; created, modified and unchanged agree with
|
|
5
|
+
// the hashes; and the receipt and changes.md list exactly the files the plan carries.
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { readFile } from 'node:fs/promises';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import process from 'node:process';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { validateStep } from '../../scripts/validate-step.mjs';
|
|
12
|
+
import { tableUnder } from '../../scripts/validate-response.mjs';
|
|
13
|
+
import { sessionRootOf } from '../../scripts/validate-request.mjs';
|
|
14
|
+
|
|
15
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
16
|
+
const empty = (v) => v === undefined || v === null || v === '' || v === '—';
|
|
17
|
+
|
|
18
|
+
// The inventory the resolution froze sits beside its receipt: <branch>/response/data/inventory.json.
|
|
19
|
+
export function inventoryPathFor(resolutionRef) {
|
|
20
|
+
return resolutionRef.replace(/response\/[a-z-]+\.md$/, 'response/data/inventory.json');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function validateApplicationStep(branchDir, root = ROOT) {
|
|
24
|
+
const base = await validateStep(root, branchDir);
|
|
25
|
+
const errors = [...base.errors];
|
|
26
|
+
const { request, response, requirements = {}, present = new Set() } = base;
|
|
27
|
+
if (!response || response.operatorId !== 'frontend.source.apply') return { errors };
|
|
28
|
+
const has = (f) => existsSync(path.join(branchDir, f));
|
|
29
|
+
const read = (f) => readFile(path.join(branchDir, f), 'utf8');
|
|
30
|
+
const mode = requirements.mode ?? 'apply';
|
|
31
|
+
|
|
32
|
+
let plan = null;
|
|
33
|
+
if (present.has('writes') && has('response/data/writes.json')) {
|
|
34
|
+
try { plan = JSON.parse(await read('response/data/writes.json')); } catch { plan = null; }
|
|
35
|
+
}
|
|
36
|
+
if (!plan) {
|
|
37
|
+
if (response.status === 'done') errors.push('response/data/writes.json: a done branch needs the write plan');
|
|
38
|
+
return { errors };
|
|
39
|
+
}
|
|
40
|
+
const at = 'response/data/writes.json';
|
|
41
|
+
|
|
42
|
+
if (plan.mode !== mode) errors.push(`${at}: mode ${plan.mode} differs from the request's ${mode}`);
|
|
43
|
+
if (!plan.branch.startsWith('session/')) errors.push(`${at}: the write lands on ${plan.branch}; only session/<sessionId> may be committed to`);
|
|
44
|
+
if (request?.sessionId && plan.branch !== `session/${request.sessionId}`) errors.push(`${at}: branch ${plan.branch} is not the session branch of ${request.sessionId}`);
|
|
45
|
+
|
|
46
|
+
const commits = response.commits ?? [];
|
|
47
|
+
if (mode === 'dry') {
|
|
48
|
+
if (plan.commit !== null) errors.push(`${at}: a dry run commits nothing, so commit must be null`);
|
|
49
|
+
if (commits.length) errors.push('response/response.json: a dry run records no commit');
|
|
50
|
+
for (const file of plan.files) if (file.change !== 'unchanged' && file.after !== null && file.before !== file.after) {
|
|
51
|
+
errors.push(`${at}: ${file.path} reports a change under a dry run, which writes nothing`);
|
|
52
|
+
}
|
|
53
|
+
} else if (response.status === 'done') {
|
|
54
|
+
if (plan.commit === null) errors.push(`${at}: an applied branch commits the declared write set exactly once`);
|
|
55
|
+
if (commits.length !== 1) errors.push(`response/response.json: an applied branch records exactly one commit, found ${commits.length}`);
|
|
56
|
+
else if (plan.commit !== null && commits[0] !== plan.commit) errors.push(`response/response.json: commits[0] ${commits[0]} is not the commit ${plan.commit} the plan recorded`);
|
|
57
|
+
if (!plan.files.some((f) => f.change !== 'unchanged')) errors.push(`${at}: an applied branch moves at least one declared path (NO_PROGRESS otherwise)`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Every value that entered source came from the resolution; there is no other source of one.
|
|
61
|
+
const sessionRoot = sessionRootOf(branchDir);
|
|
62
|
+
const resolutionRef = request?.inputs?.['frontend-presentation-resolution'];
|
|
63
|
+
let inventory = null;
|
|
64
|
+
if (sessionRoot && resolutionRef) {
|
|
65
|
+
const full = path.join(sessionRoot, inventoryPathFor(resolutionRef));
|
|
66
|
+
if (existsSync(full)) { try { inventory = JSON.parse(await readFile(full, 'utf8')); } catch { inventory = null; } }
|
|
67
|
+
}
|
|
68
|
+
if (inventory) {
|
|
69
|
+
const published = new Set(inventory.classNames);
|
|
70
|
+
for (const file of plan.files) {
|
|
71
|
+
for (const className of file.classes) {
|
|
72
|
+
if (!published.has(className)) errors.push(`${at}: ${file.path} writes class ${className}, which the resolution never published (WRITE_REJECTED)`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} else if (response.status === 'done') errors.push(`${at}: the resolution inventory could not be read beside the receipt the request bound (RESOLUTION_STALE)`);
|
|
76
|
+
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
for (const file of plan.files) {
|
|
79
|
+
if (seen.has(file.path)) errors.push(`${at}: path ${file.path} appears twice in the plan`);
|
|
80
|
+
seen.add(file.path);
|
|
81
|
+
if (file.change === 'created' && file.before !== null) errors.push(`${at}: ${file.path} was created but reports a prior hash`);
|
|
82
|
+
if (file.change === 'modified') {
|
|
83
|
+
if (file.before === null) errors.push(`${at}: ${file.path} was modified but reports no prior hash`);
|
|
84
|
+
else if (file.before === file.after) errors.push(`${at}: ${file.path} reports a modification with an unchanged hash`);
|
|
85
|
+
}
|
|
86
|
+
if (file.change === 'unchanged') {
|
|
87
|
+
if (file.before !== file.after) errors.push(`${at}: ${file.path} is reported unchanged with a different hash`);
|
|
88
|
+
if (file.classes.length) errors.push(`${at}: ${file.path} is reported unchanged while carrying classes`);
|
|
89
|
+
}
|
|
90
|
+
if (file.change === 'deleted' && file.after !== null) errors.push(`${at}: ${file.path} was deleted and still reports a later hash`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (present.has('frontend-source-application') && has('response/response.md')) {
|
|
94
|
+
const text = await read('response/response.md');
|
|
95
|
+
const rel = 'response/response.md';
|
|
96
|
+
const binding = Object.fromEntries((tableUnder(text, '## Binding') ?? []).map(([k, v]) => [k, v]));
|
|
97
|
+
if (binding.Mode !== mode) errors.push(`${rel}: Mode ${binding.Mode} differs from the request's ${mode}`);
|
|
98
|
+
if (binding.Branch !== plan.branch) errors.push(`${rel}: Branch differs from the write plan`);
|
|
99
|
+
if (binding.Base !== plan.base) errors.push(`${rel}: Base differs from the write plan`);
|
|
100
|
+
if (binding.Commit !== (plan.commit ?? '—')) errors.push(`${rel}: Commit ${binding.Commit} differs from the write plan's ${plan.commit ?? '—'}`);
|
|
101
|
+
const rows = tableUnder(text, '## Projection') ?? [];
|
|
102
|
+
if (rows.length !== plan.files.length) errors.push(`${rel}: Projection has ${rows.length} rows, the plan has ${plan.files.length}`);
|
|
103
|
+
for (const [p, change] of rows) {
|
|
104
|
+
const file = plan.files.find((f) => f.path === p);
|
|
105
|
+
if (!file) { errors.push(`${rel}: Projection names ${p}, which the plan does not carry`); continue; }
|
|
106
|
+
if (file.change !== change) errors.push(`${rel}: ${p} is ${change} here and ${file.change} in the plan`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (present.has('changes') && has('response/changes.md')) {
|
|
111
|
+
const text = await read('response/changes.md');
|
|
112
|
+
const rel = 'response/changes.md';
|
|
113
|
+
const rows = tableUnder(text, '## Files') ?? [];
|
|
114
|
+
const listed = rows.map(([p]) => p).sort();
|
|
115
|
+
const planned = plan.files.map((f) => f.path).sort();
|
|
116
|
+
if (listed.join('\n') !== planned.join('\n')) errors.push(`${rel}: Files lists ${listed.length} paths, the plan carries ${planned.length}`);
|
|
117
|
+
for (const [p, change] of rows) {
|
|
118
|
+
const file = plan.files.find((f) => f.path === p);
|
|
119
|
+
if (!file) { errors.push(`${rel}: Files names ${p}, which the plan does not carry`); continue; }
|
|
120
|
+
if (file.change !== change) errors.push(`${rel}: ${p} is ${change} here and ${file.change} in the plan`);
|
|
121
|
+
}
|
|
122
|
+
const binding = Object.fromEntries((tableUnder(text, '## Binding') ?? []).map(([k, v]) => [k, v]));
|
|
123
|
+
if (!empty(binding.Checkout) && !binding.Checkout.includes(plan.branch)) errors.push(`${rel}: the Checkout row does not name the session branch ${plan.branch}`);
|
|
124
|
+
}
|
|
125
|
+
return { errors };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
129
|
+
const target = process.argv[2];
|
|
130
|
+
if (!target) { process.stderr.write('usage: node validate.mjs <session>/step-N/parallel-M\n'); process.exit(2); }
|
|
131
|
+
const { errors } = await validateApplicationStep(path.resolve(target));
|
|
132
|
+
if (errors.length) { process.stderr.write(`${errors.join('\n')}\n`); process.exitCode = 1; } else process.stdout.write('valid frontend.source.apply branch\n');
|
|
133
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 9,
|
|
3
|
+
"note": "Stop codes frontend.surface.audit emits. INVALID_INPUT, SOURCE_DRIFT, EVIDENCE_MISSING and NO_PROGRESS come from operators/errors.json. RUNTIME_UNAVAILABLE is defined here and also emitted by uat.verify; UNKNOWN_RULE is emitted here too and is defined in operators/frontend-presentation-resolve/errors.json. Both belong in operators/errors.json with a scope list naming every operator that emits them.",
|
|
4
|
+
"codes": {}
|
|
5
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 9,
|
|
3
|
+
"id": "frontend.surface.audit",
|
|
4
|
+
"domain": "frontend",
|
|
5
|
+
"job": "Observe one rendered surface across a declared viewport and state matrix, measure every presentation value it actually produces, compare each measurement against the contract claim on that node, and return findings drawn only from the bound rule inventory.",
|
|
6
|
+
"package": "operator.md",
|
|
7
|
+
"errors": "errors.json",
|
|
8
|
+
"validator": "validate.mjs",
|
|
9
|
+
"selfTest": "self-test.mjs",
|
|
10
|
+
"resources": {
|
|
11
|
+
"profile": "sol-reviewer",
|
|
12
|
+
"grammarBound": true,
|
|
13
|
+
"tools": {
|
|
14
|
+
"@tools/fileread": "context-aliases",
|
|
15
|
+
"@tools/git": "read",
|
|
16
|
+
"@tools/websearch": "bounded",
|
|
17
|
+
"@tools/visualize": "html",
|
|
18
|
+
"@tools/browsercontrol": "required",
|
|
19
|
+
"@tools/http": "probe"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frontend.surface.audit
|
|
2
|
+
|
|
3
|
+
## Job
|
|
4
|
+
|
|
5
|
+
Observe the committed surface at the served route across the matrix the direction's coverage implies,
|
|
6
|
+
measure every node that carries a claim, and judge each measurement against the published proof rules
|
|
7
|
+
by the owner of the node it stands on.
|
|
8
|
+
|
|
9
|
+
## Readiness, capture and judgement are one job
|
|
10
|
+
|
|
11
|
+
Splitting them produced a familiar failure: a capture taken before the surface was ready, judged by a
|
|
12
|
+
step that could no longer tell, against evidence it had not itself collected. One operator that
|
|
13
|
+
waits, measures and judges under one receipt cannot lose that connection.
|
|
14
|
+
|
|
15
|
+
## The surface must be the committed surface
|
|
16
|
+
|
|
17
|
+
The applied receipt names the commit it wrote, the pinned head equals that commit, and the preview
|
|
18
|
+
serves the session worktree at it. A head that differs is `SOURCE_DRIFT` with nothing captured,
|
|
19
|
+
because a measurement of another tree proves nothing about this one.
|
|
20
|
+
|
|
21
|
+
## Measurement beats claim, always
|
|
22
|
+
|
|
23
|
+
Each node carries the identifiers it claims to satisfy, and the audit measures what the surface
|
|
24
|
+
actually renders. A claim is never evidence of passing: a node claiming `GAP-4` while the computed
|
|
25
|
+
gap measures `1.5rem` is a failure, and no amount of claiming changes the measurement. That is the
|
|
26
|
+
whole mechanic, and it is why the claim exists.
|
|
27
|
+
|
|
28
|
+
## The owner decides where a failure goes
|
|
29
|
+
|
|
30
|
+
A failing claim on an application-owned node is a value the resolution has to publish again, so it
|
|
31
|
+
routes back to `frontend.presentation.resolve`, which is the operator that caps the rounds. A failing claim
|
|
32
|
+
on a Grammar component's own render is a Grammar gap: it goes to a person and into the family's own
|
|
33
|
+
gap table, never into a resolve loop, because no application value can fix a component. The interior
|
|
34
|
+
of an application-owned node carries no claim and is not audited at all; only that node's own measure
|
|
35
|
+
rules are.
|
|
36
|
+
|
|
37
|
+
## The audit changes nothing
|
|
38
|
+
|
|
39
|
+
No verdict is a repair, a workaround or an instruction. A failure stays a failure in the receipt
|
|
40
|
+
until a resolution publishes a new value and the applier writes it, and the same surface is audited
|
|
41
|
+
again. That separation is why the receipt is worth anything: an operator that could fix what it found
|
|
42
|
+
would always be able to report a clean surface.
|
|
43
|
+
|
|
44
|
+
## Boundary
|
|
45
|
+
|
|
46
|
+
Context is read-only, and the runtime is consumed, never owned. The operator writes only `response/`
|
|
47
|
+
of its own branch: the audit receipt, its captures, its screenshots and its verdicts. It does not
|
|
48
|
+
modify product source, the applied tree, knowledge or Grammar, repair, restyle or work around
|
|
49
|
+
anything it observes, start, stop, deploy or reconfigure a runtime service, cite a rule identifier
|
|
50
|
+
absent from the bound inventory, judge a node it did not measure, or accept a claim as evidence that
|
|
51
|
+
a node passes.
|
|
52
|
+
|
|
53
|
+
## Context
|
|
54
|
+
|
|
55
|
+
| Alias | Bind | Required |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `@knowledge/ui/proof` | what only becomes true once rendered; the audit's whole rule inventory | yes |
|
|
58
|
+
| `@workspaces/fe` | the routed checkout at the commit the application wrote; the owners and identifiers observed there | yes |
|
|
59
|
+
| `@worktrees/sessions/central-runtime` | the shared runtime owner: the preview serving the session worktree at that commit | yes |
|
|
60
|
+
| `@knowledge/grammars/starci` | how the Core family is meant to realize Common, and where its gaps are recorded | no |
|
|
61
|
+
|
|
62
|
+
## Inputs
|
|
63
|
+
|
|
64
|
+
| Kind | From | Required |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| `frontend-source-application` | `frontend.source.apply`, the commit under observation and the claims it wrote | yes |
|
|
67
|
+
| `frontend-presentation-resolution` | `frontend.presentation.resolve`, the owner of every node | yes |
|
|
68
|
+
| `frontend-direction-decision` | `frontend.direction.decide`, the route and the coverage the matrix is derived from | yes |
|
|
69
|
+
|
|
70
|
+
## Requirements
|
|
71
|
+
|
|
72
|
+
| Field | Type | Default | Ask |
|
|
73
|
+
| --- | --- | --- | --- |
|
|
74
|
+
| `matrix` | list | every coverage entry | The matrix entries to capture; the person may only narrow what the coverage implies |
|
|
75
|
+
| `readinessProbe` | choice | route-served | The floor for readiness; a state that needs data rises to `route-and-data-served` on its own |
|
|
76
|
+
| `resume` | token | null | The blocked branch's token when re-entering after a stop |
|
|
77
|
+
|
|
78
|
+
## Steps
|
|
79
|
+
|
|
80
|
+
| # | Step | Params | Reads | Writes | Stops with |
|
|
81
|
+
| --- | --- | --- | --- | --- | --- |
|
|
82
|
+
| 1 | Validate the gate and resume, and confirm the head and the served route | `resume` | `request/request.json`, input `frontend-source-application` (its commit must equal the pinned head), @workspaces/fe at the frozen head, @worktrees/sessions/central-runtime (the preview serving the session worktree at that commit), @tools/git | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
|
|
83
|
+
| 2 | Bind the authority | — | @knowledge/ui/proof (every topic with its fingerprint and inventory), input `frontend-source-application` (the claims), input `frontend-presentation-resolution` (the owner of every node), @worktrees/sessions/central-runtime | — | — |
|
|
84
|
+
| 3 | Select the matrix entries | `matrix` | input `frontend-direction-decision` (the coverage: state by viewport by colour scheme) | — | — |
|
|
85
|
+
| 4 | Reach readiness for each entry | `readinessProbe` | @worktrees/sessions/central-runtime, @tools/http | — | `RUNTIME_UNAVAILABLE` |
|
|
86
|
+
| 5 | Capture and measure each entry | — | @worktrees/sessions/central-runtime, @workspaces/fe (the observed owners and the identifiers each node carries), @tools/browsercontrol | `response/artifacts/<matrixId>.png`, `response/data/captures/<matrixId>.json` | `EVIDENCE_MISSING` |
|
|
87
|
+
| 6 | Compare against the claims and the proof rules, judge by owner, and emit | — | @knowledge/ui/proof, @knowledge/grammars/starci, the captures, @tools/websearch | `response/data/verdicts.json`, `response/response.md`, `response/response.json`, @tools/visualize | `UNKNOWN_RULE` |
|
|
88
|
+
|
|
89
|
+
The matrix is the direction's coverage, not a new decision: `matrix` may only narrow it, and the
|
|
90
|
+
orchestrator may split the entries across up to three parallel branches of the same step. Every entry
|
|
91
|
+
produces one screenshot and one capture, and an entry that produced neither is `EVIDENCE_MISSING`
|
|
92
|
+
rather than a quiet omission. Every node carrying a claim is measured; a verdict is never recorded
|
|
93
|
+
for a node that was not.
|
|
94
|
+
|
|
95
|
+
## Outputs
|
|
96
|
+
|
|
97
|
+
| Kind | File | Type | Required |
|
|
98
|
+
| --- | --- | --- | --- |
|
|
99
|
+
| `frontend-surface-audit` | `response/response.md` | md | yes |
|
|
100
|
+
| `capture` | `response/data/captures/<matrixId>.json` | data | yes |
|
|
101
|
+
| `screenshot` | `response/artifacts/<matrixId>.png` | artifact | yes |
|
|
102
|
+
| `verdicts` | `response/data/verdicts.json` | data | yes |
|
|
103
|
+
|
|
104
|
+
## Stops
|
|
105
|
+
|
|
106
|
+
| Code | Disposition |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| `INVALID_INPUT` | terminate |
|
|
109
|
+
| `SOURCE_DRIFT` | terminate |
|
|
110
|
+
| `RUNTIME_UNAVAILABLE` | terminate |
|
|
111
|
+
| `EVIDENCE_MISSING` | terminate |
|
|
112
|
+
| `UNKNOWN_RULE` | terminate |
|
|
113
|
+
| `NO_PROGRESS` | terminate |
|
|
114
|
+
|
|
115
|
+
## Next
|
|
116
|
+
|
|
117
|
+
| When | Operator |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| a claim fails on an application-owned node, so a value must be published again | `frontend.presentation.resolve` |
|
|
120
|
+
| every claim holds and the checkout's own gates must run | `quality.verify` |
|
|
121
|
+
| a claim fails on a Grammar component's own render, so a person records the family gap and publishes | `frontend.surface.audit` |
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frontend.surface.audit
|
|
2
|
+
|
|
3
|
+
## Việc
|
|
4
|
+
|
|
5
|
+
Quan sát bề mặt đã commit ở route đang được phục vụ, qua đúng ma trận mà coverage của hướng suy ra,
|
|
6
|
+
đo mọi node có mang lời khai, và phán quyết từng phép đo theo luật proof đã publish, dựa trên chủ sở
|
|
7
|
+
hữu của node mà nó đứng lên.
|
|
8
|
+
|
|
9
|
+
## Sẵn sàng, chụp và phán quyết là một việc
|
|
10
|
+
|
|
11
|
+
Tách chúng ra đã sinh ra một kiểu hỏng quen thuộc: một bức chụp lấy trước khi bề mặt sẵn sàng, bị
|
|
12
|
+
phán quyết bởi một bước không còn cách nào biết điều đó, dựa trên bằng chứng mà chính nó không thu.
|
|
13
|
+
Một operator vừa chờ, vừa đo, vừa phán quyết dưới một biên nhận thì không đánh mất mối nối ấy.
|
|
14
|
+
|
|
15
|
+
## Bề mặt phải là bề mặt đã commit
|
|
16
|
+
|
|
17
|
+
Biên nhận áp dụng gọi tên commit nó đã ghi, head được ghim bằng đúng commit đó, và preview phục vụ
|
|
18
|
+
worktree phiên ở chính commit ấy. Head khác là `SOURCE_DRIFT` và không chụp gì cả, vì đo một cây khác
|
|
19
|
+
thì không chứng minh được gì về cây này.
|
|
20
|
+
|
|
21
|
+
## Phép đo thắng lời khai, luôn luôn
|
|
22
|
+
|
|
23
|
+
Mỗi node mang những identifier nó khai là mình thoả, và lượt audit đo thứ bề mặt thực sự render ra.
|
|
24
|
+
Lời khai không bao giờ là bằng chứng của việc pass: một node khai `GAP-4` trong khi gap tính được đo
|
|
25
|
+
ra `1.5rem` là một lần hỏng, và khai bao nhiêu cũng không đổi được phép đo. Đó là toàn bộ cơ chế, và
|
|
26
|
+
đó là lý do lời khai tồn tại.
|
|
27
|
+
|
|
28
|
+
## Chủ sở hữu quyết định chỗ hỏng đi về đâu
|
|
29
|
+
|
|
30
|
+
Một lời khai hỏng trên node do ứng dụng sở hữu là một giá trị mà resolution phải publish lại, nên nó
|
|
31
|
+
quay về `frontend.presentation.resolve`, chính operator giữ số vòng. Một lời khai hỏng trên phần render của
|
|
32
|
+
chính một component Grammar là một khoảng trống Grammar: nó đi tới một con người và vào bảng gap của
|
|
33
|
+
họ, không bao giờ đi vào vòng lặp resolve, vì không giá trị ứng dụng nào sửa được một component. Phần
|
|
34
|
+
ruột của node do ứng dụng sở hữu không mang lời khai nào và không bị audit; chỉ các luật đo của chính
|
|
35
|
+
node ấy bị audit.
|
|
36
|
+
|
|
37
|
+
## Audit không đổi gì cả
|
|
38
|
+
|
|
39
|
+
Không phán quyết nào là một lần sửa, một cách chữa cháy hay một mệnh lệnh. Một chỗ hỏng vẫn là chỗ
|
|
40
|
+
hỏng trong biên nhận cho tới khi một lượt resolution publish giá trị mới và người ghi viết nó xuống,
|
|
41
|
+
rồi chính bề mặt ấy được audit lại. Sự tách bạch đó là lý do biên nhận này có giá trị: một operator
|
|
42
|
+
có thể tự sửa thứ nó tìm ra thì lúc nào cũng báo được một bề mặt sạch.
|
|
43
|
+
|
|
44
|
+
## Ranh giới
|
|
45
|
+
|
|
46
|
+
Context chỉ đọc, và runtime là thứ được tiêu thụ chứ không được sở hữu. Operator chỉ ghi `response/`
|
|
47
|
+
của nhánh mình: biên nhận audit, các bức chụp, các ảnh màn hình và các phán quyết. Nó không sửa source
|
|
48
|
+
sản phẩm, cây đã áp dụng, knowledge hay Grammar, không sửa chữa, không tô lại, không chữa cháy bất cứ
|
|
49
|
+
thứ gì nó thấy, không khởi động, dừng, deploy hay cấu hình lại dịch vụ runtime, không trích một
|
|
50
|
+
identifier vắng mặt trong kho đã bind, không phán quyết một node nó chưa đo, và không nhận lời khai
|
|
51
|
+
làm bằng chứng rằng node đã pass.
|
|
52
|
+
|
|
53
|
+
## Context
|
|
54
|
+
|
|
55
|
+
| Alias | Bind | Bắt buộc |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `@knowledge/ui/proof` | thứ chỉ thành thật khi đã render; toàn bộ kho luật của lượt audit | có |
|
|
58
|
+
| `@workspaces/fe` | checkout được route ở commit mà lần áp dụng đã ghi; chủ sở hữu và identifier được quan sát ở đó | có |
|
|
59
|
+
| `@worktrees/sessions/central-runtime` | chủ runtime dùng chung: preview đang phục vụ worktree phiên ở commit ấy | có |
|
|
60
|
+
| `@knowledge/grammars/starci` | họ Core được kỳ vọng hiện thực hoá Common ra sao, và gap của họ được ghi ở đâu | không |
|
|
61
|
+
|
|
62
|
+
## Đầu vào
|
|
63
|
+
|
|
64
|
+
| Kind | Từ đâu | Bắt buộc |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| `frontend-source-application` | `frontend.source.apply`, commit đang được quan sát và các lời khai nó đã ghi | có |
|
|
67
|
+
| `frontend-presentation-resolution` | `frontend.presentation.resolve`, chủ sở hữu của từng node | có |
|
|
68
|
+
| `frontend-direction-decision` | `frontend.direction.decide`, route và coverage mà ma trận suy ra từ đó | có |
|
|
69
|
+
|
|
70
|
+
## Yêu cầu
|
|
71
|
+
|
|
72
|
+
| Field | Kiểu | Mặc định | Hỏi |
|
|
73
|
+
| --- | --- | --- | --- |
|
|
74
|
+
| `matrix` | list | every coverage entry | Các mục ma trận cần chụp; người dùng chỉ được thu hẹp thứ coverage đã suy ra |
|
|
75
|
+
| `readinessProbe` | choice | route-served | Mức sàn của sẵn sàng; một state cần dữ liệu tự nâng lên `route-and-data-served` |
|
|
76
|
+
| `resume` | token | null | Token của nhánh bị chặn khi vào lại sau một mã dừng |
|
|
77
|
+
|
|
78
|
+
## Các bước
|
|
79
|
+
|
|
80
|
+
| # | Bước | Tham số | Đọc | Ghi | Dừng với |
|
|
81
|
+
| --- | --- | --- | --- | --- | --- |
|
|
82
|
+
| 1 | Kiểm gate, chạy lại, và xác nhận head cùng route đang phục vụ | `resume` | `request/request.json`, đầu vào `frontend-source-application` (commit của nó phải bằng head đã ghim), @workspaces/fe ở head đóng băng, @worktrees/sessions/central-runtime (preview phục vụ worktree phiên ở commit ấy), @tools/git | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
|
|
83
|
+
| 2 | Bind thẩm quyền | — | @knowledge/ui/proof (mọi topic kèm fingerprint và kho luật), đầu vào `frontend-source-application` (các lời khai), đầu vào `frontend-presentation-resolution` (chủ sở hữu của từng node), @worktrees/sessions/central-runtime | — | — |
|
|
84
|
+
| 3 | Chọn các mục ma trận | `matrix` | đầu vào `frontend-direction-decision` (coverage: state nhân viewport nhân bảng màu) | — | — |
|
|
85
|
+
| 4 | Chờ từng mục tới lúc sẵn sàng | `readinessProbe` | @worktrees/sessions/central-runtime, @tools/http | — | `RUNTIME_UNAVAILABLE` |
|
|
86
|
+
| 5 | Chụp và đo từng mục | — | @worktrees/sessions/central-runtime, @workspaces/fe (các owner được quan sát và identifier từng node mang), @tools/browsercontrol | `response/artifacts/<matrixId>.png`, `response/data/captures/<matrixId>.json` | `EVIDENCE_MISSING` |
|
|
87
|
+
| 6 | Đối chiếu với lời khai và luật proof, phán quyết theo chủ sở hữu, rồi phát | — | @knowledge/ui/proof, @knowledge/grammars/starci, các bức chụp, @tools/websearch | `response/data/verdicts.json`, `response/response.md`, `response/response.json`, @tools/visualize | `UNKNOWN_RULE` |
|
|
88
|
+
|
|
89
|
+
Ma trận là coverage của hướng, không phải một quyết định mới: `matrix` chỉ được thu hẹp nó, và
|
|
90
|
+
orchestrator có thể chia các mục ra tối đa ba nhánh song song của cùng một bậc. Mỗi mục cho đúng một
|
|
91
|
+
ảnh màn hình và một bức chụp, và một mục không cho cái nào là `EVIDENCE_MISSING` chứ không phải một
|
|
92
|
+
chỗ khuyết lặng lẽ. Mọi node mang lời khai đều được đo; không bao giờ có phán quyết cho một node chưa
|
|
93
|
+
được đo.
|
|
94
|
+
|
|
95
|
+
## Đầu ra
|
|
96
|
+
|
|
97
|
+
| Kind | File | Kiểu | Bắt buộc |
|
|
98
|
+
| --- | --- | --- | --- |
|
|
99
|
+
| `frontend-surface-audit` | `response/response.md` | md | có |
|
|
100
|
+
| `capture` | `response/data/captures/<matrixId>.json` | data | có |
|
|
101
|
+
| `screenshot` | `response/artifacts/<matrixId>.png` | artifact | có |
|
|
102
|
+
| `verdicts` | `response/data/verdicts.json` | data | có |
|
|
103
|
+
|
|
104
|
+
## Dừng
|
|
105
|
+
|
|
106
|
+
| Code | Xử lý |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| `INVALID_INPUT` | terminate |
|
|
109
|
+
| `SOURCE_DRIFT` | terminate |
|
|
110
|
+
| `RUNTIME_UNAVAILABLE` | terminate |
|
|
111
|
+
| `EVIDENCE_MISSING` | terminate |
|
|
112
|
+
| `UNKNOWN_RULE` | terminate |
|
|
113
|
+
| `NO_PROGRESS` | terminate |
|
|
114
|
+
|
|
115
|
+
## Kế tiếp
|
|
116
|
+
|
|
117
|
+
| Khi | Operator |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| một lời khai hỏng trên node do ứng dụng sở hữu, nên phải publish lại một giá trị | `frontend.presentation.resolve` |
|
|
120
|
+
| mọi lời khai đều đứng vững và các cổng của chính checkout phải chạy | `quality.verify` |
|
|
121
|
+
| một lời khai hỏng trên phần render của chính component Grammar, nên một người ghi gap của họ rồi publish | `frontend.surface.audit` |
|