@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,277 @@
|
|
|
1
|
+
// business.decide's own law over one branch, on top of the shared step check: the mode decides which
|
|
2
|
+
// half ran, and a reconcile branch binds the delivered source it reconciles against; the head is
|
|
3
|
+
// exactly one feature directory below the businesses root; the model, the response and the frozen
|
|
4
|
+
// matrix are the same decision read three ways; the lineage transition is legal and agrees with both
|
|
5
|
+
// states; an implemented head reconciles against delivered source with no discrepancy; the matrix
|
|
6
|
+
// carries exactly one row per declared dimension and no other; a mandatory dimension and a discovered
|
|
7
|
+
// lifecycle branch are never not-applicable; each disposition carries the substance it owes and no
|
|
8
|
+
// more; every enforcing row rests on a fact claim from claims.json; every discovered consumer is
|
|
9
|
+
// disposed once, under the dimension it was discovered in; and a blocked branch publishes no head.
|
|
10
|
+
import { existsSync } from 'node:fs';
|
|
11
|
+
import { readFile } from 'node:fs/promises';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import process from 'node:process';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { validateStep } from '../../scripts/validate-step.mjs';
|
|
16
|
+
import { tableUnder } from '../../scripts/validate-response.mjs';
|
|
17
|
+
|
|
18
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
19
|
+
|
|
20
|
+
// A promise always has an actor, an entry, a purchase effect, a settlement, an idempotency answer, an
|
|
21
|
+
// entitlement consumer, and a denial path. Marking one of these not-applicable, once it is declared,
|
|
22
|
+
// is how "full access" was published while its downstream consumers were never proved.
|
|
23
|
+
const MANDATORY_DIMENSIONS = new Set([
|
|
24
|
+
'actor-eligibility', 'offer-entry', 'read-entry', 'purchase-side-effect', 'settlement',
|
|
25
|
+
'idempotency', 'entitlement-consumer', 'denial',
|
|
26
|
+
]);
|
|
27
|
+
const LEGAL_TRANSITIONS = {
|
|
28
|
+
'absent->pending': { from: null, to: 'pending' },
|
|
29
|
+
'pending->in-progress': { from: 'pending', to: 'in-progress' },
|
|
30
|
+
'pending->rejected': { from: 'pending', to: 'rejected' },
|
|
31
|
+
'in-progress->implemented': { from: 'in-progress', to: 'implemented' },
|
|
32
|
+
'in-progress->rejected': { from: 'in-progress', to: 'rejected' },
|
|
33
|
+
'implemented->in-progress': { from: 'implemented', to: 'in-progress' },
|
|
34
|
+
'implemented->rejected': { from: 'implemented', to: 'rejected' },
|
|
35
|
+
'rejected->pending': { from: 'rejected', to: 'pending' },
|
|
36
|
+
};
|
|
37
|
+
const BUSINESSES_ROOT = /\.worktrees\/businesses$/;
|
|
38
|
+
const empty = (v) => v === undefined || v === null || v === '' || v === '—';
|
|
39
|
+
const fields = (rows) => Object.fromEntries((rows ?? []).map(([k, v]) => [k, v]));
|
|
40
|
+
|
|
41
|
+
export async function validateBusinessStep(branchDir, root = ROOT) {
|
|
42
|
+
const base = await validateStep(root, branchDir);
|
|
43
|
+
const errors = [...base.errors];
|
|
44
|
+
const { request, response, requirements = {}, present = new Set() } = base;
|
|
45
|
+
if (!response || response.operatorId !== 'business.decide') return { errors };
|
|
46
|
+
const has = (f) => existsSync(path.join(branchDir, f));
|
|
47
|
+
const read = (f) => readFile(path.join(branchDir, f), 'utf8');
|
|
48
|
+
const mode = requirements.mode ?? 'model';
|
|
49
|
+
const inputs = request?.inputs ?? {};
|
|
50
|
+
|
|
51
|
+
// The Inputs table cannot say "required under one mode", so the mode says it here.
|
|
52
|
+
if (mode === 'reconcile' && empty(inputs['backend-source-application'])) {
|
|
53
|
+
errors.push('request.json: mode reconcile reconciles against delivered source, so input backend-source-application is required');
|
|
54
|
+
}
|
|
55
|
+
if (mode === 'model' && !empty(inputs['backend-source-application'])) {
|
|
56
|
+
errors.push('request.json: mode model models the promise and reads no delivered source, so input backend-source-application is refused');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// A blocked branch publishes no head and freezes no coverage fingerprint.
|
|
60
|
+
if (response.status === 'blocked') {
|
|
61
|
+
if (present.has('coverage-matrix')) errors.push('response/response.json: a blocked branch cannot freeze a coverage matrix');
|
|
62
|
+
if (present.has('model')) errors.push('response/response.json: a blocked branch cannot publish a head');
|
|
63
|
+
}
|
|
64
|
+
if (response.status === 'done' && mode === 'model' && !present.has('coverage-matrix')) {
|
|
65
|
+
errors.push('response/response.json: mode model freezes the coverage matrix, so coverage-matrix must be in fields');
|
|
66
|
+
}
|
|
67
|
+
if (mode === 'reconcile' && present.has('coverage-matrix')) {
|
|
68
|
+
errors.push('response/response.json: mode reconcile models nothing, so it may not freeze a new coverage matrix');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const declaredDimensions = Array.isArray(requirements.dimensions) ? requirements.dimensions : [];
|
|
72
|
+
|
|
73
|
+
let claims = new Map();
|
|
74
|
+
if (present.has('claims') && has('response/data/claims.json')) {
|
|
75
|
+
let doc = null;
|
|
76
|
+
try { doc = JSON.parse(await read('response/data/claims.json')); } catch { doc = null; }
|
|
77
|
+
if (doc) {
|
|
78
|
+
if (!empty(requirements.featureId) && doc.featureId !== requirements.featureId) errors.push(`response/data/claims.json: featureId ${doc.featureId} differs from the request's ${requirements.featureId}`);
|
|
79
|
+
for (const claim of doc.claims ?? []) {
|
|
80
|
+
if (claims.has(claim.claimId)) errors.push(`response/data/claims.json: claim ${claim.claimId} is declared more than once`);
|
|
81
|
+
claims.set(claim.claimId, claim);
|
|
82
|
+
if (claim.lineEnd < claim.lineStart) errors.push(`response/data/claims.json: claim ${claim.claimId} has an inverted line range`);
|
|
83
|
+
if (claim.kind === 'fact' && claim.sourceHead === null) errors.push(`response/data/claims.json: fact claim ${claim.claimId} must bind the observed source head`);
|
|
84
|
+
if (claim.kind === 'contradiction') errors.push(`response/data/claims.json: claim ${claim.claimId} is a contradiction, which stops the branch with CONTRADICTION_UNRESOLVED rather than being published`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let matrix = null;
|
|
90
|
+
if (present.has('coverage-matrix') && has('response/data/coverage-matrix.json')) {
|
|
91
|
+
try { matrix = JSON.parse(await read('response/data/coverage-matrix.json')); } catch { matrix = null; }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (matrix) {
|
|
95
|
+
if (!empty(requirements.featureId) && matrix.featureId !== requirements.featureId) {
|
|
96
|
+
errors.push(`response/data/coverage-matrix.json: featureId ${matrix.featureId} differs from the request's ${requirements.featureId}`);
|
|
97
|
+
}
|
|
98
|
+
if (declaredDimensions.length) {
|
|
99
|
+
for (const dimension of declaredDimensions) if (!matrix.dimensions.includes(dimension)) errors.push(`response/data/coverage-matrix.json: the request declared dimension ${dimension}, which the matrix does not carry`);
|
|
100
|
+
for (const dimension of matrix.dimensions) if (!declaredDimensions.includes(dimension)) errors.push(`response/data/coverage-matrix.json: dimension ${dimension} was never declared by the request`);
|
|
101
|
+
}
|
|
102
|
+
const rowByDimension = new Map();
|
|
103
|
+
for (const row of matrix.rows) {
|
|
104
|
+
if (rowByDimension.has(row.dimension)) errors.push(`response/data/coverage-matrix.json: coverage dimension ${row.dimension} appears in more than one row`);
|
|
105
|
+
rowByDimension.set(row.dimension, row);
|
|
106
|
+
if (!matrix.dimensions.includes(row.dimension)) errors.push(`response/data/coverage-matrix.json: row ${row.dimension} is not one of the declared dimensions`);
|
|
107
|
+
}
|
|
108
|
+
for (const dimension of matrix.dimensions) {
|
|
109
|
+
if (!rowByDimension.has(dimension)) errors.push(`response/data/coverage-matrix.json: coverage dimension ${dimension} has no disposition`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const consumerIds = new Set();
|
|
113
|
+
for (const consumer of matrix.discoveredConsumers) {
|
|
114
|
+
if (consumerIds.has(consumer.consumerId)) errors.push(`response/data/coverage-matrix.json: discovered consumer ${consumer.consumerId} is declared more than once`);
|
|
115
|
+
consumerIds.add(consumer.consumerId);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const claimedConsumers = new Map();
|
|
119
|
+
for (const row of matrix.rows) {
|
|
120
|
+
const { dimension, disposition } = row;
|
|
121
|
+
if (disposition === 'not-applicable' && MANDATORY_DIMENSIONS.has(dimension)) {
|
|
122
|
+
errors.push(`response/data/coverage-matrix.json: ${dimension} is mandatory for a published promise and cannot be marked not-applicable`);
|
|
123
|
+
}
|
|
124
|
+
if (disposition === 'preserve' || disposition === 'replace') {
|
|
125
|
+
if (row.enforcementOwner === null) errors.push(`response/data/coverage-matrix.json: ${dimension} names no enforcement owner`);
|
|
126
|
+
if (row.sourceRef === null) errors.push(`response/data/coverage-matrix.json: ${dimension} names no enforcing source`);
|
|
127
|
+
if (row.positiveProofRef === null) errors.push(`response/data/coverage-matrix.json: ${dimension} has no positive proof`);
|
|
128
|
+
if (row.negativeProofRef === null) errors.push(`response/data/coverage-matrix.json: ${dimension} has no negative proof, so nothing shows the promise is denied when it should be`);
|
|
129
|
+
}
|
|
130
|
+
if (disposition === 'retire') {
|
|
131
|
+
if (row.enforcementOwner === null) errors.push(`response/data/coverage-matrix.json: retired ${dimension} names no owner of the retirement`);
|
|
132
|
+
if (row.sourceRef === null) errors.push(`response/data/coverage-matrix.json: retired ${dimension} names no source where the path was closed`);
|
|
133
|
+
if (row.positiveProofRef === null) errors.push(`response/data/coverage-matrix.json: retired ${dimension} has no proof the path is closed`);
|
|
134
|
+
}
|
|
135
|
+
if (disposition === 'defer') {
|
|
136
|
+
if (row.deferralRef === null) errors.push(`response/data/coverage-matrix.json: deferred ${dimension} names no deferral owner reference`);
|
|
137
|
+
if (row.positiveProofRef !== null || row.negativeProofRef !== null) errors.push(`response/data/coverage-matrix.json: deferred ${dimension} cannot claim proof for work that has not happened`);
|
|
138
|
+
}
|
|
139
|
+
if (disposition === 'not-applicable' && (row.enforcementOwner !== null || row.sourceRef !== null || row.positiveProofRef !== null || row.negativeProofRef !== null || row.deferralRef !== null || row.consumerIds.length > 0 || row.claimIds.length > 0)) {
|
|
140
|
+
errors.push(`response/data/coverage-matrix.json: ${dimension} is marked not-applicable but still carries owners, proof, consumers, or claims`);
|
|
141
|
+
}
|
|
142
|
+
for (const consumerId of row.consumerIds) {
|
|
143
|
+
if (claimedConsumers.has(consumerId)) errors.push(`response/data/coverage-matrix.json: consumer ${consumerId} is disposed in more than one row`);
|
|
144
|
+
claimedConsumers.set(consumerId, dimension);
|
|
145
|
+
}
|
|
146
|
+
// An example or a screenshot illustrates a promise; it never creates one.
|
|
147
|
+
if (claims.size) {
|
|
148
|
+
for (const claimId of row.claimIds) if (!claims.has(claimId)) errors.push(`response/data/coverage-matrix.json: ${dimension} cites claim ${claimId}, which claims.json does not carry`);
|
|
149
|
+
if (disposition === 'preserve' || disposition === 'replace' || disposition === 'retire') {
|
|
150
|
+
if (!row.claimIds.some((claimId) => claims.get(claimId)?.kind === 'fact')) errors.push(`response/data/coverage-matrix.json: ${dimension} asserts enforcement without one fact claim; examples and intent never create product truth`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// The rule the "full access" release lacked: nothing discovered may pass without a disposition.
|
|
156
|
+
for (const consumer of matrix.discoveredConsumers) {
|
|
157
|
+
const disposedAt = claimedConsumers.get(consumer.consumerId);
|
|
158
|
+
if (disposedAt === undefined) errors.push(`response/data/coverage-matrix.json: discovered consumer ${consumer.consumerId} has no disposition in the coverage matrix`);
|
|
159
|
+
else if (disposedAt !== consumer.dimension) errors.push(`response/data/coverage-matrix.json: discovered consumer ${consumer.consumerId} was discovered under ${consumer.dimension} but disposed under ${disposedAt}`);
|
|
160
|
+
}
|
|
161
|
+
for (const consumerId of claimedConsumers.keys()) {
|
|
162
|
+
if (!matrix.discoveredConsumers.some((item) => item.consumerId === consumerId)) errors.push(`response/data/coverage-matrix.json: row consumer ${consumerId} was never discovered, so no evidence supports it`);
|
|
163
|
+
}
|
|
164
|
+
for (const branch of matrix.discoveredLifecycleBranches) {
|
|
165
|
+
const row = rowByDimension.get(branch);
|
|
166
|
+
if (!row) errors.push(`response/data/coverage-matrix.json: lifecycle branch ${branch} was discovered in the source and has no disposition`);
|
|
167
|
+
else if (row.disposition === 'not-applicable') errors.push(`response/data/coverage-matrix.json: lifecycle branch ${branch} was discovered in the source and cannot be marked not-applicable`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let model = null;
|
|
172
|
+
if (present.has('model') && has('response/data/model.json')) {
|
|
173
|
+
try { model = JSON.parse(await read('response/data/model.json')); } catch { model = null; }
|
|
174
|
+
}
|
|
175
|
+
if (model) {
|
|
176
|
+
if (!empty(requirements.featureId) && model.featureId !== requirements.featureId) errors.push(`response/data/model.json: featureId ${model.featureId} differs from the request's ${requirements.featureId}`);
|
|
177
|
+
if (model.mode !== mode) errors.push(`response/data/model.json: mode ${model.mode} differs from the request's ${mode}`);
|
|
178
|
+
if (!empty(requirements.targetState) && model.state !== requirements.targetState) errors.push(`response/data/model.json: state ${model.state} differs from the request's target ${requirements.targetState}`);
|
|
179
|
+
|
|
180
|
+
// The head is one flat segment below the businesses root, and the root is the businesses worktree.
|
|
181
|
+
const headRef = String(model.headRef ?? '');
|
|
182
|
+
const cut = headRef.lastIndexOf('/features/');
|
|
183
|
+
if (cut === -1) errors.push(`response/data/model.json: head ${headRef} must be exactly <businesses root>/features/${model.featureId}: the feature directory, with no project segment below the businesses root`);
|
|
184
|
+
else {
|
|
185
|
+
if (!BUSINESSES_ROOT.test(headRef.slice(0, cut))) errors.push(`response/data/model.json: head ${headRef} is not under a .worktrees/businesses root`);
|
|
186
|
+
if (headRef.slice(cut + '/features/'.length) !== model.featureId) errors.push(`response/data/model.json: head ${headRef} must name the feature ${model.featureId}, with no project segment below the businesses root`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Lifecycle. A state that arrives through an unlisted transition has no lineage behind it.
|
|
190
|
+
const transition = LEGAL_TRANSITIONS[model.lineage.transition];
|
|
191
|
+
if (!transition) errors.push(`response/data/model.json: transition ${model.lineage.transition} is not a legal lifecycle transition`);
|
|
192
|
+
else {
|
|
193
|
+
if (transition.from !== model.lineage.previousState) errors.push(`response/data/model.json: transition ${model.lineage.transition} contradicts previous state ${String(model.lineage.previousState)}`);
|
|
194
|
+
if (transition.to !== model.state) errors.push(`response/data/model.json: transition ${model.lineage.transition} contradicts published state ${model.state}`);
|
|
195
|
+
}
|
|
196
|
+
if (model.lineage.previousState === null && model.lineage.previousHeadRef !== null) errors.push('response/data/model.json: a first publication cannot name a previous head');
|
|
197
|
+
if (model.lineage.previousState !== null && model.lineage.previousHeadRef === null) errors.push('response/data/model.json: a transition from an existing state must name the previous head, because rejection preserves lineage');
|
|
198
|
+
|
|
199
|
+
if (matrix && model.coverageFingerprint !== matrix.fingerprint) errors.push('response/data/model.json: coverageFingerprint must equal the frozen matrix fingerprint, or backend and UAT cannot correlate the same matrix');
|
|
200
|
+
if (mode === 'model' && model.reconciliation !== null) errors.push('response/data/model.json: mode model reconciles nothing, so reconciliation must be null');
|
|
201
|
+
if (mode === 'reconcile') {
|
|
202
|
+
if (model.reconciliation === null) errors.push('response/data/model.json: mode reconcile must carry the reconciliation it performed');
|
|
203
|
+
else if (response.status === 'done' && model.reconciliation.discrepancies.length) errors.push(`response/data/model.json: ${model.reconciliation.discrepancies.length} reconciliation discrepancy or discrepancies remain, so the branch is RECONCILIATION_DISCREPANCY rather than done`);
|
|
204
|
+
}
|
|
205
|
+
// implemented is never published on the strength of a plan.
|
|
206
|
+
if (model.state === 'implemented' && model.reconciliation === null) errors.push('response/data/model.json: an implemented head requires reconciliation against delivered source');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (present.has('business-promise-authority') && has('response/response.md')) {
|
|
210
|
+
const text = await read('response/response.md');
|
|
211
|
+
const binding = fields(tableUnder(text, '## Binding'));
|
|
212
|
+
const lineage = fields(tableUnder(text, '## Lineage'));
|
|
213
|
+
const featureId = binding.Feature;
|
|
214
|
+
|
|
215
|
+
if (!empty(requirements.featureId) && featureId !== requirements.featureId) errors.push(`response/response.md: Feature ${featureId} differs from the request's ${requirements.featureId}`);
|
|
216
|
+
if (binding.Mode !== mode) errors.push(`response/response.md: Mode ${binding.Mode} differs from the request's ${mode}`);
|
|
217
|
+
if (!empty(requirements.targetState) && binding['Target state'] !== requirements.targetState) errors.push(`response/response.md: Target state ${binding['Target state']} differs from the request's ${requirements.targetState}`);
|
|
218
|
+
if (model) {
|
|
219
|
+
if (binding.Head !== model.headRef) errors.push(`response/response.md: Head ${binding.Head} differs from the published head ${model.headRef}`);
|
|
220
|
+
if (lineage.Transition !== model.lineage.transition) errors.push(`response/response.md: Transition ${lineage.Transition} differs from the model's ${model.lineage.transition}`);
|
|
221
|
+
if (binding['Claims fingerprint'] !== model.claimsFingerprint) errors.push('response/response.md: Claims fingerprint must equal the model claimsFingerprint');
|
|
222
|
+
}
|
|
223
|
+
if (matrix) {
|
|
224
|
+
if (binding['Coverage fingerprint'] !== matrix.fingerprint) errors.push('response/response.md: Coverage fingerprint must equal the coverage matrix fingerprint, or backend and UAT cannot correlate the same matrix');
|
|
225
|
+
if (featureId !== matrix.featureId) errors.push(`response/response.md: Feature ${featureId} differs from the matrix feature ${matrix.featureId}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Cited claims: the response says exactly what claims.json holds, read by a person.
|
|
229
|
+
const cited = new Set();
|
|
230
|
+
for (const [claim, kind] of tableUnder(text, '## Cited claims') ?? []) {
|
|
231
|
+
if (cited.has(claim)) errors.push(`response/response.md: cited claim ${claim} is listed more than once`);
|
|
232
|
+
cited.add(claim);
|
|
233
|
+
if (claims.size) {
|
|
234
|
+
const record = claims.get(claim);
|
|
235
|
+
if (!record) errors.push(`response/response.md: cited claim ${claim} is absent from claims.json`);
|
|
236
|
+
else if (record.kind !== kind) errors.push(`response/response.md: claim ${claim} is ${kind} here and ${record.kind} in claims.json`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
for (const claimId of claims.keys()) if (!cited.has(claimId)) errors.push(`response/response.md: claims.json carries ${claimId}, which Cited claims omits`);
|
|
240
|
+
|
|
241
|
+
// The response table and the frozen matrix are the same decision, read two ways.
|
|
242
|
+
const mdRows = tableUnder(text, '## Coverage') ?? [];
|
|
243
|
+
if (matrix) {
|
|
244
|
+
if (mdRows.length !== matrix.rows.length) errors.push(`response/response.md: Coverage has ${mdRows.length} rows, the matrix has ${matrix.rows.length}`);
|
|
245
|
+
const byDimension = new Map(matrix.rows.map((r) => [r.dimension, r]));
|
|
246
|
+
for (const [dimension, disposition] of mdRows) {
|
|
247
|
+
const row = byDimension.get(dimension);
|
|
248
|
+
if (!row) { errors.push(`response/response.md: Coverage names dimension ${dimension}, which the matrix does not carry`); continue; }
|
|
249
|
+
if (row.disposition !== disposition) errors.push(`response/response.md: ${dimension} is ${disposition} here and ${row.disposition} in the matrix`);
|
|
250
|
+
}
|
|
251
|
+
} else if (mdRows.length) errors.push('response/response.md: Coverage carries rows, but no coverage matrix was frozen in this mode');
|
|
252
|
+
|
|
253
|
+
const reconciliation = tableUnder(text, '## Reconciliation') ?? [];
|
|
254
|
+
if (mode === 'reconcile') {
|
|
255
|
+
if (reconciliation.length === 0) errors.push('response/response.md: a reconcile branch requires reconciliation against delivered source');
|
|
256
|
+
for (const [dimension, delivered, discrepancy] of reconciliation) {
|
|
257
|
+
if (empty(delivered)) errors.push(`response/response.md: reconciliation of ${dimension} names no delivered evidence`);
|
|
258
|
+
if (!empty(discrepancy) && response.status === 'done') errors.push(`response/response.md: a published head cannot carry the unresolved reconciliation discrepancy on ${dimension}`);
|
|
259
|
+
}
|
|
260
|
+
} else if (reconciliation.length) errors.push('response/response.md: mode model reconciles nothing, so Reconciliation carries no rows');
|
|
261
|
+
|
|
262
|
+
// A published receipt cannot retain an error finding.
|
|
263
|
+
if (response.status === 'done') {
|
|
264
|
+
for (const [code, severity] of tableUnder(text, '## Findings') ?? []) {
|
|
265
|
+
if (severity === 'error') errors.push(`response/response.md: finding ${code} is still an open error, so the promise cannot be published`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { errors };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
273
|
+
const target = process.argv[2];
|
|
274
|
+
if (!target) { process.stderr.write('usage: node validate.mjs <session>/step-N/parallel-M\n'); process.exit(2); }
|
|
275
|
+
const { errors } = await validateBusinessStep(path.resolve(target));
|
|
276
|
+
if (errors.length) { process.stderr.write(`${errors.join('\n')}\n`); process.exitCode = 1; } else process.stdout.write('valid business.decide branch\n');
|
|
277
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 9,
|
|
3
|
+
"note": "Stop codes only content.generate emits. Same entry shape as operators/errors.json, scope implicit. Shared codes (INVALID_INPUT, SOURCE_DRIFT, NO_PROGRESS) come from operators/errors.json. Every domain below is the one the old package's owningDomain enum published for this operator: content, curriculum, engineering, workspace, caller. routing.json answers content with a resume of this operator, and curriculum and engineering each with a person, which is not the same person as caller: the curriculum owner and the engineer who owns the generator are named separately on purpose.",
|
|
4
|
+
"codes": {
|
|
5
|
+
"BRIEF_UNBOUND": {
|
|
6
|
+
"domain": "curriculum",
|
|
7
|
+
"disposition": "terminate",
|
|
8
|
+
"meaning": {
|
|
9
|
+
"en": "The teacher brief cannot be frozen from the bound curriculum and source evidence.",
|
|
10
|
+
"vi": "Không đóng băng được brief người dạy từ chương trình học và bằng chứng nguồn đã ràng."
|
|
11
|
+
},
|
|
12
|
+
"resume": {
|
|
13
|
+
"en": "Supply the missing curriculum or source evidence.",
|
|
14
|
+
"vi": "Bổ sung chương trình học hoặc bằng chứng nguồn còn thiếu."
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"OUTCOME_UNCOVERED": {
|
|
18
|
+
"domain": "content",
|
|
19
|
+
"disposition": "terminate",
|
|
20
|
+
"meaning": {
|
|
21
|
+
"en": "A declared language edition leaves a published learning outcome uncovered.",
|
|
22
|
+
"vi": "Một bản viết đã khai bỏ trống một kết quả học tập đã công bố."
|
|
23
|
+
},
|
|
24
|
+
"resume": {
|
|
25
|
+
"en": "Rewrite the edition, or narrow the brief.",
|
|
26
|
+
"vi": "Viết lại bản đó, hoặc thu hẹp brief."
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"IMAGE_UNAVAILABLE": {
|
|
30
|
+
"domain": "engineering",
|
|
31
|
+
"disposition": "terminate",
|
|
32
|
+
"meaning": {
|
|
33
|
+
"en": "A required image cannot be generated to the brief's claims.",
|
|
34
|
+
"vi": "Không sinh được hình ảnh bắt buộc theo các tuyên bố của brief."
|
|
35
|
+
},
|
|
36
|
+
"resume": {
|
|
37
|
+
"en": "Provide a working generator, or turn the image stage off.",
|
|
38
|
+
"vi": "Cấp một bộ sinh chạy được, hoặc tắt giai đoạn hình ảnh."
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"CODE_BUILD_FAILED": {
|
|
42
|
+
"domain": "content",
|
|
43
|
+
"disposition": "terminate",
|
|
44
|
+
"meaning": {
|
|
45
|
+
"en": "A declared implementation track does not build.",
|
|
46
|
+
"vi": "Một track cài đặt đã khai không build được."
|
|
47
|
+
},
|
|
48
|
+
"resume": {
|
|
49
|
+
"en": "Repair the track, then build it again.",
|
|
50
|
+
"vi": "Sửa track đó, rồi build lại."
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"E2E_FAILED": {
|
|
54
|
+
"domain": "content",
|
|
55
|
+
"disposition": "terminate",
|
|
56
|
+
"meaning": {
|
|
57
|
+
"en": "A declared executable check still fails when maxE2eIterations is spent.",
|
|
58
|
+
"vi": "Một phép kiểm chạy được đã khai vẫn hỏng khi đã tiêu hết maxE2eIterations."
|
|
59
|
+
},
|
|
60
|
+
"resume": {
|
|
61
|
+
"en": "Repair the implementation, or approve more iterations.",
|
|
62
|
+
"vi": "Sửa phần cài đặt, hoặc duyệt thêm số vòng."
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"CONTRACT_WEAKENED": {
|
|
66
|
+
"domain": "content",
|
|
67
|
+
"disposition": "terminate",
|
|
68
|
+
"meaning": {
|
|
69
|
+
"en": "The executable contract moved during the repair loop, so the proof measures nothing.",
|
|
70
|
+
"vi": "Contract chạy được bị dời trong vòng lặp sửa, nên bằng chứng không đo được gì."
|
|
71
|
+
},
|
|
72
|
+
"resume": {
|
|
73
|
+
"en": "Restore the contract and rerun without touching it.",
|
|
74
|
+
"vi": "Khôi phục contract và chạy lại mà không đụng vào nó."
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"REVIEW_REVISION_REQUIRED": {
|
|
78
|
+
"domain": "content",
|
|
79
|
+
"disposition": "fallback",
|
|
80
|
+
"meaning": {
|
|
81
|
+
"en": "The independent review returned a revision.",
|
|
82
|
+
"vi": "Phản biện độc lập trả về yêu cầu sửa."
|
|
83
|
+
},
|
|
84
|
+
"fallback": {
|
|
85
|
+
"en": "Repair exactly the artifacts the review's findings name, by owning stage, record the round under ## Fallbacks taken, and reopen the review exchange for the next round.",
|
|
86
|
+
"vi": "Sửa đúng những artifact mà các finding của phản biện nêu tên, theo giai đoạn chủ, ghi vòng đó dưới ## Fallbacks taken, rồi mở lại cuộc trao đổi phản biện cho vòng kế."
|
|
87
|
+
},
|
|
88
|
+
"resume": {
|
|
89
|
+
"en": "Nothing is asked of anyone; the branch revises and reviews again until maxReviewRounds is spent.",
|
|
90
|
+
"vi": "Không hỏi ai cả; nhánh tự sửa và phản biện lại cho tới khi hết maxReviewRounds."
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"REVIEW_ROUNDS_EXHAUSTED": {
|
|
94
|
+
"domain": "caller",
|
|
95
|
+
"disposition": "terminate",
|
|
96
|
+
"meaning": {
|
|
97
|
+
"en": "maxReviewRounds is spent and the review still returns a revision.",
|
|
98
|
+
"vi": "Đã tiêu hết maxReviewRounds mà phản biện vẫn trả về yêu cầu sửa."
|
|
99
|
+
},
|
|
100
|
+
"resume": {
|
|
101
|
+
"en": "Approve more rounds, or narrow the unit.",
|
|
102
|
+
"vi": "Duyệt thêm vòng, hoặc thu hẹp đơn vị."
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 9,
|
|
3
|
+
"id": "content.generate",
|
|
4
|
+
"domain": "content",
|
|
5
|
+
"job": "Generate or refactor one educational content unit in one linear pass: a teacher brief that constrains everything after it, one written edition per declared language, images made to a stated claim, code and executable checks that actually run, and an independent critique that receives the artifact without the producer's rationale.",
|
|
6
|
+
"package": "operator.md",
|
|
7
|
+
"errors": "errors.json",
|
|
8
|
+
"validator": "validate.mjs",
|
|
9
|
+
"selfTest": "self-test.mjs",
|
|
10
|
+
"resources": {
|
|
11
|
+
"profile": "luna",
|
|
12
|
+
"grammarBound": false,
|
|
13
|
+
"tools": {
|
|
14
|
+
"@tools/fileread": "context-aliases",
|
|
15
|
+
"@tools/shell": "declared-commands",
|
|
16
|
+
"@tools/websearch": "bounded",
|
|
17
|
+
"@tools/imagegen": "required",
|
|
18
|
+
"@tools/objectstorage": "read"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# content.generate
|
|
2
|
+
|
|
3
|
+
## Job
|
|
4
|
+
|
|
5
|
+
Generate or refactor one educational content unit in one linear pass: a teacher brief that constrains
|
|
6
|
+
everything after it, one written edition per declared language, images made to a stated claim, code
|
|
7
|
+
and executable checks that actually run, and an independent review that receives the artifacts
|
|
8
|
+
without the producer's rationale.
|
|
9
|
+
|
|
10
|
+
## The brief comes first and constrains what follows
|
|
11
|
+
|
|
12
|
+
The teacher brief is written before anything else, from curriculum and source evidence, in one fresh
|
|
13
|
+
execution that inherits no turns. It publishes the learner inputs, the observable outcomes, the claims
|
|
14
|
+
a visual may encode, the examples, and the explicit add, change, and remove dispositions, and it is
|
|
15
|
+
then fingerprinted so nothing downstream may extend it. Everything after it is measured against it: an
|
|
16
|
+
edition may only claim coverage of an outcome the brief published, an image may only encode a claim
|
|
17
|
+
the brief published, and every declared edition must cover the whole published outcome set before the
|
|
18
|
+
unit can ship. An edition that covers part of the brief is a partial lesson in one language and a
|
|
19
|
+
complete one in another, which is exactly the silent defect this check exists to catch. A change or
|
|
20
|
+
remove disposition names what it acts on, because a disposition with no target is a wish.
|
|
21
|
+
|
|
22
|
+
## Generated code must actually run
|
|
23
|
+
|
|
24
|
+
Every implementation track is built with the exact command `commands` names for it, and the exit code
|
|
25
|
+
is read and recorded in `response/data/e2e.json`. A non-zero exit code cannot ship, because the lesson
|
|
26
|
+
would tell a learner that code works on nobody's evidence. Step 7 runs the declared command per track
|
|
27
|
+
inside a bounded run, read and repair loop whose ceiling is `maxE2eIterations`; the loop lives inside
|
|
28
|
+
the step, so `E2E_FAILED` is raised once, when the iterations are spent, rather than on the first red
|
|
29
|
+
run. The loop may repair the implementation or the harness. It may never move the contract it is
|
|
30
|
+
measured by, so the contract fingerprint is taken before the first run and compared after the last
|
|
31
|
+
one: a test deleted, skipped, loosened, or special cased to make the run green is the failure that
|
|
32
|
+
comparison exists to catch, and it is `CONTRACT_WEAKENED`. A track carries its build command and its
|
|
33
|
+
exit code, a run carries its command, its exit code, and its named assertions, and neither carries a
|
|
34
|
+
sentence claiming success.
|
|
35
|
+
|
|
36
|
+
## The image is made to a stated intent
|
|
37
|
+
|
|
38
|
+
The visual is derived from the brief's claims, the prompt that states that intent is persisted beside
|
|
39
|
+
the result, and the result is inspected for legibility, hierarchy, and claim fidelity. Step 5 runs
|
|
40
|
+
only when `stageModes` turns the image on; with the image off the step produces nothing at all, no
|
|
41
|
+
image and no prompt, and the receipt records `STAGE_DISABLED`, because a decision that leaves no
|
|
42
|
+
record reads later as an omission. A claim the brief never published cannot appear, and a visual that
|
|
43
|
+
fails its own claim-fidelity inspection cannot ship. An image is not decoration added at the end; it
|
|
44
|
+
is one of the claims, drawn.
|
|
45
|
+
|
|
46
|
+
## The review is a nested exchange, and revision is a fallback with a ceiling
|
|
47
|
+
|
|
48
|
+
After the executable check, the branch pauses: it emits `response/response.json` with status `waiting`
|
|
49
|
+
and `awaiting { exchange: review, kind: content-review }`. The orchestrator writes
|
|
50
|
+
`review/request/request.json` with the produced artifacts and the claims they make as inputs, never
|
|
51
|
+
the producer's rationale, and runs a fresh agent on this operator's own profile with no inherited
|
|
52
|
+
turns; that agent is never the execution that wrote the brief or an edition, and it writes only
|
|
53
|
+
`review/response/`. It scores correctness, pedagogy, interview value and language, and, where those
|
|
54
|
+
stages ran, visual fidelity, code quality and executable proof. Approval requires every applicable
|
|
55
|
+
score at or above 85 and no open error finding. A returned revision is not a stop: it is the fallback
|
|
56
|
+
`REVIEW_REVISION_REQUIRED`, and the branch repairs exactly the artifacts the review's findings name,
|
|
57
|
+
by owning stage, then reopens the exchange for the next round. The ceiling is `maxReviewRounds`; when
|
|
58
|
+
those rounds are spent and the review still returns a revision, `REVIEW_ROUNDS_EXHAUSTED` terminates,
|
|
59
|
+
because a unit that has been rewritten to a reviewer's satisfaction after unbounded rounds was
|
|
60
|
+
rewritten by the reviewer. A unit that passes its own author's review has not been reviewed, which is
|
|
61
|
+
why shared executions, inherited turns, and producer rationale are three separate refusals.
|
|
62
|
+
|
|
63
|
+
## Boundary
|
|
64
|
+
|
|
65
|
+
Context is read-only. The operator writes only `response/` of its own branch: the brief, the receipt,
|
|
66
|
+
the machine record of the build and the executable check, and the editions, image, prompt and tracks
|
|
67
|
+
under `response/artifacts/`; the review agent writes only `review/response/`. It runs only the
|
|
68
|
+
declared build and executable-check commands. It never writes an article before the brief is frozen or
|
|
69
|
+
beyond what the brief published, claims a learning outcome, example, or visual claim the brief does not
|
|
70
|
+
publish, reports a build or executable check that was not run or whose exit code was not read, changes
|
|
71
|
+
the executable contract during a repair loop, lets the producer of an artifact perform, steer, or
|
|
72
|
+
brief its own review, or approves a unit while any applicable score sits below the published minimum.
|
|
73
|
+
It does not edit the curriculum, publish the unit to the served object, or claim any readiness beyond
|
|
74
|
+
the checks it actually ran.
|
|
75
|
+
|
|
76
|
+
## Context
|
|
77
|
+
|
|
78
|
+
| Alias | Bind | Required |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| `@remote/minio/<contentId>/<locale>` | the lesson as served, by the fingerprint of the fetched object; the unit being authored or revised | yes |
|
|
81
|
+
| `@worktrees/sessions/central-runtime` | the AI runtime that runs, reads, and repairs generated code, by generation | yes |
|
|
82
|
+
|
|
83
|
+
## Inputs
|
|
84
|
+
|
|
85
|
+
| Kind | From | Required |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| `content-generation-receipt` | a prior run of `content.generate` on the same unit; regression history, absent on the first pass | no |
|
|
88
|
+
|
|
89
|
+
## Requirements
|
|
90
|
+
|
|
91
|
+
| Field | Type | Default | Ask |
|
|
92
|
+
| --- | --- | --- | --- |
|
|
93
|
+
| `unit` | id | — | The content unit being authored or revised |
|
|
94
|
+
| `naturalLanguages` | list | vi | The languages the unit is written in; every one of them covers the whole outcome set |
|
|
95
|
+
| `implementationLanguages` | list | empty | The languages the implementation tracks are written in; empty means no code and no executable check |
|
|
96
|
+
| `stageModes` | list of `{image}` | image off | Which optional stages run; `image on` turns step 5 on |
|
|
97
|
+
| `commands` | list of `{language, buildCommand, checkCommand}` | the commands the unit declares | The exact command each track is built and checked with |
|
|
98
|
+
| `maxE2eIterations` | number 1–20 | 2 | How many run, read and repair iterations step 7 may spend before `E2E_FAILED` |
|
|
99
|
+
| `maxReviewRounds` | number 1–20 | 2 | How many review rounds the revision fallback may spend before `REVIEW_ROUNDS_EXHAUSTED` |
|
|
100
|
+
| `resume` | token | null | The blocked branch's token when re-entering after a stop |
|
|
101
|
+
|
|
102
|
+
## Steps
|
|
103
|
+
|
|
104
|
+
| # | Step | Params | Reads | Writes | Stops with |
|
|
105
|
+
| --- | --- | --- | --- | --- | --- |
|
|
106
|
+
| 1 | Validate the gate and resume | `resume` | `request/request.json`, input `content-generation-receipt` when present, @remote/minio/<contentId>/<locale> at the frozen unit binding | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
|
|
107
|
+
| 2 | Bind the served unit and the runtime | `unit` | @remote/minio/<contentId>/<locale> for curriculum and style as served, @worktrees/sessions/central-runtime for the runtime configuration, @tools/objectstorage | — | — |
|
|
108
|
+
| 3 | Write and freeze the brief | `naturalLanguages`, `implementationLanguages` | @remote/minio/<contentId>/<locale> for curriculum and source evidence, @tools/websearch | `response/brief.md` | `BRIEF_UNBOUND` |
|
|
109
|
+
| 4 | Write every declared edition | `naturalLanguages` | `response/brief.md` as the frozen brief | `response/artifacts/article.<language>.md` | `OUTCOME_UNCOVERED` |
|
|
110
|
+
| 5 | Generate the image to its claims, only when `stageModes` turns it on | `stageModes` | `response/brief.md` for the claims and the stated image intent | `response/artifacts/image.<name>`, `response/artifacts/prompt.<name>.txt`, @tools/imagegen | `IMAGE_UNAVAILABLE` |
|
|
111
|
+
| 6 | Implement every declared track | `implementationLanguages`, `commands` | `response/brief.md`, @worktrees/sessions/central-runtime where each build command runs | `response/artifacts/track.<language>.<extension>`, @tools/shell | `CODE_BUILD_FAILED` |
|
|
112
|
+
| 7 | Run the executable check, at most `maxE2eIterations` iterations | `maxE2eIterations` | @worktrees/sessions/central-runtime where each declared command runs | `response/data/e2e.json`, @tools/shell | `E2E_FAILED`, `CONTRACT_WEAKENED` |
|
|
113
|
+
| 8 | Await the review: pause, a fresh agent reads the artifacts without the producer's rationale, revise and reopen while rounds remain | `maxReviewRounds` | `review/response/review.md` once the exchange is done | `response/response.json` (waiting, awaiting review) | `REVIEW_REVISION_REQUIRED`, `REVIEW_ROUNDS_EXHAUSTED` |
|
|
114
|
+
| 9 | Write the receipt and emit | — | everything above | `response/response.md`, `response/response.json` | — |
|
|
115
|
+
|
|
116
|
+
Under the defaults the unit is one Vietnamese edition with no image, no track and no executable check,
|
|
117
|
+
and its quality rests entirely on step 8. A resume begins again at step 1, reuses only unchanged
|
|
118
|
+
fingerprinted observations, and consumes the exact delta; a revised brief arrives as a new
|
|
119
|
+
fingerprint, because the same fingerprint cannot yield a different answer. The receipt claims no
|
|
120
|
+
publication, no learner outcome in production, and no acceptance beyond the checks actually run.
|
|
121
|
+
|
|
122
|
+
## Outputs
|
|
123
|
+
|
|
124
|
+
| Kind | File | Type | Required |
|
|
125
|
+
| --- | --- | --- | --- |
|
|
126
|
+
| `content-generation-receipt` | `response/response.md` | md | yes |
|
|
127
|
+
| `content-brief` | `response/brief.md` | md | yes |
|
|
128
|
+
| `e2e` | `response/data/e2e.json` | data | no |
|
|
129
|
+
| `content-review` | `review/response/review.md` | md | yes |
|
|
130
|
+
| `article` | `response/artifacts/article.<language>.md` | artifact | yes |
|
|
131
|
+
| `image` | `response/artifacts/image.<name>` | artifact | no |
|
|
132
|
+
| `image-prompt` | `response/artifacts/prompt.<name>.txt` | artifact | no |
|
|
133
|
+
| `track` | `response/artifacts/track.<language>.<extension>` | artifact | no |
|
|
134
|
+
|
|
135
|
+
## Stops
|
|
136
|
+
|
|
137
|
+
| Code | Disposition |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `INVALID_INPUT` | terminate |
|
|
140
|
+
| `SOURCE_DRIFT` | terminate |
|
|
141
|
+
| `NO_PROGRESS` | terminate |
|
|
142
|
+
| `BRIEF_UNBOUND` | terminate |
|
|
143
|
+
| `OUTCOME_UNCOVERED` | terminate |
|
|
144
|
+
| `IMAGE_UNAVAILABLE` | terminate |
|
|
145
|
+
| `CODE_BUILD_FAILED` | terminate |
|
|
146
|
+
| `E2E_FAILED` | terminate |
|
|
147
|
+
| `CONTRACT_WEAKENED` | terminate |
|
|
148
|
+
| `REVIEW_REVISION_REQUIRED` | fallback |
|
|
149
|
+
| `REVIEW_ROUNDS_EXHAUSTED` | terminate |
|
|
150
|
+
|
|
151
|
+
## Next
|
|
152
|
+
|
|
153
|
+
| When | Operator |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
| the routed content object or the shared runtime does not answer at the frozen binding | `workspace.bind` |
|