@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,163 @@
|
|
|
1
|
+
// Proves validate.mjs on a synthetic session branch: one conforming resolution of a three-node tree
|
|
2
|
+
// (one application-owned gap, one Grammar-owned padding, one recorded gap), one branch with the
|
|
3
|
+
// contract emission off, one blocked on RULE_MISSING before anything was written, and one mutation
|
|
4
|
+
// per law, each 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 { validateResolutionStep } from './validate.mjs';
|
|
10
|
+
|
|
11
|
+
const TREE = 'response/artifacts/plan-picker.resolved.tsx';
|
|
12
|
+
const MAIN = 'body>main';
|
|
13
|
+
const SECTION = 'body>main>section';
|
|
14
|
+
const ASIDE = 'body>aside';
|
|
15
|
+
const FINGERPRINT = `sha256:${'a'.repeat(64)}`;
|
|
16
|
+
|
|
17
|
+
const inventory = () => ({
|
|
18
|
+
treeFingerprint: FINGERPRINT,
|
|
19
|
+
classNames: ['gap-6', 'gap-1'],
|
|
20
|
+
ruleIds: ['GAP-5', 'GAP-1'],
|
|
21
|
+
gaps: [{ nodePath: ASIDE, property: 'gap', missingPath: 'Common exposes no public path for the compact identity pair' }],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const resolvedTree = ({ emission = 'on' } = {}) => (emission === 'on'
|
|
25
|
+
? `export default function PlanPicker() {
|
|
26
|
+
return (
|
|
27
|
+
<main className="gap-6" data-contract="GAP-5">
|
|
28
|
+
<SurfaceCard />
|
|
29
|
+
<aside className="gap-1" data-contract="GAP-1" />
|
|
30
|
+
</main>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
`
|
|
34
|
+
: `export default function PlanPicker() {
|
|
35
|
+
return (
|
|
36
|
+
<main className="gap-6">
|
|
37
|
+
<SurfaceCard />
|
|
38
|
+
<aside className="gap-1" />
|
|
39
|
+
</main>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
`);
|
|
43
|
+
|
|
44
|
+
const responseMd = ({ gaps = 1, removed = 1 } = {}) => `# frontend-presentation-resolution — plan-picker
|
|
45
|
+
|
|
46
|
+
## Owner map
|
|
47
|
+
|
|
48
|
+
| Node | Property | Owner | Rule |
|
|
49
|
+
| --- | --- | --- | --- |
|
|
50
|
+
| \`${MAIN}\` | gap | app | \`GAP-5\` |
|
|
51
|
+
| \`${SECTION}\` | padding | grammar | \`PADDING-4\` |
|
|
52
|
+
| \`${ASIDE}\` | gap | app | \`GAP-1\` |
|
|
53
|
+
|
|
54
|
+
## Rules chosen
|
|
55
|
+
|
|
56
|
+
| Node | Rule | Class | Condition |
|
|
57
|
+
| --- | --- | --- | --- |
|
|
58
|
+
| \`${MAIN}\` | \`GAP-5\` | \`gap-6\` | region stack inside the page shell |
|
|
59
|
+
| \`${ASIDE}\` | \`GAP-1\` | \`gap-1\` | the compact identity pair |
|
|
60
|
+
|
|
61
|
+
## Removed
|
|
62
|
+
|
|
63
|
+
| Node | Class | Because |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
${removed ? `| \`${SECTION}\` | \`p-4\` | reimplements an owned relationship |` : ''}
|
|
66
|
+
|
|
67
|
+
## Gaps
|
|
68
|
+
|
|
69
|
+
| Node | Property | Missing path |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
${gaps ? `| \`${ASIDE}\` | gap | Common exposes no public path for the compact identity pair |` : ''}
|
|
72
|
+
|
|
73
|
+
## Fallbacks taken
|
|
74
|
+
|
|
75
|
+
| Code | Action |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
const requestJson = ({ extra = {} } = {}) => ({
|
|
80
|
+
schemaVersion: 9,
|
|
81
|
+
operatorId: 'frontend.presentation.resolve',
|
|
82
|
+
step: 2,
|
|
83
|
+
parallel: 1,
|
|
84
|
+
sessionId: 's-test',
|
|
85
|
+
contexts: [{ alias: '@knowledge/ui/presentation', head: null }, { alias: '@grammar/core', head: null }, { alias: '@workspaces/fe', head: 'd'.repeat(40) }],
|
|
86
|
+
requirements: { maxRounds: 2, contractEmission: 'on', resume: null, ...extra },
|
|
87
|
+
inputs: { 'frontend-direction-decision': 'step-1/parallel-1/response/response.md' },
|
|
88
|
+
resume: null,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const responseJson = ({ status = 'done', stop, fields, fallbacks = [], next = ['frontend.source.apply'] } = {}) => ({
|
|
92
|
+
schemaVersion: 9,
|
|
93
|
+
operatorId: 'frontend.presentation.resolve',
|
|
94
|
+
step: 2,
|
|
95
|
+
parallel: 1,
|
|
96
|
+
status,
|
|
97
|
+
...(stop ? { stop } : {}),
|
|
98
|
+
fallbacks,
|
|
99
|
+
fields: fields ?? { 'frontend-presentation-resolution': 'response/response.md', inventory: 'response/data/inventory.json', 'resolved-tree': TREE },
|
|
100
|
+
commits: [],
|
|
101
|
+
next,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
function writeBranch(files) {
|
|
105
|
+
const session = mkdtempSync(path.join(tmpdir(), 'fe-resolve-session-'));
|
|
106
|
+
const branch = path.join(session, 'step-2', 'parallel-1');
|
|
107
|
+
for (const d of ['request', 'response/data', 'response/artifacts']) mkdirSync(path.join(branch, d), { recursive: true });
|
|
108
|
+
mkdirSync(path.join(session, 'step-1', 'parallel-1', 'response'), { recursive: true });
|
|
109
|
+
writeFileSync(path.join(session, 'step-1', 'parallel-1', 'response', 'response.md'), '# frontend-direction-decision — plan-picker\n');
|
|
110
|
+
writeFileSync(path.join(session, 'state.json'), JSON.stringify({ id: 's-test', chain: [['1/1'], ['2/1']], steps: { '1/1': 'frontend.direction.decide', '2/1': 'frontend.presentation.resolve' }, current: '2/1', status: 'running' }));
|
|
111
|
+
for (const [name, content] of Object.entries(files)) {
|
|
112
|
+
if (content === null) continue;
|
|
113
|
+
writeFileSync(path.join(branch, name), typeof content === 'string' ? content : JSON.stringify(content, null, 2));
|
|
114
|
+
}
|
|
115
|
+
return { branch, session };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const baseline = () => ({
|
|
119
|
+
'request/request.json': requestJson(),
|
|
120
|
+
'response/response.json': responseJson(),
|
|
121
|
+
'response/response.md': responseMd(),
|
|
122
|
+
'response/data/inventory.json': inventory(),
|
|
123
|
+
[TREE]: resolvedTree(),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
async function expectValid(files, label) {
|
|
127
|
+
const { branch, session } = writeBranch(files);
|
|
128
|
+
const { errors } = await validateResolutionStep(branch);
|
|
129
|
+
rmSync(session, { recursive: true, force: true });
|
|
130
|
+
assert.deepEqual(errors, [], `${label} should be valid`);
|
|
131
|
+
}
|
|
132
|
+
async function expectError(files, needle, label) {
|
|
133
|
+
const { branch, session } = writeBranch(files);
|
|
134
|
+
const { errors } = await validateResolutionStep(branch);
|
|
135
|
+
rmSync(session, { recursive: true, force: true });
|
|
136
|
+
assert.ok(errors.some((e) => e.includes(needle)), `${label}: expected an error containing "${needle}", got:\n${errors.join('\n') || '(none)'}`);
|
|
137
|
+
}
|
|
138
|
+
const mutate = (change) => { const m = inventory(); change(m); return { ...baseline(), 'response/data/inventory.json': m }; };
|
|
139
|
+
|
|
140
|
+
await expectValid(baseline(), 'one application-owned gap, one Grammar-owned padding, one recorded gap');
|
|
141
|
+
await expectValid({ ...baseline(), 'request/request.json': requestJson({ extra: { contractEmission: 'off' } }), [TREE]: resolvedTree({ emission: 'off' }) }, 'contract emission off leaves the tree bare');
|
|
142
|
+
await expectValid({ 'request/request.json': requestJson(), 'response/response.json': responseJson({ status: 'blocked', stop: 'RULE_MISSING', next: [], fields: {} }) }, 'blocked on RULE_MISSING before anything was written');
|
|
143
|
+
|
|
144
|
+
await expectError({ ...baseline(), 'request/request.json': requestJson({ extra: { mystery: 1 } }) }, 'requirements.mystery is not a field', 'undeclared requirement');
|
|
145
|
+
await expectError({ ...baseline(), 'request/request.json': requestJson({ extra: { maxRounds: 0 } }) }, 'maxRounds must be a positive whole number', 'no rounds allowed');
|
|
146
|
+
await expectError(mutate((m) => { m.ruleIds.push('GAP-99'); }), 'is outside the published presentation inventory', 'unpublished rule');
|
|
147
|
+
await expectError(mutate((m) => { m.classNames.push('gap-11'); }), 'is in the inventory and not in the resolved tree', 'class that never reached the tree');
|
|
148
|
+
await expectError(mutate((m) => { m.treeFingerprint = 'nope'; }), 'treeFingerprint', 'inventory schema');
|
|
149
|
+
await expectError({ ...baseline(), [TREE]: resolvedTree().replace(' data-contract="GAP-1"', '') }, 'no node claims it under data-contract', 'applied rule with no claim');
|
|
150
|
+
await expectError({ ...baseline(), 'request/request.json': requestJson({ extra: { contractEmission: 'off' } }) }, 'contract emission is off and the resolved tree carries a data-contract', 'claims written under emission off');
|
|
151
|
+
await expectError({ ...baseline(), 'response/response.md': responseMd().replace('| `body>main` | `GAP-5` | `gap-6` |', '| `body>main` | `GAP-5` | `gap-5` |'), 'response/data/inventory.json': (() => { const m = inventory(); m.classNames = ['gap-5', 'gap-1']; return m; })(), [TREE]: resolvedTree().replace('gap-6', 'gap-5') }, 'renders GAP-5 as gap-5, expected step 6', 'ordinal written as the step');
|
|
152
|
+
await expectError({ ...baseline(), 'response/response.md': responseMd().replace(`| \`${SECTION}\` | padding | grammar | \`PADDING-4\` |`, `| \`${SECTION}\` | padding | app | \`PADDING-4\` |`) }, 'owns PADDING-4 and chooses no class for it', 'application claims an owned property and writes nothing');
|
|
153
|
+
await expectError({ ...baseline(), 'response/response.md': responseMd().replace(`| \`${ASIDE}\` | \`GAP-1\` | \`gap-1\` | the compact identity pair |`, `| \`${SECTION}\` | \`PADDING-4\` | \`p-4\` | the card inset |`) }, 'chooses a class for PADDING-4, which Grammar already owns', 'application reimplements a Grammar relationship');
|
|
154
|
+
await expectError({ ...baseline(), 'response/response.md': responseMd().replace(`| \`${MAIN}\` | gap | app | \`GAP-5\` |`, `| \`${MAIN}\` | gap | app | \`GAP-5\` |\n| \`${MAIN}\` | gap | app | \`GAP-4\` |`) }, 'decides gap more than once in the owner map', 'one property decided twice');
|
|
155
|
+
await expectError({ ...baseline(), 'response/response.md': responseMd({ gaps: 0 }) }, 'the Gaps table and inventory.gaps differ', 'gap recorded in data and not for the reader');
|
|
156
|
+
await expectError(mutate((m) => { m.gaps = []; }), 'the Gaps table and inventory.gaps differ', 'gap recorded for the reader and not in data');
|
|
157
|
+
await expectError({ ...baseline(), 'response/response.md': responseMd().replace('| `p-4` | reimplements an owned relationship |', '| `gap-6` | reimplements an owned relationship |') }, 'and the inventory still carries it', 'class removed and still inventoried');
|
|
158
|
+
await expectError({ ...baseline(), 'response/response.md': responseMd().replace('## Removed', '## Deleted') }, 'missing section ^## Removed$', 'receipt section renamed');
|
|
159
|
+
await expectError({ ...baseline(), 'response/data/inventory.json': null, 'response/response.json': responseJson({ fields: { 'frontend-presentation-resolution': 'response/response.md', 'resolved-tree': TREE } }) }, 'required output inventory is not in fields', 'missing required output');
|
|
160
|
+
await expectError({ ...baseline(), 'response/response.json': responseJson({ status: 'blocked', stop: 'MADE_UP_CODE', next: [] }) }, 'not a registered code', 'unknown stop code');
|
|
161
|
+
await expectError({ ...baseline(), 'response/response.json': responseJson({ fallbacks: ['RULE_MISSING'] }) }, 'has disposition terminate under these requirements', 'a terminate code taken as a fallback');
|
|
162
|
+
|
|
163
|
+
process.stdout.write('frontend.presentation.resolve self-test: 3 valid branches, 18 rejected mutations\n');
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// frontend.presentation.resolve's own law over one branch, on top of the shared step check: every rule the
|
|
2
|
+
// inventory carries is one @knowledge/ui/presentation actually publishes; every class the inventory
|
|
3
|
+
// carries appears in the resolved tree; a Grammar-owned property emits no application class while an
|
|
4
|
+
// application-owned one emits a class whose step matches the ordinal of its rule; the gaps the
|
|
5
|
+
// receipt lists are exactly the gaps the inventory records; and with emission on every claimed rule
|
|
6
|
+
// reaches the tree as a data-contract token.
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import process from 'node:process';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { validateStep } from '../../scripts/validate-step.mjs';
|
|
13
|
+
import { tableUnder } from '../../scripts/validate-response.mjs';
|
|
14
|
+
|
|
15
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
16
|
+
const empty = (v) => v === undefined || v === null || v === '' || v === '—';
|
|
17
|
+
const list = (v) => (Array.isArray(v) ? v : empty(v) ? [] : [v]);
|
|
18
|
+
|
|
19
|
+
// The scale topics publish an ordinal rule identifier while the utility scale publishes a step
|
|
20
|
+
// number, and the two diverge above the fourth value. Writing the ordinal as the class is the defect
|
|
21
|
+
// this map exists to catch: GAP-5 renders `gap-6`, never `gap-5`.
|
|
22
|
+
const ORDINAL_TO_STEP = { 0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '6', 6: '8' };
|
|
23
|
+
const SCALE_PATTERN = {
|
|
24
|
+
GAP: /^gap(?:-[xy])?-([0-9]+)$/,
|
|
25
|
+
PADDING: /^p(?:[xytrbse])?-([0-9]+)$/,
|
|
26
|
+
MARGIN: /^-?m(?:[xytrbse])?-([0-9]+)$/,
|
|
27
|
+
};
|
|
28
|
+
const prefixOf = (ruleId) => ruleId.slice(0, ruleId.lastIndexOf('-'));
|
|
29
|
+
const ordinalOf = (ruleId) => Number(ruleId.slice(ruleId.lastIndexOf('-') + 1));
|
|
30
|
+
|
|
31
|
+
// The bound inventory is what @knowledge/ui/presentation publishes: one `## PREFIX-n` heading per rule.
|
|
32
|
+
export async function publishedRuleIds(root = ROOT) {
|
|
33
|
+
const dir = path.join(root, 'knowledge', 'ui', 'presentation');
|
|
34
|
+
const ids = new Set();
|
|
35
|
+
if (!existsSync(dir)) return ids;
|
|
36
|
+
for (const name of (await readdir(dir)).filter((f) => f.endsWith('.md') && !f.endsWith('.vi.md') && f !== 'INDEX.md')) {
|
|
37
|
+
const text = await readFile(path.join(dir, name), 'utf8');
|
|
38
|
+
for (const m of text.matchAll(/^##\s+([A-Z][A-Z0-9-]*-[0-9]+)\b/gm)) ids.add(m[1]);
|
|
39
|
+
}
|
|
40
|
+
return ids;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function validateResolutionStep(branchDir, root = ROOT) {
|
|
44
|
+
const base = await validateStep(root, branchDir);
|
|
45
|
+
const errors = [...base.errors];
|
|
46
|
+
const { response, requirements = {}, present = new Set() } = base;
|
|
47
|
+
if (!response || response.operatorId !== 'frontend.presentation.resolve') return { errors };
|
|
48
|
+
const has = (f) => existsSync(path.join(branchDir, f));
|
|
49
|
+
const read = (f) => readFile(path.join(branchDir, f), 'utf8');
|
|
50
|
+
|
|
51
|
+
const maxRounds = Number(requirements.maxRounds ?? 2);
|
|
52
|
+
if (!Number.isInteger(maxRounds) || maxRounds < 1) errors.push(`request.json: maxRounds must be a positive whole number, not ${requirements.maxRounds}`);
|
|
53
|
+
const emission = requirements.contractEmission ?? 'on';
|
|
54
|
+
|
|
55
|
+
let inventory = null;
|
|
56
|
+
if (present.has('inventory') && has('response/data/inventory.json')) {
|
|
57
|
+
try { inventory = JSON.parse(await read('response/data/inventory.json')); } catch { inventory = null; }
|
|
58
|
+
}
|
|
59
|
+
if (!inventory) {
|
|
60
|
+
if (response.status === 'done') errors.push('response/data/inventory.json: a done branch needs the inventory');
|
|
61
|
+
return { errors };
|
|
62
|
+
}
|
|
63
|
+
const at = 'response/data/inventory.json';
|
|
64
|
+
|
|
65
|
+
const published = await publishedRuleIds(root);
|
|
66
|
+
for (const ruleId of inventory.ruleIds) {
|
|
67
|
+
if (!published.has(ruleId)) errors.push(`${at}: rule ${ruleId} is outside the published presentation inventory (UNKNOWN_RULE)`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The resolved tree is the only place the classes may land, so every class the inventory claims
|
|
71
|
+
// must actually be in it.
|
|
72
|
+
const treeRefs = list(response.fields?.['resolved-tree']);
|
|
73
|
+
let tree = null;
|
|
74
|
+
for (const ref of treeRefs) if (has(ref)) tree = await read(ref);
|
|
75
|
+
if (tree === null) { if (response.status === 'done') errors.push('response/artifacts: the resolved tree is missing'); }
|
|
76
|
+
else {
|
|
77
|
+
for (const className of inventory.classNames) {
|
|
78
|
+
if (!tree.includes(className)) errors.push(`${at}: class ${className} is in the inventory and not in the resolved tree`);
|
|
79
|
+
}
|
|
80
|
+
if (emission === 'on') {
|
|
81
|
+
for (const ruleId of inventory.ruleIds) {
|
|
82
|
+
const claimed = [...tree.matchAll(/data-contract="([^"]*)"/g)].some((m) => m[1].split(/\s+/).includes(ruleId));
|
|
83
|
+
if (!claimed) errors.push(`${at}: rule ${ruleId} is applied and no node claims it under data-contract`);
|
|
84
|
+
}
|
|
85
|
+
} else if (/data-contract=/.test(tree)) errors.push('response/artifacts: contract emission is off and the resolved tree carries a data-contract attribute');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (present.has('frontend-presentation-resolution') && has('response/response.md')) {
|
|
89
|
+
const text = await read('response/response.md');
|
|
90
|
+
const rel = 'response/response.md';
|
|
91
|
+
const owners = tableUnder(text, '## Owner map') ?? [];
|
|
92
|
+
const chosen = tableUnder(text, '## Rules chosen') ?? [];
|
|
93
|
+
const removed = tableUnder(text, '## Removed') ?? [];
|
|
94
|
+
const gaps = tableUnder(text, '## Gaps') ?? [];
|
|
95
|
+
|
|
96
|
+
const appRules = new Set();
|
|
97
|
+
const grammarRules = new Set();
|
|
98
|
+
const seen = new Set();
|
|
99
|
+
for (const [node, property, owner, rule] of owners) {
|
|
100
|
+
const key = `${node}|${property}`;
|
|
101
|
+
if (seen.has(key)) errors.push(`${rel}: ${node} decides ${property} more than once in the owner map`);
|
|
102
|
+
seen.add(key);
|
|
103
|
+
(owner === 'app' ? appRules : grammarRules).add(`${node}|${rule}`);
|
|
104
|
+
}
|
|
105
|
+
for (const [node, rule, className] of chosen) {
|
|
106
|
+
if (grammarRules.has(`${node}|${rule}`)) { errors.push(`${rel}: ${node} chooses a class for ${rule}, which Grammar already owns`); continue; }
|
|
107
|
+
if (!appRules.has(`${node}|${rule}`)) { errors.push(`${rel}: ${node} chooses ${rule} without an application-owned row in the owner map`); continue; }
|
|
108
|
+
if (!inventory.ruleIds.includes(rule)) errors.push(`${rel}: ${node} chooses ${rule}, which the inventory does not carry`);
|
|
109
|
+
if (!inventory.classNames.includes(className)) errors.push(`${rel}: ${node} writes ${className}, which the inventory does not carry`);
|
|
110
|
+
const pattern = SCALE_PATTERN[prefixOf(rule)];
|
|
111
|
+
if (pattern) {
|
|
112
|
+
const expected = ORDINAL_TO_STEP[ordinalOf(rule)];
|
|
113
|
+
const steps = className.split(/\s+/).map((token) => pattern.exec(token)).filter(Boolean).map((m) => m[1]);
|
|
114
|
+
if (expected !== undefined) {
|
|
115
|
+
if (steps.length === 0) errors.push(`${rel}: ${node} emits no class for ${rule}`);
|
|
116
|
+
else if (!steps.includes(expected)) errors.push(`${rel}: ${node} renders ${rule} as ${className}, expected step ${expected}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const key of appRules) {
|
|
121
|
+
const [node, rule] = key.split('|');
|
|
122
|
+
if (!chosen.some(([n, r]) => n === node && r === rule)) errors.push(`${rel}: ${node} owns ${rule} and chooses no class for it`);
|
|
123
|
+
}
|
|
124
|
+
for (const [node, className] of removed) {
|
|
125
|
+
if (inventory.classNames.includes(className)) errors.push(`${rel}: ${node} removed ${className} and the inventory still carries it`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// The gaps a reader sees and the gaps the machine carries are one list.
|
|
129
|
+
const receiptGaps = gaps.map(([node, property, missing]) => `${node}|${property}|${missing}`).sort();
|
|
130
|
+
const dataGaps = inventory.gaps.map((g) => `${g.nodePath}|${g.property}|${g.missingPath}`).sort();
|
|
131
|
+
if (receiptGaps.join('\n') !== dataGaps.join('\n')) {
|
|
132
|
+
errors.push(`${rel}: the Gaps table and inventory.gaps differ (${receiptGaps.length} rows against ${dataGaps.length})`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { errors };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
139
|
+
const target = process.argv[2];
|
|
140
|
+
if (!target) { process.stderr.write('usage: node validate.mjs <session>/step-N/parallel-M\n'); process.exit(2); }
|
|
141
|
+
const { errors } = await validateResolutionStep(path.resolve(target));
|
|
142
|
+
if (errors.length) { process.stderr.write(`${errors.join('\n')}\n`); process.exitCode = 1; } else process.stdout.write('valid frontend.presentation.resolve branch\n');
|
|
143
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 9,
|
|
3
|
+
"note": "Stop codes frontend.source.apply emits. Same entry shape as operators/errors.json, scope implicit. Shared codes (INVALID_INPUT, SOURCE_DRIFT, NO_PROGRESS) come from operators/errors.json. OWNER_CONFLICT is also emitted here but is defined in operators/frontend-presentation-resolve/errors.json; it belongs in operators/errors.json with every emitting operator in scope.",
|
|
4
|
+
"codes": {
|
|
5
|
+
"RESOLUTION_STALE": {
|
|
6
|
+
"domain": "resolution",
|
|
7
|
+
"disposition": "terminate",
|
|
8
|
+
"meaning": {
|
|
9
|
+
"en": "The resolution actually read differs from the resolution the request bound.",
|
|
10
|
+
"vi": "Resolution thực sự đọc được khác resolution mà request đã bind."
|
|
11
|
+
},
|
|
12
|
+
"resume": {
|
|
13
|
+
"en": "Bind the current resolution, or resolve the tree again.",
|
|
14
|
+
"vi": "Bind resolution hiện tại, hoặc resolve lại cây."
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"WRITE_REJECTED": {
|
|
18
|
+
"domain": "caller",
|
|
19
|
+
"disposition": "terminate",
|
|
20
|
+
"meaning": {
|
|
21
|
+
"en": "A file or a value the write would produce lies outside what was authorized, or the committed tree is not the resolved tree.",
|
|
22
|
+
"vi": "Một file hay một giá trị lần ghi sẽ tạo ra nằm ngoài phần đã được uỷ quyền, hoặc cây đã commit không phải cây đã resolve."
|
|
23
|
+
},
|
|
24
|
+
"resume": {
|
|
25
|
+
"en": "Declare a corrected write set, or publish a resolution that carries the value.",
|
|
26
|
+
"vi": "Khai lại write set cho đúng, hoặc publish một resolution có mang giá trị đó."
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 9,
|
|
3
|
+
"id": "frontend.source.apply",
|
|
4
|
+
"domain": "frontend",
|
|
5
|
+
"job": "Write one already-resolved tree into product source inside a frozen owner ceiling and a declared file set, emitting only values the bound resolution receipt already contains.",
|
|
6
|
+
"package": "operator.md",
|
|
7
|
+
"errors": "errors.json",
|
|
8
|
+
"validator": "validate.mjs",
|
|
9
|
+
"selfTest": "self-test.mjs",
|
|
10
|
+
"resources": {
|
|
11
|
+
"profile": "luna",
|
|
12
|
+
"grammarBound": true,
|
|
13
|
+
"tools": {
|
|
14
|
+
"@tools/fileread": "context-aliases",
|
|
15
|
+
"@tools/sourcewrite": "declared-write-set",
|
|
16
|
+
"@tools/git": "commit-session-branch",
|
|
17
|
+
"@tools/imagegen": "judged"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frontend.source.apply
|
|
2
|
+
|
|
3
|
+
## Job
|
|
4
|
+
|
|
5
|
+
Write one already-resolved tree into product source on the session branch, inside a frozen owner
|
|
6
|
+
ceiling and a declared file set, emitting only values the bound resolution already contains, and
|
|
7
|
+
account for every byte that entered the repository in one commit.
|
|
8
|
+
|
|
9
|
+
## The single writer
|
|
10
|
+
|
|
11
|
+
This is the only operator in the frontend pipeline that writes product source. Direction decides what
|
|
12
|
+
to build and writes nothing; resolution decides every value and writes only its own artifact; the
|
|
13
|
+
audit observes and writes nothing. One writer exists so that one receipt can account for every byte
|
|
14
|
+
that entered the repository, which is impossible when three operators each write a little. Because it
|
|
15
|
+
is the only writer, it is also the only place a fabricated value could enter source, and it has no way
|
|
16
|
+
to produce one.
|
|
17
|
+
|
|
18
|
+
## The session branch
|
|
19
|
+
|
|
20
|
+
The operator never writes on the person's checked-out branch. It writes only on `session/<sessionId>`
|
|
21
|
+
of the routed checkout, in the git worktree the orchestrator prepared from the frozen head, and it
|
|
22
|
+
holds an exclusive lease on that worktree while it writes. The declared write set is committed exactly
|
|
23
|
+
once; `response.json` carries that one sha under `commits`, the `changes.md` Binding row reads
|
|
24
|
+
`@workspaces/fe` at the base head then the new sha on `session/<sessionId>`, and the next requests pin
|
|
25
|
+
`@workspaces/fe` at that sha. Nothing is pushed and nothing is merged here: `git.publish` owns both.
|
|
26
|
+
|
|
27
|
+
## No invented value
|
|
28
|
+
|
|
29
|
+
Every class the write produces already appears in the bound resolution's class inventory, and every
|
|
30
|
+
identifier it carries into a claim already appears in the applied rule inventory. Both lists are
|
|
31
|
+
complete and frozen. A class absent from the resolution is `WRITE_REJECTED`: there is no rounding to a
|
|
32
|
+
nearby value, no copying from a neighbouring file, and no reformatting that changes a step. A file the
|
|
33
|
+
write would touch that the write set does not declare is `WRITE_REJECTED` too, even when it plainly
|
|
34
|
+
needs the change; the correct next step is a corrected write set, not a wider write. A declared path
|
|
35
|
+
whose owner root does not contain it is `OWNER_CONFLICT`, because owner membership alone is not the
|
|
36
|
+
ceiling. The inventory check runs on the projection, before anything is written, so a rejected
|
|
37
|
+
application leaves source untouched.
|
|
38
|
+
|
|
39
|
+
## An application-owned node becomes an empty leaf
|
|
40
|
+
|
|
41
|
+
When the direction marks a node as owned by the application, a canvas for instance, the projection
|
|
42
|
+
writes an empty leaf file that carries that node's contract, its props and its states, so the tree
|
|
43
|
+
compiles and the surface can be rendered and measured. The operator never writes that leaf's logic:
|
|
44
|
+
the contract is what the direction decided, and the behaviour behind it belongs to whoever owns the
|
|
45
|
+
node.
|
|
46
|
+
|
|
47
|
+
## Unchanged is a measurement
|
|
48
|
+
|
|
49
|
+
Every declared path is hashed before the projection and after the commit. A path whose projection
|
|
50
|
+
differs from its current content is created or modified; a path whose projection equals it is recorded
|
|
51
|
+
`unchanged`. After the commit the tree is read back and compared against the resolved tree, and a
|
|
52
|
+
difference is `WRITE_REJECTED` rather than a silent success. Under `mode = dry` nothing is written at
|
|
53
|
+
all: the branch emits the plan in `response/data/writes.json` with a null commit and stops there.
|
|
54
|
+
|
|
55
|
+
## Boundary
|
|
56
|
+
|
|
57
|
+
The operator writes the declared write-set paths on the session branch of `@workspaces/fe`, each under
|
|
58
|
+
a mutable owner root, and its own `response/`. It does not write a class, value or rule identifier
|
|
59
|
+
absent from the bound resolution, decide a presentation value, choose a Grammar component, restructure
|
|
60
|
+
the tree, write the logic of an application-owned leaf, touch a file outside the declared write set,
|
|
61
|
+
commit to any other branch, push, merge, edit knowledge, publish Grammar, change the resolution, start
|
|
62
|
+
or reconfigure runtime services, or record a verdict, score or pass claim on the applied source. It
|
|
63
|
+
knows what it wrote, never how it renders.
|
|
64
|
+
|
|
65
|
+
## Context
|
|
66
|
+
|
|
67
|
+
| Alias | Bind | Required |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `@workspaces/fe` | the routed frontend checkout at the frozen head; the write lands on its session branch and nowhere else | yes |
|
|
70
|
+
|
|
71
|
+
## Inputs
|
|
72
|
+
|
|
73
|
+
| Kind | From | Required |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| `frontend-presentation-resolution` | `frontend.presentation.resolve`, the resolved tree and, beside it, the frozen class and rule inventory | yes |
|
|
76
|
+
| `frontend-direction-decision` | `frontend.direction.decide`, the intent and the owner ceiling; never a source of values | yes |
|
|
77
|
+
|
|
78
|
+
## Requirements
|
|
79
|
+
|
|
80
|
+
| Field | Type | Default | Ask |
|
|
81
|
+
| --- | --- | --- | --- |
|
|
82
|
+
| `mode` | choice | apply | `apply` writes and commits, `dry` emits the plan and writes nothing |
|
|
83
|
+
| `resume` | token | null | The blocked branch's token when re-entering after a stop |
|
|
84
|
+
|
|
85
|
+
## Steps
|
|
86
|
+
|
|
87
|
+
| # | Step | Params | Reads | Writes | Stops with |
|
|
88
|
+
| --- | --- | --- | --- | --- | --- |
|
|
89
|
+
| 1 | Validate the gate and resume, and confirm the head | `resume`, `mode` | `request/request.json`, input `frontend-presentation-resolution`, @workspaces/fe at the frozen head | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
|
|
90
|
+
| 2 | Bind the resolution, the direction and the declared write set | — | inputs `frontend-presentation-resolution` (tree fingerprint, class and rule inventory, resolved tree) and `frontend-direction-decision` (intent and owner ceiling), @workspaces/fe (the declared paths and their owner roots) | — | `RESOLUTION_STALE`, `OWNER_CONFLICT` |
|
|
91
|
+
| 3 | Project the resolved tree onto the declared paths | — | input `frontend-presentation-resolution` (the resolved tree), @workspaces/fe (the declared write set) | — | — |
|
|
92
|
+
| 4 | Check every produced value against the inventory | `mode` | input `frontend-presentation-resolution` (the inventory beside the receipt) | `response/data/writes.json` | `WRITE_REJECTED` |
|
|
93
|
+
| 5 | Write atomically on the session branch and commit once | — | @workspaces/fe (the current content of each declared path, under an exclusive lease) | @workspaces/fe/branch/session, `response/data/writes.json`, @tools/sourcewrite, @tools/git, image assets the direction judged, @tools/imagegen | — |
|
|
94
|
+
| 6 | Read the tree back at the commit | — | @workspaces/fe at the commit | — | `WRITE_REJECTED` |
|
|
95
|
+
| 7 | Emit | — | everything above | `response/response.md`, `response/changes.md`, `response/response.json` | — |
|
|
96
|
+
|
|
97
|
+
Under `mode = dry` the branch stops after step 4 with the plan alone: `writes.json` carries a null
|
|
98
|
+
commit, `response.json` carries no commit, and the checkout is untouched. Under `apply`, step 5 writes
|
|
99
|
+
and commits exactly once and step 6 proves that the committed tree is the resolved tree. `changes.md`
|
|
100
|
+
is the record the next steps read: which paths moved, which claims they carry, which gates the
|
|
101
|
+
checkout pins for them, and which surfaces must now be observed.
|
|
102
|
+
|
|
103
|
+
## Outputs
|
|
104
|
+
|
|
105
|
+
| Kind | File | Type | Required |
|
|
106
|
+
| --- | --- | --- | --- |
|
|
107
|
+
| `frontend-source-application` | `response/response.md` | md | yes |
|
|
108
|
+
| `changes` | `response/changes.md` | md | yes |
|
|
109
|
+
| `writes` | `response/data/writes.json` | data | yes |
|
|
110
|
+
|
|
111
|
+
## Stops
|
|
112
|
+
|
|
113
|
+
| Code | Disposition |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| `INVALID_INPUT` | terminate |
|
|
116
|
+
| `SOURCE_DRIFT` | terminate |
|
|
117
|
+
| `OWNER_CONFLICT` | terminate |
|
|
118
|
+
| `RESOLUTION_STALE` | terminate |
|
|
119
|
+
| `WRITE_REJECTED` | terminate |
|
|
120
|
+
| `NO_PROGRESS` | terminate |
|
|
121
|
+
|
|
122
|
+
## Next
|
|
123
|
+
|
|
124
|
+
| When | Operator |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| the source is committed and the rendered surface must be measured | `frontend.surface.audit` |
|
|
127
|
+
| the source is committed and the checkout's own gates must run | `quality.verify` |
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frontend.source.apply
|
|
2
|
+
|
|
3
|
+
## Việc
|
|
4
|
+
|
|
5
|
+
Ghi một cây đã resolve vào source sản phẩm trên nhánh phiên, bên trong một trần owner đã đóng băng và
|
|
6
|
+
một tập file đã khai, chỉ phát ra những giá trị mà resolution được bind đã chứa sẵn, và chịu trách
|
|
7
|
+
nhiệm cho từng byte đã vào repository trong đúng một commit.
|
|
8
|
+
|
|
9
|
+
## Người ghi duy nhất
|
|
10
|
+
|
|
11
|
+
Đây là operator duy nhất trong chuỗi frontend ghi source sản phẩm. Direction quyết dựng cái gì và
|
|
12
|
+
không ghi gì; resolution quyết mọi giá trị và chỉ ghi artifact của chính nó; audit quan sát và không
|
|
13
|
+
ghi gì. Một người ghi duy nhất tồn tại để một biên nhận duy nhất chịu trách nhiệm cho từng byte đã vào
|
|
14
|
+
repository, điều bất khả thi khi ba operator mỗi anh ghi một ít. Vì là người ghi duy nhất, nó cũng là
|
|
15
|
+
chỗ duy nhất một giá trị bịa có thể lọt vào source, nên nó không có đường nào để tạo ra một giá trị
|
|
16
|
+
như vậy.
|
|
17
|
+
|
|
18
|
+
## Nhánh phiên
|
|
19
|
+
|
|
20
|
+
Operator không bao giờ ghi lên nhánh mà người dùng đang checkout. Nó chỉ ghi trên
|
|
21
|
+
`session/<sessionId>` của checkout được route, trong git worktree mà orchestrator đã dựng sẵn từ head
|
|
22
|
+
đóng băng, và nó giữ một lease độc quyền trên worktree ấy suốt lúc ghi. Write set đã khai được commit
|
|
23
|
+
đúng một lần; `response.json` mang đúng một sha đó trong `commits`, dòng Checkout của `changes.md` ghi
|
|
24
|
+
`@workspaces/fe` ở head gốc rồi tới sha mới trên `session/<sessionId>`, và các request sau ghim
|
|
25
|
+
`@workspaces/fe` ở sha đó. Ở đây không push và không merge: `git.publish` sở hữu cả hai việc ấy.
|
|
26
|
+
|
|
27
|
+
## Không có giá trị tự nghĩ ra
|
|
28
|
+
|
|
29
|
+
Mọi class mà lần ghi tạo ra đều đã có sẵn trong kho class của resolution được bind, và mọi identifier
|
|
30
|
+
nó mang vào một lời khai đều đã có sẵn trong kho luật đã áp dụng. Cả hai danh sách đều đầy đủ và đóng
|
|
31
|
+
băng. Một class không có trong resolution là `WRITE_REJECTED`: không làm tròn về giá trị gần, không
|
|
32
|
+
chép từ file bên cạnh, không định dạng lại theo kiểu đổi mất một bước thang. Một file mà lần ghi sẽ
|
|
33
|
+
chạm tới nhưng write set không khai cũng là `WRITE_REJECTED`, kể cả khi rõ ràng nó cần đổi; bước đúng
|
|
34
|
+
tiếp theo là sửa write set, không phải ghi rộng ra. Một path đã khai mà gốc owner của nó không chứa là
|
|
35
|
+
`OWNER_CONFLICT`, vì thuộc về owner nào chưa phải là trần. Phép kiểm kho chạy trên bản chiếu, trước
|
|
36
|
+
khi ghi bất cứ thứ gì, nên một lần áp dụng bị từ chối để source y nguyên.
|
|
37
|
+
|
|
38
|
+
## Node do ứng dụng sở hữu thành một file lá rỗng
|
|
39
|
+
|
|
40
|
+
Khi hướng đánh dấu một node là do ứng dụng sở hữu, chẳng hạn một canvas, bản chiếu ghi ra một file lá
|
|
41
|
+
rỗng mang contract của node đó, tức props và states của nó, để cây biên dịch được và bề mặt render ra
|
|
42
|
+
đo được. Operator không bao giờ viết phần logic của file lá ấy: contract là thứ hướng đã quyết, còn
|
|
43
|
+
hành vi phía sau nó thuộc về ai sở hữu node.
|
|
44
|
+
|
|
45
|
+
## Không đổi là một phép đo
|
|
46
|
+
|
|
47
|
+
Mọi path đã khai đều được băm trước bản chiếu và sau commit. Path có bản chiếu khác nội dung hiện tại
|
|
48
|
+
thì được tạo hoặc sửa; path có bản chiếu bằng đúng nội dung hiện tại được ghi `unchanged`. Sau commit,
|
|
49
|
+
cây được đọc lại và đối chiếu với cây đã resolve, và một chỗ lệch là `WRITE_REJECTED` chứ không phải
|
|
50
|
+
một thành công lặng lẽ. Dưới `mode = dry` không có gì được ghi cả: nhánh phát ra bản kế hoạch trong
|
|
51
|
+
`response/data/writes.json` với commit rỗng rồi dừng ở đó.
|
|
52
|
+
|
|
53
|
+
## Ranh giới
|
|
54
|
+
|
|
55
|
+
Operator ghi các path trong write set đã khai trên nhánh phiên của `@workspaces/fe`, mỗi path nằm
|
|
56
|
+
dưới một gốc owner được sửa, và ghi `response/` của chính nó. Nó không ghi class, giá trị hay
|
|
57
|
+
identifier vắng mặt trong resolution được bind, không quyết một giá trị trình bày, không chọn
|
|
58
|
+
component Grammar, không dựng lại cây, không viết logic của file lá do ứng dụng sở hữu, không chạm
|
|
59
|
+
file ngoài write set đã khai, không commit lên nhánh nào khác, không push, không merge, không sửa
|
|
60
|
+
knowledge, không publish Grammar, không đổi resolution, không khởi động hay cấu hình lại dịch vụ
|
|
61
|
+
runtime, và không ghi phán quyết, điểm số hay tuyên bố pass lên source đã áp dụng. Nó biết nó ghi gì,
|
|
62
|
+
không bao giờ biết nó render ra sao.
|
|
63
|
+
|
|
64
|
+
## Context
|
|
65
|
+
|
|
66
|
+
| Alias | Bind | Bắt buộc |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| `@workspaces/fe` | checkout frontend được route ở head đóng băng; lần ghi đáp xuống nhánh phiên của nó và không đâu khác | có |
|
|
69
|
+
|
|
70
|
+
## Đầu vào
|
|
71
|
+
|
|
72
|
+
| Kind | Từ đâu | Bắt buộc |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| `frontend-presentation-resolution` | `frontend.presentation.resolve`, cây đã resolve và, ngay bên cạnh, kho class và kho luật đã đóng băng | có |
|
|
75
|
+
| `frontend-direction-decision` | `frontend.direction.decide`, ý đồ và trần owner; không bao giờ là nguồn giá trị | có |
|
|
76
|
+
|
|
77
|
+
## Yêu cầu
|
|
78
|
+
|
|
79
|
+
| Field | Kiểu | Mặc định | Hỏi |
|
|
80
|
+
| --- | --- | --- | --- |
|
|
81
|
+
| `mode` | choice | apply | `apply` ghi rồi commit, `dry` chỉ phát bản kế hoạch và không ghi gì |
|
|
82
|
+
| `resume` | token | null | Token của nhánh bị chặn khi vào lại sau một mã dừng |
|
|
83
|
+
|
|
84
|
+
## Các bước
|
|
85
|
+
|
|
86
|
+
| # | Bước | Tham số | Đọc | Ghi | Dừng với |
|
|
87
|
+
| --- | --- | --- | --- | --- | --- |
|
|
88
|
+
| 1 | Kiểm gate, chạy lại, và xác nhận head | `resume`, `mode` | `request/request.json`, đầu vào `frontend-presentation-resolution`, @workspaces/fe ở head đóng băng | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
|
|
89
|
+
| 2 | Bind resolution, direction và write set đã khai | — | đầu vào `frontend-presentation-resolution` (fingerprint cây, kho class và kho luật, cây đã resolve) và `frontend-direction-decision` (ý đồ và trần owner), @workspaces/fe (các path đã khai và gốc owner của chúng) | — | `RESOLUTION_STALE`, `OWNER_CONFLICT` |
|
|
90
|
+
| 3 | Chiếu cây đã resolve lên các path đã khai | — | đầu vào `frontend-presentation-resolution` (cây đã resolve), @workspaces/fe (write set đã khai) | — | — |
|
|
91
|
+
| 4 | Đối chiếu mọi giá trị tạo ra với kho | `mode` | đầu vào `frontend-presentation-resolution` (kho nằm cạnh biên nhận) | `response/data/writes.json` | `WRITE_REJECTED` |
|
|
92
|
+
| 5 | Ghi nguyên khối trên nhánh phiên và commit một lần | — | @workspaces/fe (nội dung hiện tại của từng path đã khai, dưới một lease độc quyền) | @workspaces/fe/branch/session, `response/data/writes.json`, @tools/sourcewrite, @tools/git, asset ảnh mà direction phán là cần, @tools/imagegen | — |
|
|
93
|
+
| 6 | Đọc lại cây ở commit | — | @workspaces/fe ở commit | — | `WRITE_REJECTED` |
|
|
94
|
+
| 7 | Phát | — | mọi thứ ở trên | `response/response.md`, `response/changes.md`, `response/response.json` | — |
|
|
95
|
+
|
|
96
|
+
Dưới `mode = dry`, nhánh dừng sau bước 4 với riêng bản kế hoạch: `writes.json` mang commit rỗng,
|
|
97
|
+
`response.json` không mang commit nào, và checkout y nguyên. Dưới `apply`, bước 5 ghi rồi commit đúng
|
|
98
|
+
một lần và bước 6 chứng minh rằng cây đã commit đúng là cây đã resolve. `changes.md` là bản ghi mà
|
|
99
|
+
các bước sau đọc: path nào đã dịch chuyển, chúng mang lời khai nào, checkout ghim cổng nào cho chúng,
|
|
100
|
+
và bề mặt nào bây giờ phải được quan sát.
|
|
101
|
+
|
|
102
|
+
## Đầu ra
|
|
103
|
+
|
|
104
|
+
| Kind | File | Kiểu | Bắt buộc |
|
|
105
|
+
| --- | --- | --- | --- |
|
|
106
|
+
| `frontend-source-application` | `response/response.md` | md | có |
|
|
107
|
+
| `changes` | `response/changes.md` | md | có |
|
|
108
|
+
| `writes` | `response/data/writes.json` | data | có |
|
|
109
|
+
|
|
110
|
+
## Dừng
|
|
111
|
+
|
|
112
|
+
| Code | Xử lý |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| `INVALID_INPUT` | terminate |
|
|
115
|
+
| `SOURCE_DRIFT` | terminate |
|
|
116
|
+
| `OWNER_CONFLICT` | terminate |
|
|
117
|
+
| `RESOLUTION_STALE` | terminate |
|
|
118
|
+
| `WRITE_REJECTED` | terminate |
|
|
119
|
+
| `NO_PROGRESS` | terminate |
|
|
120
|
+
|
|
121
|
+
## Kế tiếp
|
|
122
|
+
|
|
123
|
+
| Khi | Operator |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| source đã commit và bề mặt render ra phải được đo | `frontend.surface.audit` |
|
|
126
|
+
| source đã commit và các cổng của chính checkout phải chạy | `quality.verify` |
|